Merge remote-tracking branch 'origin/merge-to-testing' into testing
authorMathieu <mbaudier@argeo.org>
Tue, 6 Dec 2022 05:47:19 +0000 (06:47 +0100)
committerMathieu <mbaudier@argeo.org>
Tue, 6 Dec 2022 05:47:19 +0000 (06:47 +0100)
1473 files changed:
.gitignore
Makefile
Makefile-rcp.mk
branch.mk
cnf/build.bnd [deleted file]
cnf/ext/osgirepo.bnd [deleted file]
cnf/testing.bnd [deleted file]
cnf/unstable.bnd [deleted file]
configure [changed mode: 0644->0755]
eclipse/org.argeo.cms.e4/.classpath [deleted file]
eclipse/org.argeo.cms.e4/.project [deleted file]
eclipse/org.argeo.cms.e4/META-INF/.gitignore [deleted file]
eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml [deleted file]
eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml [deleted file]
eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml [deleted file]
eclipse/org.argeo.cms.e4/bnd.bnd [deleted file]
eclipse/org.argeo.cms.e4/build.properties [deleted file]
eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi [deleted file]
eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java [deleted file]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java [deleted file]
eclipse/org.argeo.cms.servlet/.classpath [deleted file]
eclipse/org.argeo.cms.servlet/.project [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml [deleted file]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml [deleted file]
eclipse/org.argeo.cms.servlet/bnd.bnd [deleted file]
eclipse/org.argeo.cms.servlet/build.properties [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java [deleted file]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java [deleted file]
eclipse/org.argeo.cms.swt/.classpath [deleted file]
eclipse/org.argeo.cms.swt/.project [deleted file]
eclipse/org.argeo.cms.swt/META-INF/.gitignore [deleted file]
eclipse/org.argeo.cms.swt/bnd.bnd [deleted file]
eclipse/org.argeo.cms.swt/build.properties [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/add.png [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/close-all.png [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/delete.png [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/edit.png [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/save-all.png [deleted file]
eclipse/org.argeo.cms.swt/icons/actions/save.png [deleted file]
eclipse/org.argeo.cms.swt/icons/active.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/add.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/add.png [deleted file]
eclipse/org.argeo.cms.swt/icons/addFolder.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/addPrivileges.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/addRepo.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/addWorkspace.png [deleted file]
eclipse/org.argeo.cms.swt/icons/adminLog.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/batch.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/begin.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/binary.png [deleted file]
eclipse/org.argeo.cms.swt/icons/browser.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/bundles.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/changePassword.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/clear.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/close-all.png [deleted file]
eclipse/org.argeo.cms.swt/icons/commit.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/delete.png [deleted file]
eclipse/org.argeo.cms.swt/icons/dumpNode.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/file.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/folder.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/getSize.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/group.png [deleted file]
eclipse/org.argeo.cms.swt/icons/home.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/home.png [deleted file]
eclipse/org.argeo.cms.swt/icons/import_fs.png [deleted file]
eclipse/org.argeo.cms.swt/icons/installed.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/log.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/logout.png [deleted file]
eclipse/org.argeo.cms.swt/icons/maintenance.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/node.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/nodes.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/password.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/person-logged-in.png [deleted file]
eclipse/org.argeo.cms.swt/icons/person.png [deleted file]
eclipse/org.argeo.cms.swt/icons/query.png [deleted file]
eclipse/org.argeo.cms.swt/icons/refresh.png [deleted file]
eclipse/org.argeo.cms.swt/icons/remote_connected.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/remove.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/removePrivileges.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/rename.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/repositories.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/repository_connected.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/resolved.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/role.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/rollback.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/save-all.png [deleted file]
eclipse/org.argeo.cms.swt/icons/save.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/save.png [deleted file]
eclipse/org.argeo.cms.swt/icons/save_security.png [deleted file]
eclipse/org.argeo.cms.swt/icons/save_security_disabled.png [deleted file]
eclipse/org.argeo.cms.swt/icons/security.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/service_published.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/service_referenced.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/sort.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/starting.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/sync.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/user.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/users.gif [deleted file]
eclipse/org.argeo.cms.swt/icons/workgroup.png [deleted file]
eclipse/org.argeo.cms.swt/icons/workgroup.xcf [deleted file]
eclipse/org.argeo.cms.swt/icons/workspace_connected.png [deleted file]
eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java [deleted file]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/.classpath [deleted file]
jcr/org.argeo.cms.jcr/.project [deleted file]
jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml [deleted file]
jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml [deleted file]
jcr/org.argeo.cms.jcr/bnd.bnd [deleted file]
jcr/org.argeo.cms.jcr/build.properties [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java [deleted file]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java [deleted file]
jcr/org.argeo.cms.ui/.classpath [deleted file]
jcr/org.argeo.cms.ui/.project [deleted file]
jcr/org.argeo.cms.ui/META-INF/.gitignore [deleted file]
jcr/org.argeo.cms.ui/bnd.bnd [deleted file]
jcr/org.argeo.cms.ui/build.properties [deleted file]
jcr/org.argeo.cms.ui/icons/loading.gif [deleted file]
jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png [deleted file]
jcr/org.argeo.cms.ui/icons/noPic-square-640px.png [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java [deleted file]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java [deleted file]
jni/.cproject [deleted file]
jni/.project [deleted file]
jni/.settings/language.settings.xml [deleted file]
jni/.settings/org.eclipse.cdt.core.prefs [deleted file]
jni/Makefile [deleted file]
jni/jni.mk [deleted file]
jni/org_argeo_api_uuid_libuuid/.gitignore [deleted file]
jni/org_argeo_api_uuid_libuuid/Makefile [deleted file]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c [deleted file]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h [deleted file]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c [deleted file]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h [deleted file]
org.argeo.api.acr/.classpath
org.argeo.api.acr/bnd.bnd
org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java
org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java [deleted file]
org.argeo.api.acr/src/org/argeo/api/acr/Content.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java [deleted file]
org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java
org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java [deleted file]
org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java
org.argeo.api.acr/src/org/argeo/api/acr/DName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java [deleted file]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java
org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java [new file with mode: 0644]
org.argeo.api.cli/.classpath [new file with mode: 0644]
org.argeo.api.cli/.project [new file with mode: 0644]
org.argeo.api.cli/bnd.bnd [new file with mode: 0644]
org.argeo.api.cli/build.properties [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java [new file with mode: 0644]
org.argeo.api.cli/src/org/argeo/api/cli/package-info.java [new file with mode: 0644]
org.argeo.api.cms/.classpath
org.argeo.api.cms/bnd.bnd
org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java
org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java
org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java [deleted file]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java [new file with mode: 0644]
org.argeo.api.register/.classpath [new file with mode: 0644]
org.argeo.api.register/.project [new file with mode: 0644]
org.argeo.api.register/META-INF/.gitignore [new file with mode: 0644]
org.argeo.api.register/bnd.bnd [new file with mode: 0644]
org.argeo.api.register/build.properties [new file with mode: 0644]
org.argeo.api.register/src/org/argeo/api/register/Component.java [new file with mode: 0644]
org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java [new file with mode: 0644]
org.argeo.api.register/src/org/argeo/api/register/RankingKey.java [new file with mode: 0644]
org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java [new file with mode: 0644]
org.argeo.api.uuid/.classpath
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java
org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java
org.argeo.cms.cli/.classpath [new file with mode: 0644]
org.argeo.cms.cli/.project [new file with mode: 0644]
org.argeo.cms.cli/bnd.bnd [new file with mode: 0644]
org.argeo.cms.cli/build.properties [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java [new file with mode: 0644]
org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java [new file with mode: 0644]
org.argeo.cms.ee/.classpath [new file with mode: 0644]
org.argeo.cms.ee/.project [new file with mode: 0644]
org.argeo.cms.ee/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
org.argeo.cms.ee/OSGI-INF/statusHandler.xml [new file with mode: 0644]
org.argeo.cms.ee/bnd.bnd [new file with mode: 0644]
org.argeo.cms.ee/build.properties [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/.classpath [new file with mode: 0644]
org.argeo.cms.lib.jetty/.project [new file with mode: 0644]
org.argeo.cms.lib.jetty/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.jetty/build.properties [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java [new file with mode: 0644]
org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/.classpath [new file with mode: 0644]
org.argeo.cms.lib.sshd/.gitignore [new file with mode: 0644]
org.argeo.cms.lib.sshd/.project [new file with mode: 0644]
org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml [new file with mode: 0644]
org.argeo.cms.lib.sshd/bnd.bnd [new file with mode: 0644]
org.argeo.cms.lib.sshd/build.properties [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java [new file with mode: 0644]
org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java [new file with mode: 0644]
org.argeo.cms.pgsql/.classpath [deleted file]
org.argeo.cms.pgsql/.project [deleted file]
org.argeo.cms.pgsql/bnd.bnd [deleted file]
org.argeo.cms.pgsql/build.properties [deleted file]
org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java [deleted file]
org.argeo.cms.ux/.classpath [new file with mode: 0644]
org.argeo.cms.ux/.project [new file with mode: 0644]
org.argeo.cms.ux/bnd.bnd [new file with mode: 0644]
org.argeo.cms.ux/build.properties [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java [new file with mode: 0644]
org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java [new file with mode: 0644]
org.argeo.cms/.classpath
org.argeo.cms/.gitignore [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsAuthenticator.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsContentRepository.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsContext.xml
org.argeo.cms/OSGI-INF/cmsDeployment.xml
org.argeo.cms/OSGI-INF/cmsEventBus.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsState.xml
org.argeo.cms/OSGI-INF/cmsUserAdmin.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsUserManager.xml
org.argeo.cms/OSGI-INF/deployConfig.xml [deleted file]
org.argeo.cms/OSGI-INF/nodeUserAdmin.xml [deleted file]
org.argeo.cms/OSGI-INF/simpleTransactionManager.xml [deleted file]
org.argeo.cms/OSGI-INF/transactionManager.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/uuidFactory.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/build.properties
org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java
org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java [deleted file]
org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java [deleted file]
org.argeo.cms/src/org/argeo/cms/ArgeoNames.java
org.argeo.cms/src/org/argeo/cms/ArgeoTypes.java
org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CmsException.java [deleted file]
org.argeo.cms/src/org/argeo/cms/CmsSshd.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java [deleted file]
org.argeo.cms/src/org/argeo/cms/CurrentUser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/LocaleUtils.java
org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/SystemRole.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/MountManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java
org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java
org.argeo.cms/src/org/argeo/cms/client/CmsClient.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavClient.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/PathSync.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/SyncResult.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java
org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java
org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg
org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java
org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/Keyring.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/package-info.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/package-info.java [deleted file]
org.argeo.cms/src/org/argeo/cms/util/CompositeString.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/CsvParser.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/DirH.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/FsUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/LangUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/OS.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/Tester.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/Throughput.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/util/package-info.java [new file with mode: 0644]
org.argeo.init/.classpath
org.argeo.init/src/org/argeo/init/Service.java
org.argeo.init/src/org/argeo/init/a2/A2Branch.java
org.argeo.init/src/org/argeo/init/a2/A2Component.java
org.argeo.init/src/org/argeo/init/a2/A2Contribution.java
org.argeo.init/src/org/argeo/init/a2/A2Module.java
org.argeo.init/src/org/argeo/init/a2/A2Source.java
org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java
org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java
org.argeo.init/src/org/argeo/init/a2/FsA2Source.java
org.argeo.init/src/org/argeo/init/a2/FsM2Source.java
org.argeo.init/src/org/argeo/init/a2/OsgiContext.java
org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java
org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java
org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java
org.argeo.init/src/org/argeo/init/logging/ThinLogging.java
org.argeo.init/src/org/argeo/init/osgi/Activator.java
org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java
org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java
org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java
org.argeo.init/src/org/argeo/init/osgi/log4j.properties [deleted file]
org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java [new file with mode: 0644]
org.argeo.util/.classpath [deleted file]
org.argeo.util/.project [deleted file]
org.argeo.util/META-INF/.gitignore [deleted file]
org.argeo.util/bnd.bnd [deleted file]
org.argeo.util/build.properties [deleted file]
org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java [deleted file]
org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java [deleted file]
org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java [deleted file]
org.argeo.util/src/org/argeo/osgi/metatype/package-info.java [deleted file]
org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java [deleted file]
org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java [deleted file]
org.argeo.util/src/org/argeo/osgi/transaction/package-info.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg [deleted file]
org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java [deleted file]
org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java [deleted file]
org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java [deleted file]
org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java [deleted file]
org.argeo.util/src/org/argeo/util/CsvParser.java [deleted file]
org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java [deleted file]
org.argeo.util/src/org/argeo/util/CsvWriter.java [deleted file]
org.argeo.util/src/org/argeo/util/DictionaryKeys.java [deleted file]
org.argeo.util/src/org/argeo/util/DigestUtils.java [deleted file]
org.argeo.util/src/org/argeo/util/DirH.java [deleted file]
org.argeo.util/src/org/argeo/util/FsUtils.java [deleted file]
org.argeo.util/src/org/argeo/util/LangUtils.java [deleted file]
org.argeo.util/src/org/argeo/util/OS.java [deleted file]
org.argeo.util/src/org/argeo/util/PasswordEncryption.java [deleted file]
org.argeo.util/src/org/argeo/util/ServiceChannel.java [deleted file]
org.argeo.util/src/org/argeo/util/StreamUtils.java [deleted file]
org.argeo.util/src/org/argeo/util/Tester.java [deleted file]
org.argeo.util/src/org/argeo/util/TesterStatus.java [deleted file]
org.argeo.util/src/org/argeo/util/Throughput.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/AuthPassword.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/Distinguished.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdapObjs.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdifParser.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/LdifWriter.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/NamingUtils.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/NodeOID.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/SharedSecret.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/SrvRecord.java [deleted file]
org.argeo.util/src/org/argeo/util/naming/package-info.java [deleted file]
org.argeo.util/src/org/argeo/util/package-info.java [deleted file]
org.argeo.util/src/org/argeo/util/register/Component.java [deleted file]
org.argeo.util/src/org/argeo/util/register/Register.java [deleted file]
org.argeo.util/src/org/argeo/util/register/Singleton.java [deleted file]
org.argeo.util/src/org/argeo/util/register/StaticRegister.java [deleted file]
osgi/equinox/org.argeo.cms.lib.equinox/.classpath [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/.gitignore [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/.project [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/build.properties [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java [new file with mode: 0644]
osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/.classpath [deleted file]
rap/org.argeo.cms.e4.rap/.project [deleted file]
rap/org.argeo.cms.e4.rap/META-INF/.gitignore [deleted file]
rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml [deleted file]
rap/org.argeo.cms.e4.rap/bnd.bnd [deleted file]
rap/org.argeo.cms.e4.rap/build.properties [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java [deleted file]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java [deleted file]
rap/org.argeo.cms.ui.rap/.classpath [deleted file]
rap/org.argeo.cms.ui.rap/.project [deleted file]
rap/org.argeo.cms.ui.rap/META-INF/.gitignore [deleted file]
rap/org.argeo.cms.ui.rap/bnd.bnd [deleted file]
rap/org.argeo.cms.ui.rap/build.properties [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java [deleted file]
rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java [deleted file]
rap/org.argeo.swt.specific.rap/.classpath [deleted file]
rap/org.argeo.swt.specific.rap/.project [deleted file]
rap/org.argeo.swt.specific.rap/META-INF/.gitignore [deleted file]
rap/org.argeo.swt.specific.rap/bnd.bnd [deleted file]
rap/org.argeo.swt.specific.rap/build.properties [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java [deleted file]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java [deleted file]
rcp/org.argeo.cms.e4.rcp/.classpath [deleted file]
rcp/org.argeo.cms.e4.rcp/.gitignore [deleted file]
rcp/org.argeo.cms.e4.rcp/.project [deleted file]
rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs [deleted file]
rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs [deleted file]
rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore [deleted file]
rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi [deleted file]
rcp/org.argeo.cms.e4.rcp/argeo-companion.properties [deleted file]
rcp/org.argeo.cms.e4.rcp/bnd.bnd [deleted file]
rcp/org.argeo.cms.e4.rcp/build.properties [deleted file]
rcp/org.argeo.cms.e4.rcp/log4j.properties [deleted file]
rcp/org.argeo.cms.e4.rcp/plugin.xml [deleted file]
rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java [deleted file]
rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java [deleted file]
rcp/org.argeo.cms.ui.rcp/.classpath [deleted file]
rcp/org.argeo.cms.ui.rcp/.gitignore [deleted file]
rcp/org.argeo.cms.ui.rcp/.project [deleted file]
rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore [deleted file]
rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml [deleted file]
rcp/org.argeo.cms.ui.rcp/bnd.bnd [deleted file]
rcp/org.argeo.cms.ui.rcp/build.properties [deleted file]
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java [deleted file]
rcp/org.argeo.swt.minidesktop/.classpath [deleted file]
rcp/org.argeo.swt.minidesktop/.gitignore [deleted file]
rcp/org.argeo.swt.minidesktop/.project [deleted file]
rcp/org.argeo.swt.minidesktop/META-INF/.gitignore [deleted file]
rcp/org.argeo.swt.minidesktop/bnd.bnd [deleted file]
rcp/org.argeo.swt.minidesktop/build.properties [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png [deleted file]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png [deleted file]
rcp/org.argeo.swt.specific.rcp/.classpath [deleted file]
rcp/org.argeo.swt.specific.rcp/.gitignore [deleted file]
rcp/org.argeo.swt.specific.rcp/.project [deleted file]
rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore [deleted file]
rcp/org.argeo.swt.specific.rcp/bnd.bnd [deleted file]
rcp/org.argeo.swt.specific.rcp/build.properties [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java [deleted file]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java [deleted file]
sdk/argeo-build
sdk/branches/testing.bnd [new file with mode: 0644]
sdk/branches/unstable.bnd [new file with mode: 0644]
sdk/cms-e4-rap.properties
sdk/cms-rcp.properties [new file with mode: 0644]
sdk/deploy/.gitignore [new file with mode: 0644]
sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open [new file with mode: 0755]
sdk/deploy/argeo-cms/usr/bin/argeo [new file with mode: 0755]
sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring [new file with mode: 0644]
sdk/deploy/argeo-init/etc/argeo.user.d/jvm.args [new file with mode: 0644]
sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service
sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/jvm.args
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]
swt/org.argeo.cms.e4/.classpath [new file with mode: 0644]
swt/org.argeo.cms.e4/.project [new file with mode: 0644]
swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml [new file with mode: 0644]
swt/org.argeo.cms.e4/bnd.bnd [new file with mode: 0644]
swt/org.argeo.cms.e4/build.properties [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.swt/.classpath [new file with mode: 0644]
swt/org.argeo.cms.swt/.project [new file with mode: 0644]
swt/org.argeo.cms.swt/META-INF/.gitignore [new file with mode: 0644]
swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml [new file with mode: 0644]
swt/org.argeo.cms.swt/bnd.bnd [new file with mode: 0644]
swt/org.argeo.cms.swt/build.properties [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java [new file with mode: 0644]
swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/.classpath [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/.gitignore [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/.project [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/META-INF/.gitignore [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/bnd.bnd [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/build.properties [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png [new file with mode: 0644]
swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/.classpath [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/.project [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/bnd.bnd [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/build.properties [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java [new file with mode: 0644]
swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/.classpath [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/.project [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/bnd.bnd [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/build.properties [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java [new file with mode: 0644]
swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/.classpath [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/.project [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/bnd.bnd [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/build.properties [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java [new file with mode: 0644]
swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/.classpath [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/.project [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/build.properties [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/log4j.properties [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/plugin.xml [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/.classpath [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/.project [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/build.properties [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java [new file with mode: 0644]
swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/.classpath [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/.project [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/build.properties [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java [new file with mode: 0644]
swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java [new file with mode: 0644]

index c306476f0ea5396b0638b3f42c036df17ba18d31..11caf4780ec37c342050325a30d1e9e7701105f2 100644 (file)
@@ -1,7 +1,4 @@
 **/bin/
-**/target/
-**/generated/
 **/MANIFEST.MF
-/build/
 /sdk.mk
 /output/
index 49a995426246cbd9dbb053f8b6283a523f5876da..330bc4fca94bd79b5de123e25dd24079678ad9c7 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,62 +1,53 @@
 include sdk.mk
-.PHONY: clean all osgi jni
+.PHONY: clean all osgi
 
-all: osgi jni move-rap
-       $(MAKE) -f Makefile-rcp.mk
-
-move-rap:
-       mkdir -p $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
-       mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rap.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
-       touch $(BUILD_BASE)/*.rap/bnd.bnd
+all: osgi
+       $(MAKE) -f Makefile-rcp.mk all
 
 A2_CATEGORY = org.argeo.cms
 
 BUNDLES = \
 org.argeo.init \
-org.argeo.util \
 org.argeo.api.uuid \
+org.argeo.api.register \
 org.argeo.api.acr \
+org.argeo.api.cli \
 org.argeo.api.cms \
 org.argeo.cms \
-org.argeo.cms.pgsql \
-eclipse/org.argeo.cms.servlet \
-eclipse/org.argeo.cms.swt \
-eclipse/org.argeo.cms.e4 \
-rap/org.argeo.cms.ui.rap \
-rap/org.argeo.swt.specific.rap \
-rap/org.argeo.cms.e4.rap \
-jcr/org.argeo.cms.jcr \
-jcr/org.argeo.cms.ui \
-
-JAVADOC_BUNDLES =  \
-org.argeo.api.uuid \
-org.argeo.api.acr \
-org.argeo.api.cms
-
-JAVADOC_PACKAGES =  \
-org.argeo.api.uuid \
-org.argeo.api.acr \
-org.argeo.api.cms
-
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
-
-VPATH = .:eclipse:rap:jcr
+org.argeo.cms.ux \
+org.argeo.cms.ee \
+org.argeo.cms.lib.jetty \
+org.argeo.cms.lib.sshd \
+org.argeo.cms.cli \
+osgi/equinox/org.argeo.cms.lib.equinox \
+swt/org.argeo.swt.minidesktop \
+swt/org.argeo.cms.swt \
+swt/org.argeo.cms.e4 \
+swt/rap/org.argeo.swt.specific.rap \
+swt/rap/org.argeo.cms.swt.rap \
+swt/rap/org.argeo.cms.e4.rap \
 
 DEP_CATEGORIES = \
 org.argeo.tp \
-org.argeo.tp.apache \
+org.argeo.tp.crypto \
 org.argeo.tp.jetty \
-org.argeo.tp.eclipse.equinox \
-org.argeo.tp.eclipse.rap \
-org.argeo.tp.jcr
+osgi/api/org.argeo.tp.osgi \
+osgi/equinox/org.argeo.tp.eclipse \
+swt/rap/org.argeo.tp.swt \
+swt/rap/org.argeo.tp.swt.workbench \
+$(A2_CATEGORY) \
+swt/$(A2_CATEGORY) \
+swt/rap/$(A2_CATEGORY) \
 
-jni:
-       $(MAKE) -C jni
+JAVADOC_PACKAGES =  \
+org.argeo.api.uuid \
+org.argeo.api.acr \
+org.argeo.api.cms
 
 clean:
        rm -rf $(BUILD_BASE)
-       $(MAKE) -C jni clean
        $(MAKE) -f Makefile-rcp.mk clean
 
+A2_BUNDLES_CLASSPATH = $(subst $(space),$(pathsep),$(strip $(A2_BUNDLES)))
+
 include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
index 8811d2448d15539af7d6b6f71a854e3eb0c476f0..29f1a23a382f3dcabe556338fd56c61f8d68fa67 100644 (file)
@@ -1,32 +1,31 @@
 include sdk.mk
 .PHONY: clean all osgi
 
-all: osgi
+all: osgi 
 
-A2_CATEGORY = org.argeo.cms.eclipse.rcp
+A2_CATEGORY = org.argeo.cms
 
 BUNDLES = \
-rcp/org.argeo.cms.e4.rcp \
-rcp/org.argeo.cms.ui.rcp \
-rcp/org.argeo.swt.minidesktop \
-rcp/org.argeo.swt.specific.rcp \
-
-A2_OUTPUT = $(SDK_BUILD_BASE)/a2
-A2_BASE = $(A2_OUTPUT)
+swt/rcp/org.argeo.swt.specific.rcp \
+swt/rcp/org.argeo.cms.swt.rcp \
+swt/rcp/org.argeo.cms.e4.rcp \
 
 DEP_CATEGORIES = \
 org.argeo.cms \
+swt/org.argeo.cms \
 org.argeo.tp \
-org.argeo.tp.apache \
+org.argeo.tp.crypto \
 org.argeo.tp.jetty \
-org.argeo.tp.eclipse.equinox \
-org.argeo.tp.eclipse.rcp \
-org.argeo.tp.jcr
+osgi/equinox/org.argeo.tp.eclipse \
+osgi/api/org.argeo.tp.osgi \
+swt/rcp/org.argeo.tp.swt \
+lib/linux/x86_64/swt/rcp/org.argeo.tp.swt \
+swt/rcp/org.argeo.tp.swt.workbench \
 
 
 clean:
        rm -rf $(BUILD_BASE)
 
-VPATH = .:rcp
+VPATH = .:swt/rcp
 
 include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
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/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/ext/osgirepo.bnd b/cnf/ext/osgirepo.bnd
deleted file mode 100644 (file)
index 427e7c5..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
--plugin.osgirepo=aQute.bnd.repository.osgi.OSGiRepository;\
-               locations=file://${workspace}/sdk/target/a2/index.xml;\
-               max.stale=-1;\
-               poll.time=86400;\
-               name=local;\
-               cache=${build}/cache/local,\
-               aQute.bnd.repository.osgi.OSGiRepository;\
-               locations=file://${workspace}/sdk/target/sdk-2.3.1-SNAPSHOT-a2-target/index.xml;\
-               max.stale=-1;\
-               poll.time=86400;\
-               name=local;\
-               cache=${build}/cache/local
\ No newline at end of file
diff --git a/cnf/testing.bnd b/cnf/testing.bnd
deleted file mode 100644 (file)
index 18e87d6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-MAJOR=2
-MINOR=1
-MICRO=106
-qualifier=.next
-
-category=org.argeo.commons
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/testing/argeo-osgi-2/argeo
-argeo.rpm.suffix=
diff --git a/cnf/unstable.bnd b/cnf/unstable.bnd
deleted file mode 100644 (file)
index 6ca2e76..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-MAJOR=2
-MINOR=3
-MICRO=7
-qualifier=.next
-
-category=org.argeo.commons
-Bundle-RequiredExecutionEnvironment=JavaSE-11
-
-argeo.rpm.stagingRepository=/srv/rpmfactory/unstable/argeo-osgi-2/argeo
-argeo.rpm.suffix=-unstable
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/eclipse/org.argeo.cms.e4/.classpath b/eclipse/org.argeo.cms.e4/.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/eclipse/org.argeo.cms.e4/.project b/eclipse/org.argeo.cms.e4/.project
deleted file mode 100644 (file)
index 0c04069..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.e4</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/eclipse/org.argeo.cms.e4/META-INF/.gitignore b/eclipse/org.argeo.cms.e4/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml
deleted file mode 100644 (file)
index fcd3ae5..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default CallbackHandler">
-   <implementation class="org.argeo.cms.swt.auth.DynamicCallbackHandler"/>
-   <service>
-      <provide interface="javax.security.auth.callback.CallbackHandler"/>
-   </service>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml
deleted file mode 100644 (file)
index 65690f2..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="Home Repository">
-   <implementation class="org.argeo.cms.e4.OsgiFilterContextFunction"/>
-   <property name="service.context.key" type="String" value="(cn=home)"/>
-   <service>
-      <provide interface="org.eclipse.e4.core.contexts.IContextFunction"/>
-   </service>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml
deleted file mode 100644 (file)
index a267aa5..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="User Admin Wrapper">
-   <implementation class="org.argeo.cms.e4.users.UserAdminWrapper"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
-   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-   <service>
-      <provide interface="org.argeo.cms.e4.users.UserAdminWrapper"/>
-   </service>
-   <reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.e4/bnd.bnd b/eclipse/org.argeo.cms.e4/bnd.bnd
deleted file mode 100644 (file)
index ea95043..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-Service-Component: OSGI-INF/homeRepository.xml,\
-OSGI-INF/userAdminWrapper.xml,\
-OSGI-INF/defaultCallbackHandler.xml
-Bundle-ActivationPolicy: lazy
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.swt.widgets;version="0.0.0",\
-org.eclipse.e4.ui.model.application.ui,\
-org.eclipse.e4.ui.model.application,\
-javax.jcr.nodetype,\
-org.argeo.cms,\
-org.eclipse.core.commands.common,\
-org.eclipse.jface.window,\
-org.argeo.cms.swt.auth,\
-org.apache.jackrabbit.*;version="[2,3)",\
-javax.servlet.*;version="[3,5)",\
-*
diff --git a/eclipse/org.argeo.cms.e4/build.properties b/eclipse/org.argeo.cms.e4/build.properties
deleted file mode 100644 (file)
index e46a7ba..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               OSGI-INF/,\
-               .,\
-               OSGI-INF/homeRepository.xml,\
-               OSGI-INF/userAdminWrapper.xml,\
-               OSGI-INF/defaultCallbackHandler.xml,\
-               e4xmi/cms-demo.e4xmi
-source.. = src/
diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi
deleted file mode 100644 (file)
index 89bcc37..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?xml version="1.0" encoding="ASCII"?>
-<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_XqkCQKknEeObFrG_clJBYA" elementId="">
-  <children xsi:type="basic:TrimmedWindow" xmi:id="_Zdy6cKknEeObFrG_clJBYA" elementId="org.argeo.cms.e4.apps.admin.trimmedwindow.0" label="" x="10" y="10" width="500" height="500">
-    <persistedState key="styleOverride" value="8"/>
-    <tags>shellMaximized</tags>
-    <tags>auth.cn=admin,ou=roles,ou=node</tags>
-    <children xsi:type="advanced:PerspectiveStack" xmi:id="_jXVqsCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspectivestack.0" selectedElement="_xOVlsDvOEeiF1foPJZSZkw">
-      <children xsi:type="advanced:Perspective" xmi:id="_xOVlsDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.perspective.users" label="Users" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png">
-        <tags>auth.cn=admin,ou=roles,ou=node</tags>
-        <children xsi:type="basic:PartSashContainer" xmi:id="_1tQoEDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partsashcontainer.2" horizontal="true">
-          <children xsi:type="basic:PartStack" xmi:id="_vtbKkDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.4" containerData="4000" selectedElement="_9gukYDvOEeiF1foPJZSZkw">
-            <children xsi:type="basic:Part" xmi:id="_9gukYDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.users" containerData="" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.UsersView" label="Users" iconURI="platform:/plugin/org.argeo.cms.swt/icons/person.png">
-              <handlers xmi:id="_0mN68DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.4" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.handlers.NewUser" command="_uL5i4DvjEeiF1foPJZSZkw"/>
-              <handlers xmi:id="_ODLdgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.5" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.handlers.DeleteUsers" command="_xkcMADvjEeiF1foPJZSZkw"/>
-              <toolbar xmi:id="_jLWmkDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.1">
-                <children xsi:type="menu:HandledToolItem" xmi:id="_jy_OUDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.cms.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
-                <children xsi:type="menu:HandledToolItem" xmi:id="_9qszMDvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.cms.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
-              </toolbar>
-            </children>
-          </children>
-          <children xsi:type="basic:PartStack" xmi:id="__g1a8DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.3" containerData="4000">
-            <tags>usersEditorArea</tags>
-          </children>
-          <children xsi:type="basic:PartStack" xmi:id="_-mFn8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partstack.5" containerData="2000">
-            <children xsi:type="basic:Part" xmi:id="_6etk4DvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.part.groups" containerData="" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.GroupsView" label="Groups" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png">
-              <handlers xmi:id="_cmShoDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.6" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.handlers.NewGroup" command="_uL5i4DvjEeiF1foPJZSZkw"/>
-              <handlers xmi:id="_fbYfcDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.7" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.handlers.DeleteGroups" command="_xkcMADvjEeiF1foPJZSZkw"/>
-              <toolbar xmi:id="_Us0rADvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.toolbar.2">
-                <children xsi:type="menu:HandledToolItem" xmi:id="_VQTLgDvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.new" label="New" iconURI="platform:/plugin/org.argeo.cms.swt/icons/add.png" command="_uL5i4DvjEeiF1foPJZSZkw"/>
-                <children xsi:type="menu:HandledToolItem" xmi:id="_XfME8DvkEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.cms.swt/icons/delete.png" command="_xkcMADvjEeiF1foPJZSZkw"/>
-              </toolbar>
-            </children>
-          </children>
-        </children>
-      </children>
-      <children xsi:type="advanced:Perspective" xmi:id="_jvjWYCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspective.data" label="Data" iconURI="platform:/plugin/org.argeo.cms.swt/icons/nodes.gif">
-        <children xsi:type="basic:PartSashContainer" xmi:id="_h3tvMCkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partsashcontainer.0" selectedElement="_0B9SECkxEein5vuhpK-Dew" horizontal="true">
-          <children xsi:type="basic:PartStack" xmi:id="_0B9SECkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.0" containerData="4000" selectedElement="_WAjPkCkTEein5vuhpK-Dew">
-            <children xsi:type="basic:Part" xmi:id="_WAjPkCkTEein5vuhpK-Dew" elementId="org.argeo.cms.e4.jcrbrowser" containerData="" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.swt/icons/browser.gif">
-              <menus xsi:type="menu:PopupMenu" xmi:id="_eXiUECqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.popupmenu.nodeViewer">
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_GVeO8CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.cms.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_fU238CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.cms.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_U4o9cCqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.cms.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_Ncxo0CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.remove" label="Remove" iconURI="platform:/plugin/org.argeo.cms.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-              </menus>
-              <menus xmi:id="_oRg_ACqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.menu.0">
-                <tags>ViewMenu</tags>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_yJR8ECqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.cms.swt/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_o6HQECqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.cms.swt/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_5D7aACqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.cms.swt/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_7rR2wCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.cms.swt/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-                <children xsi:type="menu:HandledMenuItem" xmi:id="_XsHLgFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledmenuitem.0" iconURI="platform:/plugin/org.argeo.cms.swt/icons/addRepo.gif" command="_ZWpasFgQEeiknZQLx-vtnA"/>
-              </menus>
-            </children>
-          </children>
-          <children xsi:type="basic:PartStack" xmi:id="_mHrEUCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.1" containerData="6000">
-            <tags>dataExplorer</tags>
-          </children>
-        </children>
-      </children>
-      <children xsi:type="advanced:Perspective" xmi:id="_u5ZakFhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.perspective.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif">
-        <children xsi:type="basic:PartStack" xmi:id="_7i7t8FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.partstack.6">
-          <children xsi:type="basic:Part" xmi:id="_Z-3cMFhbEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.osgiConfigurations" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.OsgiConfigurationsView" label="OSGi Configurations" iconURI="platform:/plugin/org.argeo.cms.swt/icons/node.gif"/>
-          <children xsi:type="basic:Part" xmi:id="_8dM90FhJEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.cmsSessions" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.CmsSessionsView" label="CMS Sessions" iconURI="platform:/plugin/org.argeo.cms.swt/icons/person-logged-in.png"/>
-          <children xsi:type="basic:Part" xmi:id="_KqRZIFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.modules" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.ModulesView" label="Modules" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif"/>
-          <children xsi:type="basic:Part" xmi:id="_dXtIoFhNEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.part.bundles" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.monitoring.BundlesView" label="Bundles" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif"/>
-        </children>
-      </children>
-      <children xsi:type="advanced:Perspective" xmi:id="_ABK2ADsNEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.perspective.files" label="Files" iconURI="platform:/plugin/org.argeo.cms.swt/icons/file.gif">
-        <children xsi:type="basic:PartSashContainer" xmi:id="_FPimEDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partsashcontainer.1" horizontal="true">
-          <children xsi:type="basic:PartStack" xmi:id="_H93NgDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.partstack.2" containerData="4000">
-            <children xsi:type="basic:Part" xmi:id="_Izxh0DsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.files" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files" iconURI="platform:/plugin/org.argeo.cms.swt/icons/file.gif"/>
-          </children>
-          <children xsi:type="basic:Part" xmi:id="_TMqBMDsSEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.part.0" containerData="6000"/>
-        </children>
-      </children>
-    </children>
-    <handlers xmi:id="_Vwax0DvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handler.8" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.OpenPerspective" command="_AF1UsDvrEeiF1foPJZSZkw"/>
-    <trimBars xmi:id="_euVxMCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.trimbar.0" side="Left">
-      <children xsi:type="menu:ToolBar" xmi:id="_fotHsCk2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.toolbar.0">
-        <children xsi:type="menu:HandledToolItem" xmi:id="_jCSQgDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.users" label="Users" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png" command="_AF1UsDvrEeiF1foPJZSZkw">
-          <tags>auth.cn=admin,ou=roles,ou=node</tags>
-          <parameters xmi:id="_lu_uYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.2" name="perspectiveId" value="org.argeo.cms.e4.perspective.users"/>
-        </children>
-        <children xsi:type="menu:HandledToolItem" xmi:id="_jfUM4Ck2Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.handledtoolitem.test" label="Data" iconURI="platform:/plugin/org.argeo.cms.swt/icons/nodes.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
-          <parameters xmi:id="_KDlXQDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.0" name="perspectiveId" value="org.argeo.cms.e4.perspective.data"/>
-        </children>
-        <children xsi:type="menu:HandledToolItem" xmi:id="_dhv80FhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.monitoring" label="Monitoring" iconURI="platform:/plugin/org.argeo.cms.swt/icons/bundles.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
-          <parameters xmi:id="_kjN0cFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.parameter.3" name="perspectiveId" value="org.argeo.cms.e4.perspective.monitoring"/>
-        </children>
-        <children xsi:type="menu:HandledToolItem" xmi:id="_b0OHUDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.handledtoolitem.files" label="Files" iconURI="platform:/plugin/org.argeo.cms.swt/icons/file.gif" command="_AF1UsDvrEeiF1foPJZSZkw">
-          <parameters xmi:id="_fXvRYDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.parameter.1" name="perspectiveId" value="org.argeo.cms.e4.perspective.files"/>
-        </children>
-        <children xsi:type="menu:ToolBarSeparator" xmi:id="_wuoL8FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.toolbarseparator.0"/>
-        <children xsi:type="menu:HandledToolItem" xmi:id="_2v8DkFhKEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.cms.swt/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
-      </children>
-    </trimBars>
-  </children>
-  <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.AddFolderNode" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_jbnNwCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.1" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.DeleteNodes" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_loxB0CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.2" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.Refresh" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_omPfkCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.3" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.RenameNode" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_dUg-cFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.9" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
-  <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.10" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
-  <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.cms.swt/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
-  <descriptors xmi:id="_sAdNwDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.userEditor" label="User Editor" iconURI="platform:/plugin/org.argeo.cms.swt/icons/person.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.UserEditor"/>
-  <descriptors xmi:id="_5nK7EDvdEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.partdescriptor.groupEditor" label="Group Editor" iconURI="platform:/plugin/org.argeo.cms.swt/icons/group.png" allowMultiple="true" category="usersEditorArea" closeable="true" dirtyable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.users.GroupEditor"/>
-  <commands xmi:id="_RgE5cCqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.addFolderNode" commandName="Add folder node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_ChJ-4CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.deleteNodes" commandName="Delete nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_TOKHsCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.refreshNodes" commandName="Refresh nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_ZrcUMCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.renameNode" commandName="Rename node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_uL5i4DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.add" commandName="Add"/>
-  <commands xmi:id="_xkcMADvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.delete" commandName="Delete"/>
-  <commands xmi:id="_AF1UsDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.openPerspective" commandName="Open Perspective">
-    <parameters xmi:id="_F3WAUDvrEeiF1foPJZSZkw" elementId="perspectiveId" name="Perspective Id" optional="false"/>
-  </commands>
-  <commands xmi:id="_ZWpasFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.addRemoteRepository" commandName="Add Remote Repository"/>
-  <commands xmi:id="_PsWd0FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.logout" commandName="Log out"/>
-  <addons xmi:id="_XqkCQaknEeObFrG_clJBYA" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
-  <addons xmi:id="_XqkCQqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
-  <addons xmi:id="_XqkCQ6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
-  <addons xmi:id="_XqkCRKknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
-  <addons xmi:id="_XqkCRaknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
-  <addons xmi:id="_XqkCRqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
-  <addons xmi:id="_XqkCR6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
-  <addons xmi:id="_8VnK8OdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.locale" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.LocaleAddon"/>
-  <addons xmi:id="_-xeJYOdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.auth" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.AuthAddon"/>
-  <categories xmi:id="_MDkwUCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.category.jcrBrowser" name="JCR Browser"/>
-</application:Application>
diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi
deleted file mode 100644 (file)
index ef659fc..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="ASCII"?>
-<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_XqkCQKknEeObFrG_clJBYA" elementId="">
-  <children xsi:type="basic:TrimmedWindow" xmi:id="_Zdy6cKknEeObFrG_clJBYA" elementId="org.argeo.cms.e4.apps.admin.trimmedwindow.0" label="" x="10" y="10" width="500" height="500">
-    <persistedState key="styleOverride" value="8"/>
-    <tags>shellMaximized</tags>
-    <tags>auth.cn=user,ou=roles,ou=node</tags>
-    <children xsi:type="basic:PartSashContainer" xmi:id="_h3tvMCkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partsashcontainer.0" selectedElement="_0B9SECkxEein5vuhpK-Dew" horizontal="true">
-      <children xsi:type="basic:PartStack" xmi:id="_0B9SECkxEein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.0" containerData="4000" selectedElement="_WAjPkCkTEein5vuhpK-Dew">
-        <children xsi:type="basic:Part" xmi:id="_WAjPkCkTEein5vuhpK-Dew" elementId="org.argeo.cms.e4.jcrbrowser" containerData="" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/browser.gif">
-          <menus xsi:type="menu:PopupMenu" xmi:id="_eXiUECqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.popupmenu.nodeViewer">
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_GVeO8CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_fU238CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_U4o9cCqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_Ncxo0CqhEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.remove" label="Remove" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-          </menus>
-          <menus xmi:id="_oRg_ACqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.menu.0">
-            <tags>ViewMenu</tags>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_yJR8ECqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.refresh" label="Refresh" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/refresh.png" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_o6HQECqTEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.addfoldernode" label="Add folder" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/addFolder.gif" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_5D7aACqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.rename" label="Rename" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/rename.gif" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_7rR2wCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handledmenuitem.delete" label="Delete" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/remove.gif" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-            <children xsi:type="menu:HandledMenuItem" xmi:id="_XsHLgFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handledmenuitem.0" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/addRepo.gif" command="_ZWpasFgQEeiknZQLx-vtnA"/>
-          </menus>
-        </children>
-      </children>
-      <children xsi:type="basic:PartStack" xmi:id="_mHrEUCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.partstack.1" containerData="6000">
-        <tags>dataExplorer</tags>
-        <children xsi:type="basic:Part" xmi:id="_LyT80MKKEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.part.egoDashboard" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.parts.EgoDashboard" label="" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/home.png">
-          <toolbar xmi:id="_Ut8wMMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.toolbar.0">
-            <children xsi:type="menu:HandledToolItem" xmi:id="_nElwUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.handledtoolitem.changepassword" label="Change password" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/actions/edit.png" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
-            <children xsi:type="menu:HandledToolItem" xmi:id="_WAD4UMKMEeqaPNgZ5fEKYw" elementId="org.argeo.cms.e4.handledtoolitem.logout" label="Log out" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/logout.png" command="_PsWd0FhLEeiknZQLx-vtnA"/>
-          </toolbar>
-        </children>
-      </children>
-    </children>
-  </children>
-  <handlers xmi:id="_Xp-P4CqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.0" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.AddFolderNode" command="_RgE5cCqREeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_jbnNwCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.1" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.DeleteNodes" command="_ChJ-4CqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_loxB0CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.2" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.Refresh" command="_TOKHsCqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_omPfkCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.handler.3" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.RenameNode" command="_ZrcUMCqYEeidr6NYQH6GbQ"/>
-  <handlers xmi:id="_dUg-cFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.9" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.handlers.AddRemoteRepository" command="_ZWpasFgQEeiknZQLx-vtnA"/>
-  <handlers xmi:id="_RQyFAFhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.handler.logout" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.CloseWorkbench" command="_PsWd0FhLEeiknZQLx-vtnA"/>
-  <handlers xmi:id="_lN4GUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.suite.e4.handler.changePassword" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangePassword" command="_jEjCUMKMEeq1Ytjq4ALs6g"/>
-  <descriptors xmi:id="_XzfoMCqlEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" label="Node Editor" iconURI="platform:/plugin/org.argeo.cms.ui.theme/icons/node.gif" allowMultiple="true" category="dataExplorer" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
-  <commands xmi:id="_RgE5cCqREeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.addFolderNode" commandName="Add folder node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_ChJ-4CqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.deleteNodes" commandName="Delete nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_TOKHsCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.refreshNodes" commandName="Refresh nodes" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_ZrcUMCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.command.renameNode" commandName="Rename node" category="_MDkwUCqYEeidr6NYQH6GbQ"/>
-  <commands xmi:id="_uL5i4DvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.add" commandName="Add"/>
-  <commands xmi:id="_xkcMADvjEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.delete" commandName="Delete"/>
-  <commands xmi:id="_AF1UsDvrEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.command.openPerspective" commandName="Open Perspective">
-    <parameters xmi:id="_F3WAUDvrEeiF1foPJZSZkw" elementId="perspectiveId" name="Perspective Id" optional="false"/>
-  </commands>
-  <commands xmi:id="_ZWpasFgQEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.addRemoteRepository" commandName="Add Remote Repository"/>
-  <commands xmi:id="_PsWd0FhLEeiknZQLx-vtnA" elementId="org.argeo.cms.e4.command.logout" commandName="Log out"/>
-  <commands xmi:id="_jEjCUMKMEeq1Ytjq4ALs6g" elementId="org.argeo.cms.e4.command.changePassword" commandName="Change Password"/>
-  <addons xmi:id="_XqkCQaknEeObFrG_clJBYA" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
-  <addons xmi:id="_XqkCQqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
-  <addons xmi:id="_XqkCQ6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
-  <addons xmi:id="_XqkCRKknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
-  <addons xmi:id="_XqkCRaknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
-  <addons xmi:id="_XqkCRqknEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
-  <addons xmi:id="_XqkCR6knEeObFrG_clJBYA" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
-  <addons xmi:id="_8VnK8OdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.locale" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.LocaleAddon"/>
-  <addons xmi:id="_-xeJYOdKEeijEOqYKRSeoQ" elementId="org.argeo.cms.e4.addon.auth" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.addons.AuthAddon"/>
-  <categories xmi:id="_MDkwUCqYEeidr6NYQH6GbQ" elementId="org.argeo.cms.e4.category.jcrBrowser" name="JCR Browser"/>
-</application:Application>
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java
deleted file mode 100644 (file)
index 21abf58..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.cms.e4;
-
-import java.util.List;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.commands.MCommand;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
-import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-
-/** Static utilities simplifying recurring Eclipse 4 patterns. */
-public class CmsE4Utils {
-       /** Open an editor based on its id. */
-       public static void openEditor(EPartService partService, String editorId, String key, String state) {
-               for (MPart part : partService.getParts()) {
-                       String id = part.getPersistedState().get(key);
-                       if (id != null && state.equals(id)) {
-                               partService.showPart(part, PartState.ACTIVATE);
-                               return;
-                       }
-               }
-
-               // new part
-               MPart part = partService.createPart(editorId);
-               if (part == null)
-                       throw new CmsException("No editor found with id " + editorId);
-               part.getPersistedState().put(key, state);
-               partService.showPart(part, PartState.ACTIVATE);
-       }
-
-       /** Dynamically creates an handled menu item from a command ID. */
-       public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app,
-                       String commandId) {
-               MCommand command = findCommand(modelService, app, commandId);
-               if (command == null)
-                       return null;
-               MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class);
-               handledItem.setCommand(command);
-               return handledItem;
-
-       }
-
-       /**
-        * Finds a command by ID.
-        * 
-        * @return the {@link MCommand} or <code>null</code> if not found.
-        */
-       public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) {
-               List<MCommand> cmds = modelService.findElements(app, null, MCommand.class, null);
-               for (MCommand cmd : cmds) {
-                       if (cmd.getElementId().equals(commandId)) {
-                               return cmd;
-                       }
-               }
-               return null;
-       }
-
-       /** Dynamically creates a direct menu item from a class. */
-       public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class<?> clss, String label) {
-               MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
-               dynamicItem.setLabel(label);
-               Bundle bundle = FrameworkUtil.getBundle(clss);
-               dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName());
-               return dynamicItem;
-       }
-
-       /** Singleton. */
-       private CmsE4Utils() {
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java
deleted file mode 100644 (file)
index c42a02a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.e4;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.e4.core.contexts.ContextFunction;
-import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.core.di.IInjector;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */
-public class OsgiFilterContextFunction extends ContextFunction {
-
-       private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext();
-
-       @Override
-       public Object compute(IEclipseContext context, String contextKey) {
-               ServiceReference<?>[] srs;
-               try {
-                       srs = bc.getServiceReferences((String) null, contextKey);
-               } catch (InvalidSyntaxException e) {
-                       throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e);
-               }
-               if (srs == null || srs.length == 0) {
-                       return IInjector.NOT_A_VALUE;
-               } else {
-                       // return the first one
-                       return bc.getService(srs[0]);
-               }
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java
deleted file mode 100644 (file)
index 89055d2..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.argeo.cms.e4;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.jobs.Job;
-
-/**
- * Propagate authentication to an eclipse job. Typically to execute a privileged
- * action outside the UI thread
- */
-public abstract class PrivilegedJob extends Job {
-       private final Subject subject;
-
-       public PrivilegedJob(String jobName) {
-               this(jobName, AccessController.getContext());
-       }
-
-       public PrivilegedJob(String jobName,
-                       AccessControlContext accessControlContext) {
-               super(jobName);
-               subject = Subject.getSubject(accessControlContext);
-
-               // Must be called *before* the job is scheduled,
-               // it is required for the progress window to appear
-               setUser(true);
-       }
-
-       @Override
-       protected IStatus run(final IProgressMonitor progressMonitor) {
-               PrivilegedAction<IStatus> privilegedAction = new PrivilegedAction<IStatus>() {
-                       public IStatus run() {
-                               return doRun(progressMonitor);
-                       }
-               };
-               return Subject.doAs(subject, privilegedAction);
-       }
-
-       /**
-        * Implement here what should be executed with default context
-        * authentication
-        */
-       protected abstract IStatus doRun(IProgressMonitor progressMonitor);
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java
deleted file mode 100644 (file)
index 3d57e16..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-package org.argeo.cms.e4.addons;
-
-import java.security.AccessController;
-import java.util.Iterator;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.MElementContainer;
-import org.eclipse.e4.ui.model.application.ui.MUIElement;
-import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
-import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
-import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
-
-public class AuthAddon {
-       private final static CmsLog log = CmsLog.getLog(AuthAddon.class);
-
-       public final static String AUTH = "auth.";
-
-       @PostConstruct
-       void init(MApplication application) {
-               Iterator<MWindow> windows = application.getChildren().iterator();
-               boolean atLeastOneTopLevelWindowVisible = false;
-               windows: while (windows.hasNext()) {
-                       MWindow window = windows.next();
-                       // main window
-                       boolean windowVisible = process(window);
-                       if (!windowVisible) {
-//                             windows.remove();
-                               continue windows;
-                       }
-                       atLeastOneTopLevelWindowVisible = true;
-                       // trim bars
-                       if (window instanceof MTrimmedWindow) {
-                               Iterator<MTrimBar> trimBars = ((MTrimmedWindow) window).getTrimBars().iterator();
-                               while (trimBars.hasNext()) {
-                                       MTrimBar trimBar = trimBars.next();
-                                       if (!process(trimBar)) {
-                                               trimBars.remove();
-                                       }
-                               }
-                       }
-               }
-
-               if (!atLeastOneTopLevelWindowVisible) {
-                       log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out..");
-                       logout();
-               }
-       }
-
-       protected boolean process(MUIElement element) {
-               for (String tag : element.getTags()) {
-                       if (tag.startsWith(AUTH)) {
-                               String role = tag.substring(AUTH.length(), tag.length());
-                               if (!CurrentUser.isInRole(role)) {
-                                       element.setVisible(false);
-                                       element.setToBeRendered(false);
-                                       return false;
-                               }
-                       }
-               }
-
-               // children
-               if (element instanceof MElementContainer) {
-                       @SuppressWarnings("unchecked")
-                       MElementContainer<? extends MUIElement> container = (MElementContainer<? extends MUIElement>) element;
-                       Iterator<? extends MUIElement> children = container.getChildren().iterator();
-                       while (children.hasNext()) {
-                               MUIElement child = children.next();
-                               boolean visible = process(child);
-                               if (!visible)
-                                       children.remove();
-                       }
-
-                       for (Object child : container.getChildren()) {
-                               if (child instanceof MUIElement) {
-                                       boolean visible = process((MUIElement) child);
-                                       if (!visible)
-                                               container.getChildren().remove(child);
-                               }
-                       }
-               }
-
-               return true;
-       }
-
-       protected void logout() {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               try {
-                       CurrentUser.logoutCmsSession(subject);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot log out", e);
-               }
-               HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest();
-               if (request != null)
-                       request.getSession().setMaxInactiveInterval(0);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java
deleted file mode 100644 (file)
index 5bc0d69..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.cms.e4.addons;
-
-import java.security.AccessController;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.e4.core.services.nls.ILocaleChangeService;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.ElementMatcher;
-import org.eclipse.swt.SWT;
-
-/** Integrate workbench with the locale provided at log in. */
-public class LocaleAddon {
-       private final static String STYLE_OVERRIDE = "styleOverride";
-
-       // Right to left languages
-       private final static String ARABIC = "ar";
-       private final static String HEBREW = "he";
-
-       @PostConstruct
-       public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               Set<Locale> locales = subject.getPublicCredentials(Locale.class);
-               if (!locales.isEmpty()) {
-                       Locale locale = locales.iterator().next();
-                       localeChangeService.changeApplicationLocale(locale);
-                       UiContext.setLocale(locale);
-
-                       if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) {
-                               List<MWindow> windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE,
-                                               new ElementMatcher(null, null, (String) null));
-                               for (MWindow window : windows) {
-                                       String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE);
-                                       int style = 0;
-                                       if (currentStyle != null) {
-                                               style = Integer.parseInt(currentStyle);
-                                       }
-                                       style = style | SWT.RIGHT_TO_LEFT;
-                                       window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style));
-                               }
-                       }
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java
deleted file mode 100644 (file)
index 6367b42..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse 4 addons to integrate with Argeo CMS. */
-package org.argeo.cms.e4.addons;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java
deleted file mode 100644 (file)
index cb9f9b9..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.argeo.cms.e4.files;
-
-import java.net.URI;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.annotation.PostConstruct;
-import javax.inject.Inject;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.eclipse.ui.fs.AdvancedFsBrowser;
-import org.argeo.eclipse.ui.fs.SimpleFsBrowser;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-
-/** Browse the node file system. */
-public class NodeFsBrowserView {
-       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID +
-       // ".nodeFsBrowserView";
-
-       @Inject
-       FileSystemProvider nodeFileSystemProvider;
-
-       @PostConstruct
-       public void createPartControl(Composite parent) {
-               try {
-                       //URI uri = new URI("node://root:demo@localhost:7070/");
-                       URI uri = new URI("node:///");
-                       FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
-                       if (fileSystem == null)
-                               fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
-                       Path nodePath = fileSystem.getPath("/");
-
-                       Path localPath = Paths.get(System.getProperty("user.home"));
-
-                       SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS);
-                       browser.setInput(nodePath, localPath);
-//                     AdvancedFsBrowser browser = new AdvancedFsBrowser();
-//                     browser.createUi(parent, localPath);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot open file system browser", e);
-               }
-       }
-
-       public void setFocus() {
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java
deleted file mode 100644 (file)
index b481dd4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Files browser perspective. */
-package org.argeo.cms.e4.files;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java
deleted file mode 100644 (file)
index 416df7d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import java.util.Locale;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.core.services.nls.ILocaleChangeService;
-
-public class ChangeLanguage {
-       @Execute
-       public void execute(ILocaleChangeService localeChangeService) {
-               localeChangeService.changeApplicationLocale(Locale.FRENCH);
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java
deleted file mode 100644 (file)
index 0ecd0a1..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import static org.argeo.cms.CmsMsg.changePassword;
-import static org.argeo.cms.CmsMsg.currentPassword;
-import static org.argeo.cms.CmsMsg.newPassword;
-import static org.argeo.cms.CmsMsg.passwordChanged;
-import static org.argeo.cms.CmsMsg.repeatNewPassword;
-
-import java.util.Arrays;
-
-import javax.inject.Inject;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.swt.dialogs.CmsMessageDialog;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.core.di.annotations.Optional;
-import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Change the password of the logged-in user. */
-public class ChangePassword {
-       @Inject
-       private UserAdmin userAdmin;
-       @Inject
-       private WorkTransaction userTransaction;
-       @Inject
-       @Optional
-       private CryptoKeyring keyring = null;
-
-       @Execute
-       public void execute() {
-               ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin);
-               if (dialog.open() == Dialog.OK) {
-                       new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(),
-                                       CmsMessageDialog.INFORMATION).open();
-               }
-       }
-
-       protected void changePassword(char[] oldPassword, char[] newPassword) {
-               String name = CurrentUser.getUsername();
-               LdapName dn;
-               try {
-                       dn = new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new CmsException("Invalid user dn " + name, e);
-               }
-               User user = (User) userAdmin.getRole(dn.toString());
-               if (!user.hasCredential(null, oldPassword))
-                       throw new CmsException("Invalid password");
-               if (Arrays.equals(newPassword, new char[0]))
-                       throw new CmsException("New password empty");
-               try {
-                       userTransaction.begin();
-                       user.getCredentials().put(null, newPassword);
-                       if (keyring != null) {
-                               keyring.changePassword(oldPassword, newPassword);
-                               // TODO change secret keys in the CMS session
-                       }
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               e1.printStackTrace();
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new CmsException("Cannot change password", e);
-               }
-       }
-
-       class ChangePasswordDialog extends CmsMessageDialog {
-               private Text oldPassword, newPassword1, newPassword2;
-
-               public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) {
-                       super(parentShell, changePassword.lead(), CONFIRM);
-               }
-
-//             protected Point getInitialSize() {
-//                     return new Point(400, 450);
-//             }
-
-               protected Control createDialogArea(Composite parent) {
-                       Composite dialogarea = (Composite) super.createDialogArea(parent);
-                       dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       Composite composite = new Composite(dialogarea, SWT.NONE);
-                       composite.setLayout(new GridLayout(2, false));
-                       composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       oldPassword = createLP(composite, currentPassword.lead());
-                       newPassword1 = createLP(composite, newPassword.lead());
-                       newPassword2 = createLP(composite, repeatNewPassword.lead());
-
-//                     parent.pack();
-                       oldPassword.setFocus();
-                       return composite;
-               }
-
-               @Override
-               protected void okPressed() {
-                       try {
-                               if (!newPassword1.getText().equals(newPassword2.getText()))
-                                       throw new CmsException("New passwords are different");
-                               changePassword(oldPassword.getTextChars(), newPassword1.getTextChars());
-                               closeShell(OK);
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Cannot change password", e);
-                       }
-               }
-
-               /** Creates label and password. */
-               protected Text createLP(Composite parent, String label) {
-                       new Label(parent, SWT.NONE).setText(label);
-                       Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       return text;
-               }
-
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java
deleted file mode 100644 (file)
index d11c041..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.CanExecute;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class CloseAllParts {
-
-       @Execute
-       void execute(EPartService partService) {
-               for (MPart part : partService.getParts()) {
-                       if (part.isCloseable()) {
-                               if (part.isDirty()) {
-                                       if (partService.savePart(part, true)) {
-                                               partService.hidePart(part, true);
-                                       }
-                               } else {
-                                       partService.hidePart(part, true);
-                               }
-                       }
-               }
-       }
-
-       @CanExecute
-       boolean canExecute(EPartService partService) {
-               boolean atLeastOnePart = false;
-               for (MPart part : partService.getParts()) {
-                       if (part.isVisible() && part.isCloseable()) {
-                               atLeastOnePart = true;
-                               break;
-                       }
-               }
-               return atLeastOnePart;
-       }
-
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java
deleted file mode 100644 (file)
index a365f3d..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import java.security.AccessController;
-
-import javax.security.auth.Subject;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.workbench.IWorkbench;
-
-public class CloseWorkbench {
-       @Execute
-       public void execute(IWorkbench workbench) {
-               logout();
-               workbench.close();
-       }
-
-       protected void logout() {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               try {
-                       CurrentUser.logoutCmsSession(subject);
-               } catch (Exception e) {
-                       throw new CmsException("Cannot log out", e);
-               }
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java
deleted file mode 100644 (file)
index 358494c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-
-public class DoNothing {
-       @Execute
-       public void execute() {
-
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java
deleted file mode 100644 (file)
index ac825bb..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-
-package org.argeo.cms.e4.handlers;
-
-import java.util.Date;
-import java.util.List;
-
-import org.eclipse.e4.ui.di.AboutToHide;
-import org.eclipse.e4.ui.di.AboutToShow;
-import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
-import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-
-public class LanguageMenuContribution {
-       @AboutToShow
-       public void aboutToShow(List<MMenuElement> items, EModelService modelService) {
-               MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
-               dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")");
-               //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4");
-               //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName());
-               dynamicItem.setEnabled(true);
-               dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage");
-               items.add(dynamicItem);
-       }
-
-       @AboutToHide
-       public void aboutToHide() {
-               
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java
deleted file mode 100644 (file)
index ac544b1..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import java.util.List;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.MApplication;
-import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
-import org.eclipse.e4.ui.workbench.modeling.EModelService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class OpenPerspective {
-       @Inject
-       MApplication application;
-       @Inject
-       EPartService partService;
-       @Inject
-       EModelService modelService;
-
-       @Execute
-       public void execute(@Named("perspectiveId") String perspectiveId) {
-               List<MPerspective> perspectives = modelService.findElements(application, perspectiveId, MPerspective.class,
-                               null);
-               if (perspectives.size() == 0)
-                       return;
-               MPerspective perspective = perspectives.get(0);
-               partService.switchPerspective(perspective);
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java
deleted file mode 100644 (file)
index 3b60abd..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.CanExecute;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class SaveAllParts {
-
-       @Execute
-       void execute(EPartService partService) {
-               partService.saveAll(false);
-       }
-
-       @CanExecute
-       boolean canExecute(EPartService partService) {
-               return partService.getDirtyParts().size() > 0;
-       }
-
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java
deleted file mode 100644 (file)
index 73486f3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cms.e4.handlers;
-
-import org.eclipse.e4.core.di.annotations.CanExecute;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-
-public class SavePart {
-       @Execute
-       void execute(EPartService partService, MPart part) {
-               partService.savePart(part, false);
-       }
-
-       @CanExecute
-       boolean canExecute(MPart part) {
-               return part.isDirty();
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java
deleted file mode 100644 (file)
index a44ca90..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic Eclipse 4 handlers. */
-package org.argeo.cms.e4.handlers;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java
deleted file mode 100644 (file)
index e17f17b..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java
deleted file mode 100644 (file)
index 98e8093..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-package org.argeo.cms.e4.jcr;
-
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventListener;
-import javax.jcr.observation.ObservationManager;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.security.Keyring;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.jcr.JcrBrowserUtils;
-import org.argeo.cms.ui.jcr.NodeContentProvider;
-import org.argeo.cms.ui.jcr.NodeLabelProvider;
-import org.argeo.cms.ui.jcr.OsgiRepositoryRegister;
-import org.argeo.cms.ui.jcr.PropertiesContentProvider;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.eclipse.ui.jcr.AsyncUiEventListener;
-import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.core.di.annotations.Optional;
-import org.eclipse.e4.ui.services.EMenuService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.IBaseLabelProvider;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-
-/**
- * Basic View to display a sash form to browse a JCR compliant multiple
- * repository environment
- */
-public class JcrBrowserView {
-       final static String ID = "org.argeo.cms.e4.jcrbrowser";
-       final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer";
-
-       private boolean sortChildNodes = true;
-
-       /* DEPENDENCY INJECTION */
-       @Inject
-       @Optional
-       private Keyring keyring;
-       @Inject
-       private RepositoryFactory repositoryFactory;
-       @Inject
-       private Repository nodeRepository;
-
-       // Current user session on the home repository default workspace
-       private Session userSession;
-
-       private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister();
-
-       // This page widgets
-       private TreeViewer nodesViewer;
-       private NodeContentProvider nodeContentProvider;
-       private TableViewer propertiesViewer;
-       private EventListener resultsObserver;
-
-       @PostConstruct
-       public void createPartControl(Composite parent, IEclipseContext context, EPartService partService,
-                       ESelectionService selectionService, EMenuService menuService) {
-               repositoryRegister.init();
-
-               parent.setLayout(new FillLayout());
-               SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
-               // sashForm.setSashWidth(4);
-               // sashForm.setLayout(new FillLayout());
-
-               // Create the tree on top of the view
-               Composite top = new Composite(sashForm, SWT.NONE);
-               // GridLayout gl = new GridLayout(1, false);
-               top.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               try {
-                       this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE);
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot open user session", e);
-               }
-
-               nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory,
-                               sortChildNodes);
-
-               // nodes viewer
-               nodesViewer = createNodeViewer(top, nodeContentProvider);
-
-               // context menu : it is completely defined in the plugin.xml file.
-               // MenuManager menuManager = new MenuManager();
-               // Menu menu = menuManager.createContextMenu(nodesViewer.getTree());
-
-               // nodesViewer.getTree().setMenu(menu);
-
-               nodesViewer.setInput("");
-
-               // Create the property viewer on the bottom
-               Composite bottom = new Composite(sashForm, SWT.NONE);
-               bottom.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               propertiesViewer = createPropertiesViewer(bottom);
-
-               sashForm.setWeights(getWeights());
-               nodesViewer.setComparer(new NodeViewerComparer());
-               nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) event.getSelection();
-                               selectionService.setSelection(selection.toList());
-                       }
-               });
-               nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService));
-               menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID);
-               // getSite().registerContextMenu(menuManager, nodesViewer);
-               // getSite().setSelectionProvider(nodesViewer);
-       }
-
-       @PreDestroy
-       public void dispose() {
-               JcrUtils.logoutQuietly(userSession);
-               repositoryRegister.destroy();
-       }
-
-       public void refresh(Object obj) {
-               // Enable full refresh from a command when no element of the tree is
-               // selected
-               if (obj == null) {
-                       Object[] elements = nodeContentProvider.getElements(null);
-                       for (Object el : elements) {
-                               if (el instanceof TreeParent)
-                                       JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el);
-                               getNodeViewer().refresh(el);
-                       }
-               } else
-                       getNodeViewer().refresh(obj);
-       }
-
-       /**
-        * To be overridden to adapt size of form and result frames.
-        */
-       protected int[] getWeights() {
-               return new int[] { 70, 30 };
-       }
-
-       protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) {
-
-               final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI);
-
-               tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               tmpNodeViewer.setContentProvider(nodeContentProvider);
-               tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider());
-               tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               if (!event.getSelection().isEmpty()) {
-                                       IStructuredSelection sel = (IStructuredSelection) event.getSelection();
-                                       Object firstItem = sel.getFirstElement();
-                                       if (firstItem instanceof SingleJcrNodeElem)
-                                               propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode());
-                               } else {
-                                       propertiesViewer.setInput("");
-                               }
-                       }
-               });
-
-               resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay());
-               if (keyring != null)
-                       try {
-                               ObservationManager observationManager = userSession.getWorkspace().getObservationManager();
-                               observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/",
-                                               true, null, null, false);
-                       } catch (RepositoryException e) {
-                               throw new EclipseUiException("Cannot register listeners", e);
-                       }
-
-               // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer));
-               return tmpNodeViewer;
-       }
-
-       protected TableViewer createPropertiesViewer(Composite parent) {
-               propertiesViewer = new TableViewer(parent, SWT.NONE);
-               propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               propertiesViewer.getTable().setHeaderVisible(true);
-               propertiesViewer.setContentProvider(new PropertiesContentProvider());
-               TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE);
-               col.getColumn().setText("Name");
-               col.getColumn().setWidth(200);
-               col.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -6684361063107478595L;
-
-                       public String getText(Object element) {
-                               try {
-                                       return ((Property) element).getName();
-                               } catch (RepositoryException e) {
-                                       throw new EclipseUiException("Unexpected exception in label provider", e);
-                               }
-                       }
-               });
-               col = new TableViewerColumn(propertiesViewer, SWT.NONE);
-               col.getColumn().setText("Value");
-               col.getColumn().setWidth(400);
-               col.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -8201994187693336657L;
-
-                       public String getText(Object element) {
-                               try {
-                                       Property property = (Property) element;
-                                       if (property.getType() == PropertyType.BINARY)
-                                               return "<binary>";
-                                       else if (property.isMultiple()) {
-                                               StringBuffer buf = new StringBuffer("[");
-                                               Value[] values = property.getValues();
-                                               for (int i = 0; i < values.length; i++) {
-                                                       if (i != 0)
-                                                               buf.append(", ");
-                                                       buf.append(values[i].getString());
-                                               }
-                                               buf.append(']');
-                                               return buf.toString();
-                                       } else
-                                               return property.getValue().getString();
-                               } catch (RepositoryException e) {
-                                       throw new EclipseUiException("Unexpected exception in label provider", e);
-                               }
-                       }
-               });
-               col = new TableViewerColumn(propertiesViewer, SWT.NONE);
-               col.getColumn().setText("Type");
-               col.getColumn().setWidth(200);
-               col.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -6009599998150286070L;
-
-                       public String getText(Object element) {
-                               return JcrBrowserUtils.getPropertyTypeAsString((Property) element);
-                       }
-               });
-               propertiesViewer.setInput("");
-               return propertiesViewer;
-       }
-
-       protected TreeViewer getNodeViewer() {
-               return nodesViewer;
-       }
-
-       /**
-        * Resets the tree content provider
-        * 
-        * @param sortChildNodes if true the content provider will use a comparer to
-        *                       sort nodes that might slow down the display
-        */
-       public void setSortChildNodes(boolean sortChildNodes) {
-               this.sortChildNodes = sortChildNodes;
-               ((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes);
-               nodesViewer.setInput("");
-       }
-
-       /** Notifies the current view that a node has been added */
-       public void nodeAdded(TreeParent parentNode) {
-               // insure that Ui objects have been correctly created:
-               JcrBrowserUtils.forceRefreshIfNeeded(parentNode);
-               getNodeViewer().refresh(parentNode);
-               getNodeViewer().expandToLevel(parentNode, 1);
-       }
-
-       /** Notifies the current view that a node has been removed */
-       public void nodeRemoved(TreeParent parentNode) {
-               IStructuredSelection newSel = new StructuredSelection(parentNode);
-               getNodeViewer().setSelection(newSel, true);
-               // Force refresh
-               IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection();
-               getNodeViewer().refresh(tmpSel.getFirstElement());
-       }
-
-       class TreeObserver extends AsyncUiEventListener {
-
-               public TreeObserver(Display display) {
-                       super(display);
-               }
-
-               @Override
-               protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
-                       for (Event event : events) {
-                               if (getLog().isTraceEnabled())
-                                       getLog().debug("Received event " + event);
-                               String path = event.getPath();
-                               int index = path.lastIndexOf('/');
-                               String propertyName = path.substring(index + 1);
-                               if (getLog().isTraceEnabled())
-                                       getLog().debug("Concerned property " + propertyName);
-                       }
-                       return false;
-               }
-
-               protected void onEventInUiThread(List<Event> events) throws RepositoryException {
-                       if (getLog().isTraceEnabled())
-                               getLog().trace("Refresh result list");
-                       nodesViewer.refresh();
-               }
-
-       }
-
-       public boolean getSortChildNodes() {
-               return sortChildNodes;
-       }
-
-       public void setFocus() {
-               getNodeViewer().getTree().setFocus();
-       }
-
-       /* DEPENDENCY INJECTION */
-       // public void setRepositoryRegister(RepositoryRegister repositoryRegister) {
-       // this.repositoryRegister = repositoryRegister;
-       // }
-
-       public void setKeyring(CryptoKeyring keyring) {
-               this.keyring = keyring;
-       }
-
-       public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
-               this.repositoryFactory = repositoryFactory;
-       }
-
-       public void setNodeRepository(Repository nodeRepository) {
-               this.nodeRepository = nodeRepository;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java
deleted file mode 100644 (file)
index ad6a547..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.cms.e4.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.jcr.JcrDClickListener;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
-import org.eclipse.jface.viewers.TreeViewer;
-
-public class JcrE4DClickListener extends JcrDClickListener {
-       EPartService partService;
-
-       public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) {
-               super(nodeViewer);
-               this.partService = partService;
-       }
-
-       @Override
-       protected void openNode(Node node) {
-               MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID);
-               try {
-                       part.setLabel(node.getName());
-                       part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName());
-                       part.getPersistedState().put("nodePath", node.getPath());
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot open " + node, e);
-               }
-
-               // the provided part is be shown
-               partService.showPart(part, PartState.ACTIVATE);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java
deleted file mode 100644 (file)
index ae2b325..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java
deleted file mode 100644 (file)
index 17d8d2a..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java
deleted file mode 100644 (file)
index 8f5bc36..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.cms.e4.jcr.handlers;
-
-import java.util.List;
-
-import javax.inject.Named;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.cms.e4.jcr.JcrBrowserView;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.cms.ui.jcr.model.WorkspaceElem;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.eclipse.ui.dialogs.SingleValue;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.services.IServiceConstants;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-
-/**
- * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and
- * {@link WorkspaceElem} TreeObject types.
- * 
- * This handler assumes that a selection provider is available and picks only
- * first selected item. It is UI's job to enable the command only when the
- * selection contains one and only one element. Thus no parameter is passed
- * through the command.
- */
-public class AddFolderNode {
-       @Execute
-       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
-               List<?> selection = (List<?>) selectionService.getSelection();
-               JcrBrowserView view = (JcrBrowserView) part.getObject();
-
-               if (selection != null && selection.size() == 1) {
-                       TreeParent treeParentNode = null;
-                       Node jcrParentNode = null;
-                       Object obj = selection.get(0);
-
-                       if (obj instanceof SingleJcrNodeElem) {
-                               treeParentNode = (TreeParent) obj;
-                               jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode();
-                       } else if (obj instanceof WorkspaceElem) {
-                               treeParentNode = (TreeParent) obj;
-                               jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode();
-                       } else
-                               return;
-
-                       String folderName = SingleValue.ask("Folder name", "Enter folder name");
-                       if (folderName != null) {
-                               try {
-                                       jcrParentNode.addNode(folderName, NodeType.NT_FOLDER);
-                                       jcrParentNode.getSession().save();
-                                       view.nodeAdded(treeParentNode);
-                               } catch (RepositoryException e) {
-                                       ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e);
-                               }
-                       }
-               } else {
-                       // ErrorFeedback.show(WorkbenchUiPlugin
-                       // .getMessage("errorUnvalidNtFolderNodeType"));
-                       ErrorFeedback.show("Invalid NT folder node type");
-               }
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java
deleted file mode 100644 (file)
index dc47f6e..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-package org.argeo.cms.e4.jcr.handlers;
-
-import java.net.URI;
-import java.util.Hashtable;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.e4.jcr.JcrBrowserView;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.Keyring;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.core.di.annotations.Optional;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.services.IServiceConstants;
-import org.eclipse.jface.dialogs.Dialog;
-import org.eclipse.jface.dialogs.IMessageProvider;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.dialogs.TitleAreaDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Connect to a remote repository and, if successful publish it as an OSGi
- * service.
- */
-public class AddRemoteRepository {
-
-       @Inject
-       private RepositoryFactory repositoryFactory;
-       @Inject
-       private Repository nodeRepository;
-       @Inject
-       @Optional
-       private Keyring keyring;
-
-       @Execute
-       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part) {
-               JcrBrowserView view = (JcrBrowserView) part.getObject();
-               RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(Display.getDefault().getActiveShell());
-               if (dlg.open() == Dialog.OK) {
-                       view.refresh(null);
-               }
-       }
-
-       // public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
-       // this.repositoryFactory = repositoryFactory;
-       // }
-       //
-       // public void setKeyring(Keyring keyring) {
-       // this.keyring = keyring;
-       // }
-       //
-       // public void setNodeRepository(Repository nodeRepository) {
-       // this.nodeRepository = nodeRepository;
-       // }
-
-       class RemoteRepositoryLoginDialog extends TitleAreaDialog {
-               private static final long serialVersionUID = 2234006887750103399L;
-               private Text name;
-               private Text uri;
-               private Text username;
-               private Text password;
-               private Button saveInKeyring;
-
-               public RemoteRepositoryLoginDialog(Shell parentShell) {
-                       super(parentShell);
-               }
-
-               protected Point getInitialSize() {
-                       return new Point(600, 400);
-               }
-
-               protected Control createDialogArea(Composite parent) {
-                       Composite dialogarea = (Composite) super.createDialogArea(parent);
-                       dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       Composite composite = new Composite(dialogarea, SWT.NONE);
-                       composite.setLayout(new GridLayout(2, false));
-                       composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       setMessage("Login to remote repository", IMessageProvider.NONE);
-                       name = createLT(composite, "Name", "remoteRepository");
-                       uri = createLT(composite, "URI", "http://localhost:7070/jcr/node");
-                       username = createLT(composite, "User", "");
-                       password = createLP(composite, "Password");
-
-                       saveInKeyring = createLC(composite, "Remember password", false);
-                       parent.pack();
-                       return composite;
-               }
-
-               @Override
-               protected void createButtonsForButtonBar(Composite parent) {
-                       super.createButtonsForButtonBar(parent);
-                       Button test = createButton(parent, 2, "Test", false);
-                       test.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = -1829962269440419560L;
-
-                               public void widgetSelected(SelectionEvent arg0) {
-                                       testConnection();
-                               }
-                       });
-               }
-
-               void testConnection() {
-                       Session session = null;
-                       try {
-                               URI checkedUri = new URI(uri.getText());
-                               String checkedUriStr = checkedUri.toString();
-
-                               Hashtable<String, String> params = new Hashtable<String, String>();
-                               params.put(CmsConstants.LABELED_URI, checkedUriStr);
-                               Repository repository = repositoryFactory.getRepository(params);
-                               if (username.getText().trim().equals("")) {// anonymous
-                                       // FIXME make it more generic
-                                       session = repository.login(CmsConstants.SYS_WORKSPACE);
-                               } else {
-                                       // FIXME use getTextChars() when upgrading to 3.7
-                                       // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=297412
-                                       char[] pwd = password.getText().toCharArray();
-                                       SimpleCredentials sc = new SimpleCredentials(username.getText(), pwd);
-                                       session = repository.login(sc, "main");
-                                       MessageDialog.openInformation(getParentShell(), "Success",
-                                                       "Connection to '" + uri.getText() + "' successful");
-                               }
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Connection test failed for " + uri.getText(), e);
-                       } finally {
-                               JcrUtils.logoutQuietly(session);
-                       }
-               }
-
-               @Override
-               protected void okPressed() {
-                       Session nodeSession = null;
-                       try {
-                               nodeSession = nodeRepository.login();
-                               Node home = CmsJcrUtils.getUserHome(nodeSession);
-
-                               Node remote = home.hasNode(ArgeoNames.ARGEO_REMOTE) ? home.getNode(ArgeoNames.ARGEO_REMOTE)
-                                               : home.addNode(ArgeoNames.ARGEO_REMOTE);
-                               if (remote.hasNode(name.getText()))
-                                       throw new EclipseUiException("There is already a remote repository named " + name.getText());
-                               Node remoteRepository = remote.addNode(name.getText(), ArgeoTypes.ARGEO_REMOTE_REPOSITORY);
-                               remoteRepository.setProperty(ArgeoNames.ARGEO_URI, uri.getText());
-                               remoteRepository.setProperty(ArgeoNames.ARGEO_USER_ID, username.getText());
-                               nodeSession.save();
-                               if (saveInKeyring.getSelection()) {
-                                       String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD;
-                                       keyring.set(pwdPath, password.getText().toCharArray());
-                               }
-                               nodeSession.save();
-                               MessageDialog.openInformation(getParentShell(), "Repository Added",
-                                               "Remote repository '" + username.getText() + "@" + uri.getText() + "' added");
-
-                               super.okPressed();
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Cannot add remote repository", e);
-                       } finally {
-                               JcrUtils.logoutQuietly(nodeSession);
-                       }
-               }
-
-               /** Creates label and text. */
-               protected Text createLT(Composite parent, String label, String initial) {
-                       new Label(parent, SWT.NONE).setText(label);
-                       Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       text.setText(initial);
-                       return text;
-               }
-
-               /** Creates label and check. */
-               protected Button createLC(Composite parent, String label, Boolean initial) {
-                       new Label(parent, SWT.NONE).setText(label);
-                       Button check = new Button(parent, SWT.CHECK);
-                       check.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       check.setSelection(initial);
-                       return check;
-               }
-
-               protected Text createLP(Composite parent, String label) {
-                       new Label(parent, SWT.NONE).setText(label);
-                       Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.PASSWORD);
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       return text;
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java
deleted file mode 100644 (file)
index 1974e4d..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.argeo.cms.e4.jcr.handlers;
-
-import java.util.List;
-
-import javax.inject.Named;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.e4.jcr.JcrBrowserView;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.cms.ui.jcr.model.WorkspaceElem;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.services.IServiceConstants;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.swt.widgets.Display;
-
-/**
- * Delete the selected nodes: both in the JCR repository and in the UI view.
- * Warning no check is done, except implementation dependent native checks,
- * handle with care.
- * 
- * This handler is still 'hard linked' to a GenericJcrBrowser view to enable
- * correct tree refresh when a node is added. This must be corrected in future
- * versions.
- */
-public class DeleteNodes {
-       @Execute
-       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
-               List<?> selection = (List<?>) selectionService.getSelection();
-               if (selection == null)
-                       return;
-
-               JcrBrowserView view = (JcrBrowserView) part.getObject();
-
-               // confirmation
-               StringBuffer buf = new StringBuffer("");
-               for (Object o : selection) {
-                       SingleJcrNodeElem sjn = (SingleJcrNodeElem) o;
-                       buf.append(sjn.getName()).append(' ');
-               }
-               Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion",
-                               "Do you want to delete " + buf + "?");
-
-               // operation
-               if (doRemove) {
-                       SingleJcrNodeElem ancestor = null;
-                       WorkspaceElem rootAncestor = null;
-                       try {
-                               for (Object obj : selection) {
-                                       if (obj instanceof SingleJcrNodeElem) {
-                                               // Cache objects
-                                               SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj;
-                                               TreeParent tp = (TreeParent) sjn.getParent();
-                                               Node node = sjn.getNode();
-
-                                               // Jcr Remove
-                                               node.remove();
-                                               node.getSession().save();
-                                               // UI remove
-                                               tp.removeChild(sjn);
-
-                                               // Check if the parent is the root node
-                                               if (tp instanceof WorkspaceElem)
-                                                       rootAncestor = (WorkspaceElem) tp;
-                                               else
-                                                       ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp);
-                                       }
-                               }
-                               if (rootAncestor != null)
-                                       view.nodeRemoved(rootAncestor);
-                               else if (ancestor != null)
-                                       view.nodeRemoved(ancestor);
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Cannot delete selected node ", e);
-                       }
-               }
-       }
-
-       private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) {
-               try {
-                       if (A == null)
-                               return B == null ? null : B;
-                       // Todo enhanced this method
-                       else
-                               return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Cannot find ancestor", re);
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java
deleted file mode 100644 (file)
index 4ae072c..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.argeo.cms.e4.jcr.handlers;
-
-import java.util.List;
-
-import javax.inject.Named;
-
-import org.argeo.cms.e4.jcr.JcrBrowserView;
-import org.argeo.cms.ui.jcr.JcrBrowserUtils;
-import org.argeo.eclipse.ui.TreeParent;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.e4.ui.services.IServiceConstants;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-
-/**
- * Force the selected objects of the active view to be refreshed doing the
- * following:
- * <ol>
- * <li>The model objects are recomputed</li>
- * <li>the view is refreshed</li>
- * </ol>
- */
-public class Refresh {
-
-       @Execute
-       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService,
-                       ESelectionService selectionService) {
-
-               JcrBrowserView view = (JcrBrowserView) part.getObject();
-               List<?> selection = (List<?>) selectionService.getSelection();
-
-               if (selection != null && !selection.isEmpty()) {
-                       for (Object obj : selection)
-                               if (obj instanceof TreeParent) {
-                                       TreeParent tp = (TreeParent) obj;
-                                       JcrBrowserUtils.forceRefreshIfNeeded(tp);
-                                       view.refresh(obj);
-                               }
-               } else if (view instanceof JcrBrowserView)
-                       view.refresh(null); // force full refresh
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java
deleted file mode 100644 (file)
index 97674ab..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java
deleted file mode 100644 (file)
index 4e075e2..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** JCR browser handlers. */
-package org.argeo.cms.e4.jcr.handlers;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java
deleted file mode 100644 (file)
index 3e92fb0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** JCR browser perspective. */
-package org.argeo.cms.e4.jcr;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java
deleted file mode 100644 (file)
index 4fd1d68..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java
deleted file mode 100644 (file)
index 260a114..0000000
+++ /dev/null
@@ -1,576 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import static org.eclipse.swt.SWT.RIGHT;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.LinkedHashMap;
-
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsLink;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ILazyContentProvider;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.Text;
-
-public class Browse implements CmsUiProvider {
-
-       // Some local constants to experiment. should be cleaned
-       private final static String BROWSE_PREFIX = "browse#";
-       private final static int THUMBNAIL_WIDTH = 400;
-       private final static int COLUMN_WIDTH = 160;
-       private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm");
-
-       // keep a cache of the opened nodes
-       // Key is the path
-       private LinkedHashMap<String, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<String, Browse.FilterEntitiesVirtualTable>();
-       private Composite nodeDisplayParent;
-       private Composite colViewer;
-       private ScrolledComposite scrolledCmp;
-       private Text parentPathTxt;
-       private Text filterTxt;
-       private Node currEdited;
-
-       private String initialPath;
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               if (context == null)
-                       // return null;
-                       throw new CmsException("Context cannot be null");
-               GridLayout layout = CmsSwtUtils.noSpaceGridLayout();
-               layout.numColumns = 2;
-               parent.setLayout(layout);
-
-               // Left
-               Composite leftCmp = new Composite(parent, SWT.NO_FOCUS);
-               leftCmp.setLayoutData(CmsSwtUtils.fillAll());
-               createBrowserPart(leftCmp, context);
-
-               // Right
-               nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER);
-               GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true);
-               gd.widthHint = THUMBNAIL_WIDTH;
-               nodeDisplayParent.setLayoutData(gd);
-               createNodeView(nodeDisplayParent, context);
-
-               // INIT
-               setEdited(context);
-               initialPath = context.getPath();
-
-               // Workaround we don't yet manage the delete to display parent of the
-               // initial context node
-
-               return null;
-       }
-
-       private void createBrowserPart(Composite parent, Node context) throws RepositoryException {
-               GridLayout layout = CmsSwtUtils.noSpaceGridLayout();
-               parent.setLayout(layout);
-               Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
-               filterCmp.setLayoutData(CmsSwtUtils.fillWidth());
-
-               // top filter
-               addFilterPanel(filterCmp);
-
-               // scrolled composite
-               scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
-               scrolledCmp.setLayoutData(CmsSwtUtils.fillAll());
-               scrolledCmp.setExpandVertical(true);
-               scrolledCmp.setExpandHorizontal(true);
-               scrolledCmp.setShowFocusedControl(true);
-
-               colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS);
-               scrolledCmp.setContent(colViewer);
-               scrolledCmp.addControlListener(new ControlAdapter() {
-                       private static final long serialVersionUID = 6589392045145698201L;
-
-                       @Override
-                       public void controlResized(ControlEvent e) {
-                               Rectangle r = scrolledCmp.getClientArea();
-                               scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, r.height));
-                       }
-               });
-               initExplorer(colViewer, context);
-       }
-
-       private Control initExplorer(Composite parent, Node context) throws RepositoryException {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               createBrowserColumn(parent, context);
-               return null;
-       }
-
-       private Control createBrowserColumn(Composite parent, Node context) throws RepositoryException {
-               // TODO style is not correctly managed.
-               FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
-               // CmsUiUtils.style(table, ArgeoOrgStyle.browserColumn.style());
-               table.filterList("*");
-               table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
-               browserCols.put(context.getPath(), table);
-               return null;
-       }
-
-       public void addFilterPanel(Composite parent) {
-
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
-
-               // Text Area for the filter
-               parentPathTxt = new Text(parent, SWT.NO_FOCUS);
-               parentPathTxt.setEditable(false);
-               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setMessage("Filter current list");
-               filterTxt.setLayoutData(CmsSwtUtils.fillWidth());
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 7709303319740056286L;
-
-                       public void modifyText(ModifyEvent event) {
-                               modifyFilter(false);
-                       }
-               });
-
-               filterTxt.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = -4523394262771183968L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
-                               // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
-                               FilterEntitiesVirtualTable currTable = null;
-                               if (currEdited != null) {
-                                       FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited));
-                                       if (table != null && !table.isDisposed())
-                                               currTable = table;
-                               }
-
-                               try {
-                                       if (e.keyCode == SWT.ARROW_DOWN)
-                                               currTable.setFocus();
-                                       else if (e.keyCode == SWT.BS) {
-                                               if (filterTxt.getText().equals("")
-                                                               && !(getPath(currEdited).equals("/") || getPath(currEdited).equals(initialPath))) {
-                                                       setEdited(currEdited.getParent());
-                                                       e.doit = false;
-                                                       filterTxt.setFocus();
-                                               }
-                                       } else if (e.keyCode == SWT.TAB && !shiftPressed) {
-                                               if (currEdited.getNodes(filterTxt.getText() + "*").getSize() == 1) {
-                                                       setEdited(currEdited.getNodes(filterTxt.getText() + "*").nextNode());
-                                               }
-                                               filterTxt.setFocus();
-                                               e.doit = false;
-                                       }
-                               } catch (RepositoryException e1) {
-                                       throw new CmsException("Unexpected error in key management for " + currEdited + "with filter "
-                                                       + filterTxt.getText(), e1);
-                               }
-
-                       }
-               });
-       }
-
-       private void setEdited(Node node) {
-               try {
-                       currEdited = node;
-                       CmsSwtUtils.clear(nodeDisplayParent);
-                       createNodeView(nodeDisplayParent, currEdited);
-                       nodeDisplayParent.layout();
-                       refreshFilters(node);
-                       refreshBrowser(node);
-               } catch (RepositoryException re) {
-                       throw new CmsException("Unable to update browser for " + node, re);
-               }
-       }
-
-       private void refreshFilters(Node node) throws RepositoryException {
-               String currNodePath = node.getPath();
-               parentPathTxt.setText(currNodePath);
-               filterTxt.setText("");
-               filterTxt.getParent().layout();
-       }
-
-       private void refreshBrowser(Node node) throws RepositoryException {
-
-               // Retrieve
-               String currNodePath = node.getPath();
-               String currParPath = "";
-               if (!"/".equals(currNodePath))
-                       currParPath = JcrUtils.parentPath(currNodePath);
-               if ("".equals(currParPath))
-                       currParPath = "/";
-
-               Object[][] colMatrix = new Object[browserCols.size()][2];
-
-               int i = 0, j = -1, k = -1;
-               for (String path : browserCols.keySet()) {
-                       colMatrix[i][0] = path;
-                       colMatrix[i][1] = browserCols.get(path);
-                       if (j >= 0 && k < 0 && !currNodePath.equals("/")) {
-                               boolean leaveOpened = path.startsWith(currNodePath);
-
-                               // workaround for same name siblings
-                               // fix me weird side effect when we go left or click on anb
-                               // already selected, unfocused node
-                               if (leaveOpened && (path.lastIndexOf("/") == 0 && currNodePath.lastIndexOf("/") == 0
-                                               || JcrUtils.parentPath(path).equals(JcrUtils.parentPath(currNodePath))))
-                                       leaveOpened = JcrUtils.lastPathElement(path).equals(JcrUtils.lastPathElement(currNodePath));
-
-                               if (!leaveOpened)
-                                       k = i;
-                       }
-                       if (currParPath.equals(path))
-                               j = i;
-                       i++;
-               }
-
-               if (j >= 0 && k >= 0)
-                       // remove useless cols
-                       for (int l = i - 1; l >= k; l--) {
-                               browserCols.remove(colMatrix[l][0]);
-                               ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
-                       }
-
-               // Remove disposed columns
-               // TODO investigate and fix the mechanism that leave them there after
-               // disposal
-               if (browserCols.containsKey(currNodePath)) {
-                       FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath);
-                       if (currCol.isDisposed())
-                               browserCols.remove(currNodePath);
-               }
-
-               if (!browserCols.containsKey(currNodePath))
-                       createBrowserColumn(colViewer, node);
-
-               colViewer.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
-               // colViewer.pack();
-               colViewer.layout();
-               // also resize the scrolled composite
-               scrolledCmp.layout();
-               scrolledCmp.getShowFocusedControl();
-               // colViewer.getParent().layout();
-               // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) {
-               // } else {
-               // }
-       }
-
-       private void modifyFilter(boolean fromOutside) {
-               if (!fromOutside)
-                       if (currEdited != null) {
-                               String filter = filterTxt.getText() + "*";
-                               FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited));
-                               if (table != null && !table.isDisposed())
-                                       table.filterList(filter);
-                       }
-
-       }
-
-       private String getPath(Node node) {
-               try {
-                       return node.getPath();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Unable to get path for node " + node, e);
-               }
-       }
-
-       private Cms2DSize imageWidth = new Cms2DSize(250, 0);
-
-       /**
-        * Recreates the content of the box that displays information about the current
-        * selected node.
-        */
-       private Control createNodeView(Composite parent, Node context) throws RepositoryException {
-
-               parent.setLayout(new GridLayout(2, false));
-
-               if (isImg(context)) {
-                       EditableImage image = new Img(parent, RIGHT, context, imageWidth);
-                       image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 2, 1));
-               }
-
-               // Name and primary type
-               Label contextL = new Label(parent, SWT.NONE);
-               CmsSwtUtils.markup(contextL);
-               contextL.setText("<b>" + context.getName() + "</b>");
-               new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType().getName());
-
-               // Children
-               for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
-                       Node child = nIt.nextNode();
-                       new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()).createUi(parent, context);
-                       new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType().getName());
-               }
-
-               // Properties
-               for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) {
-                       Property property = pIt.nextProperty();
-                       Label label = new Label(parent, SWT.NONE);
-                       label.setText(property.getName());
-                       label.setToolTipText(JcrUtils.getPropertyDefinitionAsString(property));
-                       new Label(parent, SWT.NONE).setText(getPropAsString(property));
-               }
-
-               return null;
-       }
-
-       private boolean isImg(Node node) throws RepositoryException {
-               // TODO support images
-               return false;
-//             return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE);
-       }
-
-       private String getPropAsString(Property property) throws RepositoryException {
-               String result = "";
-               if (property.isMultiple()) {
-                       result = getMultiAsString(property, ", ");
-               } else {
-                       Value value = property.getValue();
-                       if (value.getType() == PropertyType.BINARY)
-                               result = "<binary>";
-                       else if (value.getType() == PropertyType.DATE)
-                               result = timeFormatter.format(value.getDate().getTime());
-                       else
-                               result = value.getString();
-               }
-               return result;
-       }
-
-       private String getMultiAsString(Property property, String separator) throws RepositoryException {
-               if (separator == null)
-                       separator = "; ";
-               Value[] values = property.getValues();
-               StringBuilder builder = new StringBuilder();
-               for (Value val : values) {
-                       String currStr = val.getString();
-                       if (!"".equals(currStr.trim()))
-                               builder.append(currStr).append(separator);
-               }
-               if (builder.lastIndexOf(separator) >= 0)
-                       return builder.substring(0, builder.length() - separator.length());
-               else
-                       return builder.toString();
-       }
-
-       /** Almost canonical implementation of a table that display entities */
-       private class FilterEntitiesVirtualTable extends Composite {
-               private static final long serialVersionUID = 8798147431706283824L;
-
-               // Context
-               private Node context;
-
-               // UI Objects
-               private TableViewer entityViewer;
-
-               // enable management of multiple columns
-               Node getNode() {
-                       return context;
-               }
-
-               @Override
-               public boolean setFocus() {
-                       if (entityViewer.getTable().isDisposed())
-                               return false;
-                       if (entityViewer.getSelection().isEmpty()) {
-                               Object first = entityViewer.getElementAt(0);
-                               if (first != null) {
-                                       entityViewer.setSelection(new StructuredSelection(first), true);
-                               }
-                       }
-                       return entityViewer.getTable().setFocus();
-               }
-
-               void filterList(String filter) {
-                       try {
-                               NodeIterator nit = context.getNodes(filter);
-                               refreshFilteredList(nit);
-                       } catch (RepositoryException e) {
-                               throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e);
-                       }
-
-               }
-
-               public FilterEntitiesVirtualTable(Composite parent, int style, Node context) {
-                       super(parent, SWT.NO_FOCUS);
-                       this.context = context;
-                       populate();
-               }
-
-               protected void populate() {
-                       Composite parent = this;
-                       GridLayout layout = CmsSwtUtils.noSpaceGridLayout();
-
-                       this.setLayout(layout);
-                       createTableViewer(parent);
-               }
-
-               private void createTableViewer(final Composite parent) {
-                       // the list
-                       // We must limit the size of the table otherwise the full list is
-                       // loaded
-                       // before the layout happens
-                       Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
-                       GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
-                       gd.widthHint = COLUMN_WIDTH;
-                       listCmp.setLayoutData(gd);
-                       listCmp.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-                       entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE);
-                       Table table = entityViewer.getTable();
-
-                       table.setLayoutData(CmsSwtUtils.fillAll());
-                       table.setLinesVisible(true);
-                       table.setHeaderVisible(false);
-                       CmsSwtUtils.markup(table);
-
-                       CmsSwtUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
-
-                       // first column
-                       TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE);
-                       TableColumn tcol = column.getColumn();
-                       tcol.setWidth(COLUMN_WIDTH);
-                       tcol.setResizable(true);
-                       column.setLabelProvider(new SimpleNameLP());
-
-                       entityViewer.setContentProvider(new MyLazyCP(entityViewer));
-                       entityViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                               @Override
-                               public void selectionChanged(SelectionChangedEvent event) {
-                                       IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection();
-                                       if (selection.isEmpty())
-                                               return;
-                                       else
-                                               setEdited((Node) selection.getFirstElement());
-
-                               }
-                       });
-
-                       table.addKeyListener(new KeyListener() {
-                               private static final long serialVersionUID = -330694313896036230L;
-
-                               @Override
-                               public void keyReleased(KeyEvent e) {
-                               }
-
-                               @Override
-                               public void keyPressed(KeyEvent e) {
-
-                                       IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection();
-                                       Node selected = null;
-                                       if (!selection.isEmpty())
-                                               selected = ((Node) selection.getFirstElement());
-                                       try {
-                                               if (e.keyCode == SWT.ARROW_RIGHT) {
-                                                       if (selected != null) {
-                                                               setEdited(selected);
-                                                               browserCols.get(selected.getPath()).setFocus();
-                                                       }
-                                               } else if (e.keyCode == SWT.ARROW_LEFT) {
-                                                       try {
-                                                               selected = getNode().getParent();
-                                                               String newPath = selected.getPath(); // getNode().getParent()
-                                                               setEdited(selected);
-                                                               if (browserCols.containsKey(newPath))
-                                                                       browserCols.get(newPath).setFocus();
-                                                       } catch (ItemNotFoundException ie) {
-                                                               // root silent
-                                                       }
-                                               }
-                                       } catch (RepositoryException ie) {
-                                               throw new CmsException("Error while managing arrow " + "events in the browser for " + selected,
-                                                               ie);
-                                       }
-                               }
-                       });
-               }
-
-               private class MyLazyCP implements ILazyContentProvider {
-                       private static final long serialVersionUID = 1L;
-                       private TableViewer viewer;
-                       private Object[] elements;
-
-                       public MyLazyCP(TableViewer viewer) {
-                               this.viewer = viewer;
-                       }
-
-                       public void dispose() {
-                       }
-
-                       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-                               // IMPORTANT: don't forget this: an exception will be thrown if
-                               // a selected object is not part of the results anymore.
-                               viewer.setSelection(null);
-                               this.elements = (Object[]) newInput;
-                       }
-
-                       public void updateElement(int index) {
-                               viewer.replace(elements[index], index);
-                       }
-               }
-
-               protected void refreshFilteredList(NodeIterator children) {
-                       Object[] rows = JcrUtils.nodeIteratorToList(children).toArray();
-                       entityViewer.setInput(rows);
-                       entityViewer.setItemCount(rows.length);
-                       entityViewer.refresh();
-               }
-
-               public class SimpleNameLP extends ColumnLabelProvider {
-                       private static final long serialVersionUID = 2465059387875338553L;
-
-                       @Override
-                       public String getText(Object element) {
-                               if (element instanceof Node) {
-                                       Node curr = ((Node) element);
-                                       try {
-                                               return curr.getName();
-                                       } catch (RepositoryException e) {
-                                               throw new CmsException("Unable to get name for" + curr);
-                                       }
-                               }
-                               return super.getText(element);
-                       }
-               }
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java
deleted file mode 100644 (file)
index 97f3e67..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java
deleted file mode 100644 (file)
index ef95bde..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java
deleted file mode 100644 (file)
index e713f53..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.api.cms.CmsState;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Group;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-
-class DeploymentEntryPoint {
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       protected void createContents(Composite parent) {
-               // FIXME manage authentication if needed
-               // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN))
-               // return;
-
-               // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               if (isDesktop()) {
-                       parent.setLayout(new GridLayout(2, true));
-               } else {
-                       // TODO add scrolling
-                       parent.setLayout(new GridLayout(1, true));
-               }
-
-               initHighLevelSummary(parent);
-
-               Group securityGroup = createHighLevelGroup(parent, "Security");
-               securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-               new SecurityDeploymentUi(securityGroup, SWT.NONE);
-
-               Group dataGroup = createHighLevelGroup(parent, "Data");
-               dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-               new DataDeploymentUi(dataGroup, SWT.NONE);
-
-               Group logGroup = createHighLevelGroup(parent, "Notifications");
-               logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
-               new LogDeploymentUi(logGroup, SWT.NONE);
-
-               Group connectivityGroup = createHighLevelGroup(parent, "Connectivity");
-               new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE);
-               connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
-
-       }
-
-       private void initHighLevelSummary(Composite parent) {
-               Composite composite = new Composite(parent, SWT.NONE);
-               GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
-               if (isDesktop())
-                       gridData.horizontalSpan = 3;
-               composite.setLayoutData(gridData);
-               composite.setLayout(new FillLayout());
-
-               ServiceReference<CmsState> nodeStateRef = bc.getServiceReference(CmsState.class);
-               if (nodeStateRef == null)
-                       throw new IllegalStateException("No CMS state available");
-               CmsState nodeState = bc.getService(nodeStateRef);
-               ServiceReference<CmsContext> nodeDeploymentRef = bc.getServiceReference(CmsContext.class);
-               Label label = new Label(composite, SWT.WRAP);
-               CmsSwtUtils.markup(label);
-               if (nodeDeploymentRef == null) {
-                       label.setText("Not yet deployed on <br>" + nodeState.getHostname() + "</br>, please configure below.");
-               } else {
-                       Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN);
-                       CmsContext nodeDeployment = bc.getService(nodeDeploymentRef);
-                       GregorianCalendar calendar = new GregorianCalendar();
-                       calendar.setTimeInMillis(nodeDeployment.getAvailableSince());
-                       calendar.setTimeZone(TimeZone.getDefault());
-                       label.setText("[" + "<b>" + nodeState.getHostname() + "</b>]# " + "Deployment state " + stateUuid
-                                       + ", available since <b>" + calendar.getTime() + "</b>");
-               }
-       }
-
-       private static Group createHighLevelGroup(Composite parent, String text) {
-               Group group = new Group(parent, SWT.NONE);
-               group.setText(text);
-               CmsSwtUtils.markup(group);
-               return group;
-       }
-
-       private boolean isDesktop() {
-               return true;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java
deleted file mode 100644 (file)
index fa5d3da..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java
deleted file mode 100644 (file)
index df1be51..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java
deleted file mode 100644 (file)
index cb38ce8..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java
deleted file mode 100644 (file)
index 3492c54..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java
deleted file mode 100644 (file)
index e4d2ad4..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Maintenance perspective. */
-package org.argeo.cms.e4.maintenance;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java
deleted file mode 100644 (file)
index 962ad38..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cms.e4.monitoring;
-
-import org.argeo.eclipse.ui.TreeParent;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.ServiceReference;
-
-/** A tree element representing a {@link Bundle} */
-class BundleNode extends TreeParent {
-       private final Bundle bundle;
-
-       public BundleNode(Bundle bundle) {
-               this(bundle, false);
-       }
-
-       @SuppressWarnings("rawtypes")
-       public BundleNode(Bundle bundle, boolean hasChildren) {
-               super(bundle.getSymbolicName());
-               this.bundle = bundle;
-
-               if (hasChildren) {
-                       // REFERENCES
-                       ServiceReference[] usedServices = bundle.getServicesInUse();
-                       if (usedServices != null) {
-                               for (ServiceReference sr : usedServices) {
-                                       if (sr != null)
-                                               addChild(new ServiceReferenceNode(sr, false));
-                               }
-                       }
-
-                       // SERVICES
-                       ServiceReference[] registeredServices = bundle
-                                       .getRegisteredServices();
-                       if (registeredServices != null) {
-                               for (ServiceReference sr : registeredServices) {
-                                       if (sr != null)
-                                               addChild(new ServiceReferenceNode(sr, true));
-                               }
-                       }
-               }
-
-       }
-
-       Bundle getBundle() {
-               return bundle;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java
deleted file mode 100644 (file)
index c639255..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-//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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java
deleted file mode 100644 (file)
index 8a36050..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-//package org.argeo.eclipse.ui.workbench.osgi;
-//public class BundlesView {}
-
-package org.argeo.cms.e4.monitoring;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.eclipse.ui.ColumnViewerComparator;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-import org.argeo.util.LangUtils;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/**
- * Overview of the active CMS sessions.
- */
-public class CmsSessionsView {
-       private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext();
-
-       private TableViewer viewer;
-
-       @PostConstruct
-       public void createPartControl(Composite parent) {
-               viewer = new TableViewer(parent);
-               viewer.setContentProvider(new CmsSessionContentProvider());
-               viewer.getTable().setHeaderVisible(true);
-
-               EclipseUiSpecificUtils.enableToolTipSupport(viewer);
-
-               int longColWidth = 150;
-               int smallColWidth = 100;
-
-               // Display name
-               TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
-               column.getColumn().setWidth(longColWidth);
-               column.getColumn().setText("User");
-               column.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -5234573509093747505L;
-
-                       public String getText(Object element) {
-                               return ((CmsSession) element).getDisplayName();
-                       }
-
-                       public String getToolTipText(Object element) {
-                               return ((CmsSession) element).getUserDn().toString();
-                       }
-               });
-               new ColumnViewerComparator(column);
-
-               // Creation time
-               column = new TableViewerColumn(viewer, SWT.NONE);
-               column.getColumn().setWidth(smallColWidth);
-               column.getColumn().setText("Since");
-               column.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -5234573509093747505L;
-
-                       public String getText(Object element) {
-                               return LangUtils.since(((CmsSession) element).getCreationTime());
-                       }
-
-                       public String getToolTipText(Object element) {
-                               return ((CmsSession) element).getCreationTime().toString();
-                       }
-               });
-               new ColumnViewerComparator(column);
-
-               // Username
-               column = new TableViewerColumn(viewer, SWT.NONE);
-               column.getColumn().setWidth(smallColWidth);
-               column.getColumn().setText("Username");
-               column.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -5234573509093747505L;
-
-                       public String getText(Object element) {
-                               LdapName userDn = ((CmsSession) element).getUserDn();
-                               return userDn.getRdn(userDn.size() - 1).getValue().toString();
-                       }
-
-                       public String getToolTipText(Object element) {
-                               return ((CmsSession) element).getUserDn().toString();
-                       }
-               });
-               new ColumnViewerComparator(column);
-
-               // UUID
-               column = new TableViewerColumn(viewer, SWT.NONE);
-               column.getColumn().setWidth(smallColWidth);
-               column.getColumn().setText("UUID");
-               column.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -5234573509093747505L;
-
-                       public String getText(Object element) {
-                               return ((CmsSession) element).getUuid().toString();
-                       }
-
-                       public String getToolTipText(Object element) {
-                               return getText(element);
-                       }
-               });
-               new ColumnViewerComparator(column);
-
-               // Local ID
-               column = new TableViewerColumn(viewer, SWT.NONE);
-               column.getColumn().setWidth(smallColWidth);
-               column.getColumn().setText("Local ID");
-               column.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = -5234573509093747505L;
-
-                       public String getText(Object element) {
-                               return ((CmsSession) element).getLocalId();
-                       }
-
-                       public String getToolTipText(Object element) {
-                               return getText(element);
-                       }
-               });
-               new ColumnViewerComparator(column);
-
-               viewer.setInput(bc);
-
-       }
-
-       @Focus
-       public void setFocus() {
-               if (viewer != null)
-                       viewer.getControl().setFocus();
-       }
-
-       /** Content provider managing the array of bundles */
-       private static class CmsSessionContentProvider implements IStructuredContentProvider {
-               private static final long serialVersionUID = -8533792785725875977L;
-
-               public Object[] getElements(Object inputElement) {
-                       if (inputElement instanceof BundleContext) {
-                               BundleContext bc = (BundleContext) inputElement;
-                               Collection<ServiceReference<CmsSession>> srs;
-                               try {
-                                       srs = bc.getServiceReferences(CmsSession.class, null);
-                               } catch (InvalidSyntaxException e) {
-                                       throw new IllegalArgumentException("Cannot retrieve CMS sessions", e);
-                               }
-                               List<CmsSession> res = new ArrayList<>();
-                               for (ServiceReference<CmsSession> sr : srs) {
-                                       res.add(bc.getService(sr));
-                               }
-                               return res.toArray();
-                       }
-                       return null;
-               }
-
-               public void dispose() {
-               }
-
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java
deleted file mode 100644 (file)
index f0d8c29..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.argeo.cms.e4.monitoring;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-
-import org.argeo.eclipse.ui.TreeParent;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** The OSGi runtime from a module perspective. */
-public class ModulesView {
-       private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext();
-       private TreeViewer viewer;
-
-       @PostConstruct
-       public void createPartControl(Composite parent) {
-               viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
-               viewer.setContentProvider(new ModulesContentProvider());
-               viewer.setLabelProvider(new ModulesLabelProvider());
-               viewer.setInput(bc);
-       }
-
-       @Focus
-       public void setFocus() {
-               viewer.getTree().setFocus();
-       }
-
-       private class ModulesContentProvider implements ITreeContentProvider {
-               private static final long serialVersionUID = 3819934804640641721L;
-
-               public Object[] getElements(Object inputElement) {
-                       return getChildren(inputElement);
-               }
-
-               public Object[] getChildren(Object parentElement) {
-                       if (parentElement instanceof BundleContext) {
-                               BundleContext bundleContext = (BundleContext) parentElement;
-                               Bundle[] bundles = bundleContext.getBundles();
-
-                               List<BundleNode> modules = new ArrayList<BundleNode>();
-                               for (Bundle bundle : bundles) {
-                                       if (bundle.getState() == Bundle.ACTIVE)
-                                               modules.add(new BundleNode(bundle, true));
-                               }
-                               return modules.toArray();
-                       } else if (parentElement instanceof TreeParent) {
-                               return ((TreeParent) parentElement).getChildren();
-                       } else {
-                               return null;
-                       }
-               }
-
-               public Object getParent(Object element) {
-                       // TODO Auto-generated method stub
-                       return null;
-               }
-
-               public boolean hasChildren(Object element) {
-                       if (element instanceof TreeParent) {
-                               return ((TreeParent) element).hasChildren();
-                       }
-                       return false;
-               }
-
-               public void dispose() {
-               }
-
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               }
-       }
-
-       private class ModulesLabelProvider extends StateLabelProvider {
-               private static final long serialVersionUID = 5290046145534824722L;
-
-               @Override
-               public String getText(Object element) {
-                       if (element instanceof BundleNode)
-                               return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]";
-                       return element.toString();
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java
deleted file mode 100644 (file)
index 759b3e9..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-package org.argeo.cms.e4.monitoring;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-
-import org.argeo.cms.CmsException;
-import org.argeo.util.LangUtils;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.jface.viewers.TreeViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-
-public class OsgiConfigurationsView {
-       private final static BundleContext bc = FrameworkUtil.getBundle(OsgiConfigurationsView.class).getBundleContext();
-
-       @PostConstruct
-       public void createPartControl(Composite parent) {
-               ConfigurationAdmin configurationAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
-
-               TreeViewer viewer = new TreeViewer(parent);
-               // viewer.getTree().setHeaderVisible(true);
-
-               TreeViewerColumn tvc = new TreeViewerColumn(viewer, SWT.NONE);
-               tvc.getColumn().setWidth(400);
-               tvc.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = 835407996597566763L;
-
-                       @Override
-                       public String getText(Object element) {
-                               if (element instanceof Configuration) {
-                                       return ((Configuration) element).getPid();
-                               } else if (element instanceof Prop) {
-                                       return ((Prop) element).key;
-                               }
-                               return super.getText(element);
-                       }
-
-                       @Override
-                       public Image getImage(Object element) {
-                               if (element instanceof Configuration)
-                                       return OsgiExplorerImages.CONFIGURATION;
-                               return null;
-                       }
-
-               });
-
-               tvc = new TreeViewerColumn(viewer, SWT.NONE);
-               tvc.getColumn().setWidth(400);
-               tvc.setLabelProvider(new ColumnLabelProvider() {
-                       private static final long serialVersionUID = 6999659261190014687L;
-
-                       @Override
-                       public String getText(Object element) {
-                               if (element instanceof Configuration) {
-                                       // return ((Configuration) element).getFactoryPid();
-                                       return null;
-                               } else if (element instanceof Prop) {
-                                       return ((Prop) element).value.toString();
-                               }
-                               return super.getText(element);
-                       }
-               });
-
-               viewer.setContentProvider(new ConfigurationsContentProvider());
-               viewer.setInput(configurationAdmin);
-       }
-
-       static class ConfigurationsContentProvider implements ITreeContentProvider {
-               private static final long serialVersionUID = -4892768279440981042L;
-               private ConfigurationComparator configurationComparator = new ConfigurationComparator();
-
-               @Override
-               public void dispose() {
-               }
-
-               @Override
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               }
-
-               @Override
-               public Object[] getElements(Object inputElement) {
-                       ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) inputElement;
-                       try {
-                               Configuration[] configurations = configurationAdmin.listConfigurations(null);
-                               Arrays.sort(configurations, configurationComparator);
-                               return configurations;
-                       } catch (IOException | InvalidSyntaxException e) {
-                               throw new CmsException("Cannot list configurations", e);
-                       }
-               }
-
-               @Override
-               public Object[] getChildren(Object parentElement) {
-                       if (parentElement instanceof Configuration) {
-                               List<Prop> res = new ArrayList<>();
-                               Configuration configuration = (Configuration) parentElement;
-                               Dictionary<String, Object> props = configuration.getProperties();
-                               keys: for (String key : LangUtils.keys(props)) {
-                                       if (Constants.SERVICE_PID.equals(key))
-                                               continue keys;
-                                       if (ConfigurationAdmin.SERVICE_FACTORYPID.equals(key))
-                                               continue keys;
-                                       res.add(new Prop(configuration, key, props.get(key)));
-                               }
-                               return res.toArray(new Prop[res.size()]);
-                       }
-                       return null;
-               }
-
-               @Override
-               public Object getParent(Object element) {
-                       if (element instanceof Prop)
-                               return ((Prop) element).configuration;
-                       return null;
-               }
-
-               @Override
-               public boolean hasChildren(Object element) {
-                       if (element instanceof Configuration)
-                               return true;
-                       return false;
-               }
-
-       }
-
-       static class Prop {
-               final Configuration configuration;
-               final String key;
-               final Object value;
-
-               public Prop(Configuration configuration, String key, Object value) {
-                       this.configuration = configuration;
-                       this.key = key;
-                       this.value = value;
-               }
-
-       }
-
-       static class ConfigurationComparator implements Comparator<Configuration> {
-
-               @Override
-               public int compare(Configuration o1, Configuration o2) {
-                       return o1.getPid().compareTo(o2.getPid());
-               }
-
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java
deleted file mode 100644 (file)
index 7217fe6..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java
deleted file mode 100644 (file)
index d9c45fe..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cms.e4.monitoring;
-
-import org.argeo.eclipse.ui.TreeParent;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.ServiceReference;
-
-/** A tree element representing a {@link ServiceReference} */
-@SuppressWarnings({ "rawtypes" })
-class ServiceReferenceNode extends TreeParent {
-       private final ServiceReference serviceReference;
-       private final boolean published;
-
-       public ServiceReferenceNode(ServiceReference serviceReference,
-                       boolean published) {
-               super(serviceReference.toString());
-               this.serviceReference = serviceReference;
-               this.published = published;
-
-               if (isPublished()) {
-                       Bundle[] usedBundles = serviceReference.getUsingBundles();
-                       if (usedBundles != null) {
-                               for (Bundle b : usedBundles) {
-                                       if (b != null)
-                                               addChild(new BundleNode(b));
-                               }
-                       }
-               } else {
-                       Bundle provider = serviceReference.getBundle();
-                       addChild(new BundleNode(provider));
-               }
-
-               for (String key : serviceReference.getPropertyKeys()) {
-                       addChild(new TreeParent(key + "="
-                                       + serviceReference.getProperty(key)));
-               }
-
-       }
-
-       public ServiceReference getServiceReference() {
-               return serviceReference;
-       }
-
-       public boolean isPublished() {
-               return published;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java
deleted file mode 100644 (file)
index 5cb5b65..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java
deleted file mode 100644 (file)
index 873bf31..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Monitoring perspective. */
-package org.argeo.cms.e4.monitoring;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java
deleted file mode 100644 (file)
index 233119c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse 4 user interfaces. */
-package org.argeo.cms.e4;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java
deleted file mode 100644 (file)
index 5a805d1..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cms.e4.parts;
-
-import java.security.AccessController;
-import java.time.ZonedDateTime;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** A canonical view of the logged in user. */
-public class EgoDashboard {
-       private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext();
-
-       @PostConstruct
-       public void createPartControl(Composite p) {
-               p.setLayout(new GridLayout());
-               String username = CurrentUser.getUsername();
-
-               CmsSwtUtils.lbl(p, "<strong>" + CurrentUser.getDisplayName() + "</strong>");
-               CmsSwtUtils.txt(p, username);
-               CmsSwtUtils.lbl(p, "Roles:");
-               roles: for (String role : CurrentUser.roles()) {
-                       if (username.equals(role))
-                               continue roles;
-                       CmsSwtUtils.txt(p, role);
-               }
-
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               if (subject != null) {
-                       CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bc, subject);
-                       ZonedDateTime loggedIndSince = cmsSession.getCreationTime();
-                       CmsSwtUtils.lbl(p, "Session:");
-                       CmsSwtUtils.txt(p, cmsSession.getUuid().toString());
-                       CmsSwtUtils.lbl(p, "Logged in since:");
-                       CmsSwtUtils.txt(p, loggedIndSince.toString());
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java
deleted file mode 100644 (file)
index 137f762..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.cms.ui.eclipse.forms.ManagedForm;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.e4.ui.di.Persist;
-import org.eclipse.e4.ui.model.application.ui.basic.MPart;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Editor for a user, might be a user or a group. */
-public abstract class AbstractRoleEditor {
-
-       // public final static String USER_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID +
-       // ".userEditor";
-       // public final static String GROUP_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID +
-       // ".groupEditor";
-
-       /* DEPENDENCY INJECTION */
-       @Inject
-       protected UserAdminWrapper userAdminWrapper;
-
-       @Inject
-       private MPart mPart;
-
-       // @Inject
-       // Composite parent;
-
-       private UserAdmin userAdmin;
-
-       // Context
-       private User user;
-       private String username;
-
-       private NameChangeListener listener;
-
-       private ManagedForm managedForm;
-
-       // public void init(IEditorSite site, IEditorInput input) throws
-       // PartInitException {
-       @PostConstruct
-       public void init(Composite parent) {
-               this.userAdmin = userAdminWrapper.getUserAdmin();
-               username = mPart.getPersistedState().get(LdapAttrs.uid.name());
-               user = (User) userAdmin.getRole(username);
-
-               listener = new NameChangeListener(Display.getCurrent());
-               userAdminWrapper.addListener(listener);
-               updateEditorTitle(null);
-
-               managedForm = new ManagedForm(parent) {
-
-                       @Override
-                       public void staleStateChanged() {
-                               refresh();
-                       }
-               };
-               ScrolledComposite scrolled = managedForm.getForm();
-               Composite body = new Composite(scrolled, SWT.NONE);
-               scrolled.setContent(body);
-               createUi(body);
-               managedForm.refresh();
-       }
-
-       abstract void createUi(Composite parent);
-
-       /**
-        * returns the list of all authorizations for the given user or of the current
-        * displayed user if parameter is null
-        */
-       protected List<User> getFlatGroups(User aUser) {
-               Authorization currAuth;
-               if (aUser == null)
-                       currAuth = userAdmin.getAuthorization(this.user);
-               else
-                       currAuth = userAdmin.getAuthorization(aUser);
-
-               String[] roles = currAuth.getRoles();
-
-               List<User> groups = new ArrayList<User>();
-               for (String roleStr : roles) {
-                       User currRole = (User) userAdmin.getRole(roleStr);
-                       if (currRole != null && !groups.contains(currRole))
-                               groups.add(currRole);
-               }
-               return groups;
-       }
-
-       protected IManagedForm getManagedForm() {
-               return managedForm;
-       }
-
-       /** Exposes the user (or group) that is displayed by the current editor */
-       protected User getDisplayedUser() {
-               return user;
-       }
-
-       private void setDisplayedUser(User user) {
-               this.user = user;
-       }
-
-       void updateEditorTitle(String title) {
-               if (title == null) {
-                       String commonName = UserAdminUtils.getProperty(user, LdapAttrs.cn.name());
-                       title = "".equals(commonName) ? user.getName() : commonName;
-               }
-               setPartName(title);
-       }
-
-       protected void setPartName(String name) {
-               mPart.setLabel(name);
-       }
-
-       // protected void addPages() {
-       // try {
-       // if (user.getType() == Role.GROUP)
-       // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance));
-       // else
-       // addPage(new UserMainPage(this, userAdminWrapper));
-       // } catch (Exception e) {
-       // throw new CmsException("Cannot add pages", e);
-       // }
-       // }
-
-       @Persist
-       public void doSave(IProgressMonitor monitor) {
-               userAdminWrapper.beginTransactionIfNeeded();
-               commitPages(true);
-               userAdminWrapper.commitOrNotifyTransactionStateChange();
-               // firePropertyChange(PROP_DIRTY);
-               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user));
-       }
-
-       protected void commitPages(boolean b) {
-               managedForm.commit(b);
-       }
-
-       @PreDestroy
-       public void dispose() {
-               userAdminWrapper.removeListener(listener);
-               managedForm.dispose();
-       }
-
-       // CONTROLERS FOR THIS EDITOR AND ITS PAGES
-
-       class NameChangeListener extends UiUserAdminListener {
-               public NameChangeListener(Display display) {
-                       super(display);
-               }
-
-               @Override
-               public void roleChangedToUiThread(UserAdminEvent event) {
-                       Role changedRole = event.getRole();
-                       if (changedRole == null || changedRole.equals(user)) {
-                               updateEditorTitle(null);
-                               User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName());
-                               setDisplayedUser(reloadedUser);
-                       }
-               }
-       }
-
-       class MainInfoListener extends UiUserAdminListener {
-               private final AbstractFormPart part;
-
-               public MainInfoListener(Display display, AbstractFormPart part) {
-                       super(display);
-                       this.part = part;
-               }
-
-               @Override
-               public void roleChangedToUiThread(UserAdminEvent event) {
-                       // Rollback
-                       if (event.getRole() == null)
-                               part.markStale();
-               }
-       }
-
-       class GroupChangeListener extends UiUserAdminListener {
-               private final AbstractFormPart part;
-
-               public GroupChangeListener(Display display, AbstractFormPart part) {
-                       super(display);
-                       this.part = part;
-               }
-
-               @Override
-               public void roleChangedToUiThread(UserAdminEvent event) {
-                       // always mark as stale
-                       part.markStale();
-               }
-       }
-
-       /** Registers a listener that will notify this part */
-       class FormPartML implements ModifyListener {
-               private static final long serialVersionUID = 6299808129505381333L;
-               private AbstractFormPart formPart;
-
-               public FormPartML(AbstractFormPart generalPart) {
-                       this.formPart = generalPart;
-               }
-
-               public void modifyText(ModifyEvent e) {
-                       // Discard event when the control does not have the focus, typically
-                       // to avoid all editors being marked as dirty during a Rollback
-                       if (((Control) e.widget).isFocusControl())
-                               formPart.markDirty();
-               }
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-
-       /** Creates label and multiline text. */
-       Text createLMT(Composite parent, String label, String value) {
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               Text text = new Text(parent, SWT.NONE);
-               text.setText(value);
-               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true));
-               return text;
-       }
-
-       /** Creates label and password. */
-       Text createLP(Composite parent, String label, String value) {
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER);
-               text.setText(value);
-               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false));
-               return text;
-       }
-
-       /** Creates label and text. */
-       Text createLT(Composite parent, String label, String value) {
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               lbl.setFont(EclipseUiUtils.getBoldFont(parent));
-               Text text = new Text(parent, SWT.BORDER);
-               text.setText(value);
-               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
-               return text;
-       }
-
-       Text createReadOnlyLT(Composite parent, String label, String value) {
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-               lbl.setFont(EclipseUiUtils.getBoldFont(parent));
-               Text text = new Text(parent, SWT.NONE);
-               text.setText(value);
-               text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false));
-               text.setEditable(false);
-               // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
-               return text;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java
deleted file mode 100644 (file)
index 07df312..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java
deleted file mode 100644 (file)
index a011c5f..0000000
+++ /dev/null
@@ -1,566 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import static org.argeo.api.cms.CmsContext.WORKGROUP;
-import static org.argeo.cms.auth.UserAdminUtils.setProperty;
-import static org.argeo.util.naming.LdapAttrs.businessCategory;
-import static org.argeo.util.naming.LdapAttrs.description;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.providers.CommonNameLP;
-import org.argeo.cms.e4.users.providers.MailLP;
-import org.argeo.cms.e4.users.providers.RoleIconLP;
-import org.argeo.cms.e4.users.providers.UserFilter;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ToolBarManager;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.ViewerDropAdapter;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.dnd.TransferData;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Link;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.ToolBar;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-//import org.eclipse.ui.forms.AbstractFormPart;
-//import org.eclipse.ui.forms.IManagedForm;
-//import org.eclipse.ui.forms.SectionPart;
-//import org.eclipse.ui.forms.editor.FormEditor;
-//import org.eclipse.ui.forms.editor.FormPage;
-//import org.eclipse.ui.forms.widgets.FormToolkit;
-//import org.eclipse.ui.forms.widgets.ScrolledForm;
-//import org.eclipse.ui.forms.widgets.Section;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Display/edit main properties of a given group */
-public class GroupEditor extends AbstractRoleEditor {
-       // final static String ID = "GroupEditor.mainPage";
-
-       @Inject
-       private EPartService partService;
-
-       // private final UserEditor editor;
-       @Inject
-       private Repository repository;
-       @Inject
-       private CmsContext nodeInstance;
-       // private final UserAdminWrapper userAdminWrapper;
-       private Session groupsSession;
-
-       // public GroupMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper,
-       // Repository repository,
-       // NodeInstance nodeInstance) {
-       // super(editor, ID, "Main");
-       // try {
-       // session = repository.login();
-       // } catch (RepositoryException e) {
-       // throw new CmsException("Cannot retrieve session of in MainGroupPage
-       // constructor", e);
-       // }
-       // this.editor = (UserEditor) editor;
-       // this.userAdminWrapper = userAdminWrapper;
-       // this.nodeInstance = nodeInstance;
-       // }
-
-       // protected void createFormContent(final IManagedForm mf) {
-       // ScrolledForm form = mf.getForm();
-       // Composite body = form.getBody();
-       // GridLayout mainLayout = new GridLayout();
-       // body.setLayout(mainLayout);
-       // Group group = (Group) editor.getDisplayedUser();
-       // appendOverviewPart(body, group);
-       // appendMembersPart(body, group);
-       // }
-
-       @Override
-       protected void createUi(Composite parent) {
-               try {
-                       groupsSession = repository.login(CmsConstants.SRV_WORKSPACE);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve session", e);
-               }
-               // ScrolledForm form = mf.getForm();
-               // Composite body = form.getBody();
-               // Composite body = new Composite(parent, SWT.NONE);
-               Composite body = parent;
-               GridLayout mainLayout = new GridLayout();
-               body.setLayout(mainLayout);
-               Group group = (Group) getDisplayedUser();
-               appendOverviewPart(body, group);
-               appendMembersPart(body, group);
-       }
-
-       @PreDestroy
-       public void dispose() {
-               JcrUtils.logoutQuietly(groupsSession);
-               super.dispose();
-       }
-
-       /** Creates the general section */
-       protected void appendOverviewPart(final Composite parent, final Group group) {
-               Composite body = new Composite(parent, SWT.NONE);
-               // GridLayout layout = new GridLayout(5, false);
-               GridLayout layout = new GridLayout(2, false);
-               body.setLayout(layout);
-               body.setLayoutData(CmsSwtUtils.fillWidth());
-
-               String cn = UserAdminUtils.getProperty(group, LdapAttrs.cn.name());
-               createReadOnlyLT(body, "Name", cn);
-               createReadOnlyLT(body, "DN", group.getName());
-               createReadOnlyLT(body, "Domain", UserAdminUtils.getDomainName(group));
-
-               // Description
-               Label descLbl = new Label(body, SWT.LEAD);
-               descLbl.setFont(EclipseUiUtils.getBoldFont(body));
-               descLbl.setText("Description");
-               descLbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1));
-               final Text descTxt = new Text(body, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER);
-               GridData gd = EclipseUiUtils.fillWidth();
-               gd.heightHint = 50;
-               gd.horizontalSpan = 2;
-               descTxt.setLayoutData(gd);
-
-               // Mark as workgroup
-               Link markAsWorkgroupLk = new Link(body, SWT.NONE);
-               markAsWorkgroupLk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
-
-               // create form part (controller)
-               final AbstractFormPart part = new AbstractFormPart() {
-
-                       private MainInfoListener listener;
-
-                       @Override
-                       public void initialize(IManagedForm form) {
-                               super.initialize(form);
-                               listener = new MainInfoListener(parent.getDisplay(), this);
-                               userAdminWrapper.addListener(listener);
-                       }
-
-                       @Override
-                       public void dispose() {
-                               userAdminWrapper.removeListener(listener);
-                               super.dispose();
-                       }
-
-                       public void commit(boolean onSave) {
-                               // group.getProperties().put(LdapAttrs.description.name(), descTxt.getText());
-                               setProperty(group, description, descTxt.getText());
-                               super.commit(onSave);
-                       }
-
-                       @Override
-                       public void refresh() {
-                               // dnTxt.setText(group.getName());
-                               // cnTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.cn.name()));
-                               descTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.description.name()));
-                               Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn);
-                               if (workgroupHome == null)
-                                       markAsWorkgroupLk.setText("<a>Mark as workgroup</a>");
-                               else
-                                       markAsWorkgroupLk.setText("Configured as workgroup");
-                               parent.layout(true, true);
-                               super.refresh();
-                       }
-               };
-
-               markAsWorkgroupLk.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = -6439340898096365078L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-
-                               boolean confirmed = MessageDialog.openConfirm(parent.getShell(), "Mark as workgroup",
-                                               "Are you sure you want to mark " + cn + " as being a workgroup? ");
-                               if (confirmed) {
-                                       Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn);
-                                       if (workgroupHome != null)
-                                               return; // already marked as workgroup, do nothing
-                                       else {
-                                               // improve transaction management
-                                               userAdminWrapper.beginTransactionIfNeeded();
-                                               nodeInstance.createWorkgroup(group.getName());
-                                               setProperty(group, businessCategory, WORKGROUP);
-                                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group));
-                                               part.refresh();
-                                       }
-                               }
-                       }
-               });
-
-               ModifyListener defaultListener = new FormPartML(part);
-               descTxt.addModifyListener(defaultListener);
-               getManagedForm().addPart(part);
-       }
-
-       /** Filtered table with members. Has drag and drop ability */
-       protected void appendMembersPart(Composite parent, Group group) {
-               // Section section = tk.createSection(parent, Section.TITLE_BAR);
-               // section.setText("Members");
-               // section.setLayoutData(EclipseUiUtils.fillAll());
-
-               Composite body = new Composite(parent, SWT.BORDER);
-               body.setLayout(new GridLayout());
-               // section.setClient(body);
-               body.setLayoutData(EclipseUiUtils.fillAll());
-
-               // Define the displayed columns
-               List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-               columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24));
-               columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150));
-               columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150));
-               // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name",
-               // 240));
-
-               // Create and configure the table
-               LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL,
-                               userAdminWrapper.getUserAdmin());
-
-               userViewerCmp.setColumnDefinitions(columnDefs);
-               userViewerCmp.populate(true, false);
-               userViewerCmp.setLayoutData(EclipseUiUtils.fillAll());
-
-               // Controllers
-               TableViewer userViewer = userViewerCmp.getTableViewer();
-               userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService));
-               int operations = DND.DROP_COPY | DND.DROP_MOVE;
-               Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
-               userViewer.addDropSupport(operations, tt,
-                               new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) getDisplayedUser()));
-
-               AbstractFormPart part = new GroupMembersPart(userViewerCmp);
-               getManagedForm().addPart(part);
-
-               // remove button
-               // addRemoveAbility(toolBarManager, userViewerCmp.getTableViewer(), group);
-               Action action = new RemoveMembershipAction(userViewer, group, "Remove selected items from this group",
-                               SecurityAdminImages.ICON_REMOVE_DESC);
-
-               ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
-               ToolBar toolBar = toolBarManager.createControl(body);
-               toolBar.setLayoutData(CmsSwtUtils.fillWidth());
-
-               toolBarManager.add(action);
-               toolBarManager.update(true);
-
-       }
-
-       // private LdifUsersTable createMemberPart(Composite parent, Group group) {
-       //
-       // // Define the displayed columns
-       // List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-       // columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24));
-       // columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150));
-       // columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150));
-       // // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished
-       // Name",
-       // // 240));
-       //
-       // // Create and configure the table
-       // LdifUsersTable userViewerCmp = new MyUserTableViewer(parent, SWT.MULTI |
-       // SWT.H_SCROLL | SWT.V_SCROLL,
-       // userAdminWrapper.getUserAdmin());
-       //
-       // userViewerCmp.setColumnDefinitions(columnDefs);
-       // userViewerCmp.populate(true, false);
-       // userViewerCmp.setLayoutData(EclipseUiUtils.fillAll());
-       //
-       // // Controllers
-       // TableViewer userViewer = userViewerCmp.getTableViewer();
-       // userViewer.addDoubleClickListener(new
-       // UserTableDefaultDClickListener(partService));
-       // int operations = DND.DROP_COPY | DND.DROP_MOVE;
-       // Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
-       // userViewer.addDropSupport(operations, tt,
-       // new GroupDropListener(userAdminWrapper, userViewerCmp, (Group)
-       // getDisplayedUser()));
-       //
-       // // userViewerCmp.refresh();
-       // return userViewerCmp;
-       // }
-
-       // Local viewers
-       private class MyUserTableViewer extends LdifUsersTable {
-               private static final long serialVersionUID = 8467999509931900367L;
-
-               private final UserFilter userFilter;
-
-               public MyUserTableViewer(Composite parent, int style, UserAdmin userAdmin) {
-                       super(parent, style, true);
-                       userFilter = new UserFilter();
-
-               }
-
-               @Override
-               protected List<User> listFilteredElements(String filter) {
-                       // reload user and set it in the editor
-                       Group group = (Group) getDisplayedUser();
-                       Role[] roles = group.getMembers();
-                       List<User> users = new ArrayList<User>();
-                       userFilter.setSearchText(filter);
-                       // userFilter.setShowSystemRole(true);
-                       for (Role role : roles)
-                               // if (role.getType() == Role.GROUP)
-                               if (userFilter.select(null, null, role))
-                                       users.add((User) role);
-                       return users;
-               }
-       }
-
-       // private void addRemoveAbility(ToolBarManager toolBarManager, TableViewer
-       // userViewer, Group group) {
-       // // Section section = sectionPart.getSection();
-       // // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
-       // // ToolBar toolbar = toolBarManager.createControl(parent);
-       // // ToolBar toolbar = toolBarManager.getControl();
-       // // final Cursor handCursor = new Cursor(toolbar.getDisplay(),
-       // SWT.CURSOR_HAND);
-       // // toolbar.setCursor(handCursor);
-       // // toolbar.addDisposeListener(new DisposeListener() {
-       // // private static final long serialVersionUID = 3882131405820522925L;
-       // //
-       // // public void widgetDisposed(DisposeEvent e) {
-       // // if ((handCursor != null) && (handCursor.isDisposed() == false)) {
-       // // handCursor.dispose();
-       // // }
-       // // }
-       // // });
-       //
-       // Action action = new RemoveMembershipAction(userViewer, group, "Remove
-       // selected items from this group",
-       // SecurityAdminImages.ICON_REMOVE_DESC);
-       // toolBarManager.add(action);
-       // toolBarManager.update(true);
-       // // section.setTextClient(toolbar);
-       // }
-
-       private class RemoveMembershipAction extends Action {
-               private static final long serialVersionUID = -1337713097184522588L;
-
-               private final TableViewer userViewer;
-               private final Group group;
-
-               RemoveMembershipAction(TableViewer userViewer, Group group, String name, ImageDescriptor img) {
-                       super(name, img);
-                       this.userViewer = userViewer;
-                       this.group = group;
-               }
-
-               @Override
-               public void run() {
-                       ISelection selection = userViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-
-                       @SuppressWarnings("unchecked")
-                       Iterator<User> it = ((IStructuredSelection) selection).iterator();
-                       List<User> users = new ArrayList<User>();
-                       while (it.hasNext()) {
-                               User currUser = it.next();
-                               users.add(currUser);
-                       }
-
-                       userAdminWrapper.beginTransactionIfNeeded();
-                       for (User user : users) {
-                               group.removeMember(user);
-                       }
-                       userAdminWrapper.commitOrNotifyTransactionStateChange();
-                       userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group));
-               }
-       }
-
-       // LOCAL CONTROLLERS
-       private class GroupMembersPart extends AbstractFormPart {
-               private final LdifUsersTable userViewer;
-               // private final Group group;
-
-               private GroupChangeListener listener;
-
-               public GroupMembersPart(LdifUsersTable userViewer) {
-                       // super(section);
-                       this.userViewer = userViewer;
-                       // this.group = group;
-               }
-
-               @Override
-               public void initialize(IManagedForm form) {
-                       super.initialize(form);
-                       listener = new GroupChangeListener(userViewer.getDisplay(), GroupMembersPart.this);
-                       userAdminWrapper.addListener(listener);
-               }
-
-               @Override
-               public void dispose() {
-                       userAdminWrapper.removeListener(listener);
-                       super.dispose();
-               }
-
-               @Override
-               public void refresh() {
-                       userViewer.refresh();
-                       super.refresh();
-               }
-       }
-
-       /**
-        * Defines this table as being a potential target to add group membership
-        * (roles) to this group
-        */
-       private class GroupDropListener extends ViewerDropAdapter {
-               private static final long serialVersionUID = 2893468717831451621L;
-
-               private final UserAdminWrapper userAdminWrapper;
-               // private final LdifUsersTable myUserViewerCmp;
-               private final Group myGroup;
-
-               public GroupDropListener(UserAdminWrapper userAdminWrapper, LdifUsersTable userTableViewerCmp, Group group) {
-                       super(userTableViewerCmp.getTableViewer());
-                       this.userAdminWrapper = userAdminWrapper;
-                       this.myGroup = group;
-                       // this.myUserViewerCmp = userTableViewerCmp;
-               }
-
-               @Override
-               public boolean validateDrop(Object target, int operation, TransferData transferType) {
-                       // Target is always OK in a list only view
-                       // TODO check if not a string
-                       boolean validDrop = true;
-                       return validDrop;
-               }
-
-               @Override
-               public void drop(DropTargetEvent event) {
-                       // TODO Is there an opportunity to perform the check before?
-                       String newUserName = (String) event.data;
-                       UserAdmin myUserAdmin = userAdminWrapper.getUserAdmin();
-                       Role role = myUserAdmin.getRole(newUserName);
-                       if (role.getType() == Role.GROUP) {
-                               Group newGroup = (Group) role;
-                               Shell shell = getViewer().getControl().getShell();
-                               // Sanity checks
-                               if (myGroup == newGroup) { // Equality
-                                       MessageDialog.openError(shell, "Forbidden addition ", "A group cannot be a member of itself.");
-                                       return;
-                               }
-
-                               // Cycle
-                               String myName = myGroup.getName();
-                               List<User> myMemberships = getFlatGroups(myGroup);
-                               if (myMemberships.contains(newGroup)) {
-                                       MessageDialog.openError(shell, "Forbidden addition: cycle",
-                                                       "Cannot add " + newUserName + " to group " + myName + ". This would create a cycle");
-                                       return;
-                               }
-
-                               // Already member
-                               List<User> newGroupMemberships = getFlatGroups(newGroup);
-                               if (newGroupMemberships.contains(myGroup)) {
-                                       MessageDialog.openError(shell, "Forbidden addition",
-                                                       "Cannot add " + newUserName + " to group " + myName + ", this membership already exists");
-                                       return;
-                               }
-                               userAdminWrapper.beginTransactionIfNeeded();
-                               myGroup.addMember(newGroup);
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup));
-                       } else if (role.getType() == Role.USER) {
-                               // TODO check if the group is already member of this group
-                               WorkTransaction transaction = userAdminWrapper.beginTransactionIfNeeded();
-                               User user = (User) role;
-                               myGroup.addMember(user);
-                               if (UserAdminWrapper.COMMIT_ON_SAVE)
-                                       try {
-                                               transaction.commit();
-                                       } catch (Exception e) {
-                                               throw new IllegalStateException(
-                                                               "Cannot commit transaction " + "after user group membership update", e);
-                                       }
-                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup));
-                       }
-                       super.drop(event);
-               }
-
-               @Override
-               public boolean performDrop(Object data) {
-                       // myUserViewerCmp.refresh();
-                       return true;
-               }
-       }
-
-       // LOCAL HELPERS
-       // private Composite addSection(FormToolkit tk, Composite parent) {
-       // Section section = tk.createSection(parent, SWT.NO_FOCUS);
-       // section.setLayoutData(EclipseUiUtils.fillWidth());
-       // Composite body = tk.createComposite(section, SWT.WRAP);
-       // body.setLayoutData(EclipseUiUtils.fillAll());
-       // section.setClient(body);
-       // return body;
-       // }
-
-       /** Creates label and text. */
-       // private Text createLT(Composite parent, String label, String value) {
-       // FormToolkit toolkit = getManagedForm().getToolkit();
-       // Label lbl = toolkit.createLabel(parent, label);
-       // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-       // lbl.setFont(EclipseUiUtils.getBoldFont(parent));
-       // Text text = toolkit.createText(parent, value, SWT.BORDER);
-       // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-       // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
-       // return text;
-       // }
-       //
-       // Text createReadOnlyLT(Composite parent, String label, String value) {
-       // FormToolkit toolkit = getManagedForm().getToolkit();
-       // Label lbl = toolkit.createLabel(parent, label);
-       // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false));
-       // lbl.setFont(EclipseUiUtils.getBoldFont(parent));
-       // Text text = toolkit.createText(parent, value, SWT.NONE);
-       // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-       // text.setEditable(false);
-       // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT);
-       // return text;
-       // }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java
deleted file mode 100644 (file)
index ddad34f..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.e4.users.providers.CommonNameLP;
-import org.argeo.cms.e4.users.providers.DomainNameLP;
-import org.argeo.cms.e4.users.providers.RoleIconLP;
-import org.argeo.cms.e4.users.providers.UserDragListener;
-//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin;
-//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener;
-//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener;
-//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-//import org.eclipse.ui.part.ViewPart;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/** List all groups with filter */
-public class GroupsView {
-       private final static CmsLog log = CmsLog.getLog(GroupsView.class);
-       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView";
-
-       @Inject
-       private EPartService partService;
-       @Inject
-       private UserAdminWrapper userAdminWrapper;
-
-       // UI Objects
-       private LdifUsersTable groupTableViewerCmp;
-       private TableViewer userViewer;
-       private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-
-       private UserAdminListener listener;
-
-       @PostConstruct
-       public void createPartControl(Composite parent, ESelectionService selectionService) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN);
-
-               // Define the displayed columns
-               columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 19));
-               columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150));
-               columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100));
-               // Only show technical DN to admin
-               // if (isAdmin)
-               // columnDefs.add(new ColumnDefinition(new UserNameLP(),
-               // "Distinguished Name", 300));
-
-               // Create and configure the table
-               groupTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
-
-               groupTableViewerCmp.setColumnDefinitions(columnDefs);
-               // if (isAdmin)
-               // groupTableViewerCmp.populateWithStaticFilters(false, false);
-               // else
-               groupTableViewerCmp.populate(true, false);
-
-               groupTableViewerCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               // Links
-               userViewer = groupTableViewerCmp.getTableViewer();
-               userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService));
-               // getViewSite().setSelectionProvider(userViewer);
-               userViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                       @Override
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) event.getSelection();
-                               selectionService.setSelection(selection.toList());
-                       }
-               });
-
-               // Really?
-               groupTableViewerCmp.refresh();
-
-               // Drag and drop
-               int operations = DND.DROP_COPY | DND.DROP_MOVE;
-               Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
-               userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer));
-
-               // // Register a useradmin listener
-               // listener = new UserAdminListener() {
-               // @Override
-               // public void roleChanged(UserAdminEvent event) {
-               // if (userViewer != null && !userViewer.getTable().isDisposed())
-               // refresh();
-               // }
-               // };
-               // userAdminWrapper.addListener(listener);
-               // }
-
-               // Register a useradmin listener
-               listener = new MyUiUAListener(parent.getDisplay());
-               userAdminWrapper.addListener(listener);
-       }
-
-       private class MyUiUAListener extends UiUserAdminListener {
-               public MyUiUAListener(Display display) {
-                       super(display);
-               }
-
-               @Override
-               public void roleChangedToUiThread(UserAdminEvent event) {
-                       if (userViewer != null && !userViewer.getTable().isDisposed())
-                               refresh();
-               }
-       }
-
-       private class MyUserTableViewer extends LdifUsersTable {
-               private static final long serialVersionUID = 8467999509931900367L;
-
-               private boolean showSystemRoles = true;
-
-               private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.cn.name(), LdapAttrs.DN };
-
-               public MyUserTableViewer(Composite parent, int style) {
-                       super(parent, style);
-                       showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN);
-               }
-
-               protected void populateStaticFilters(Composite staticFilterCmp) {
-                       staticFilterCmp.setLayout(new GridLayout());
-                       final Button showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK);
-                       showSystemRoleBtn.setText("Show system roles");
-                       showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN);
-                       showSystemRoleBtn.setSelection(showSystemRoles);
-
-                       showSystemRoleBtn.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = -7033424592697691676L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       showSystemRoles = showSystemRoleBtn.getSelection();
-                                       refresh();
-                               }
-
-                       });
-               }
-
-               @Override
-               protected List<User> listFilteredElements(String filter) {
-                       Role[] roles;
-                       try {
-                               StringBuilder builder = new StringBuilder();
-                               StringBuilder tmpBuilder = new StringBuilder();
-                               if (EclipseUiUtils.notEmpty(filter))
-                                       for (String prop : knownProps) {
-                                               tmpBuilder.append("(");
-                                               tmpBuilder.append(prop);
-                                               tmpBuilder.append("=*");
-                                               tmpBuilder.append(filter);
-                                               tmpBuilder.append("*)");
-                                       }
-                               if (tmpBuilder.length() > 1) {
-                                       builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                       .append(LdapObjs.groupOfNames.name()).append(")");
-                                       // hide tokens
-                                       builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.TOKENS_BASEDN)
-                                                       .append("))");
-
-                                       if (!showSystemRoles)
-                                               builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.ROLES_BASEDN)
-                                                               .append("))");
-                                       builder.append("(|");
-                                       builder.append(tmpBuilder.toString());
-                                       builder.append("))");
-                               } else {
-                                       if (!showSystemRoles)
-                                               builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                               .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*")
-                                                               .append(CmsConstants.ROLES_BASEDN).append("))(!(").append(LdapAttrs.DN).append("=*")
-                                                               .append(CmsConstants.TOKENS_BASEDN).append(")))");
-                                       else
-                                               builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                               .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*")
-                                                               .append(CmsConstants.TOKENS_BASEDN).append(")))");
-
-                               }
-                               roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString());
-                       } catch (InvalidSyntaxException e) {
-                               throw new CmsException("Unable to get roles with filter: " + filter, e);
-                       }
-                       List<User> users = new ArrayList<User>();
-                       for (Role role : roles)
-                               if (!users.contains(role))
-                                       users.add((User) role);
-                               else
-                                       log.warn("Duplicated role: " + role);
-
-                       return users;
-               }
-       }
-
-       public void refresh() {
-               groupTableViewerCmp.refresh();
-       }
-
-       @PreDestroy
-       public void dispose() {
-               userAdminWrapper.removeListener(listener);
-       }
-
-       @Focus
-       public void setFocus() {
-               groupTableViewerCmp.setFocus();
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java
deleted file mode 100644 (file)
index 7bbe3c7..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java
deleted file mode 100644 (file)
index f856492..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import org.argeo.osgi.transaction.WorkTransaction;
-
-/** First effort to centralize back end methods used by the user admin UI */
-public class UiAdminUtils {
-       /*
-        * INTERNAL METHODS: Below methods are meant to stay here and are not part
-        * of a potential generic backend to manage the useradmin
-        */
-       /** Easily notify the ActiveWindow that the transaction had a state change */
-       public final static void notifyTransactionStateChange(
-                       WorkTransaction userTransaction) {
-//             try {
-//                     IWorkbenchWindow aww = PlatformUI.getWorkbench()
-//                                     .getActiveWorkbenchWindow();
-//                     ISourceProviderService sourceProviderService = (ISourceProviderService) aww
-//                                     .getService(ISourceProviderService.class);
-//                     UserTransactionProvider esp = (UserTransactionProvider) sourceProviderService
-//                                     .getSourceProvider(UserTransactionProvider.TRANSACTION_STATE);
-//                     esp.fireTransactionStateChange();
-//             } catch (Exception e) {
-//                     throw new CmsException("Unable to begin transaction", e);
-//             }
-       }
-
-       /**
-        * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}.
-        * Thanks to <a href=
-        * "http://www.mkyong.com/regular-expressions/how-to-validate-email-address-with-regular-expression/"
-        * >this tip</a>.
-        */
-       public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java
deleted file mode 100644 (file)
index eb64aba..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java
deleted file mode 100644 (file)
index 16aa783..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/** Centralise interaction with the UserAdmin in this bundle */
-public class UserAdminWrapper {
-
-       private UserAdmin userAdmin;
-       // private ServiceReference<UserAdmin> userAdminServiceReference;
-//     private Set<String> uris;
-       private Map<UserDirectory, Hashtable<String, String>> userDirectories = Collections
-                       .synchronizedMap(new LinkedHashMap<>());
-       private WorkTransaction userTransaction;
-
-       // First effort to simplify UX while managing users and groups
-       public final static boolean COMMIT_ON_SAVE = true;
-
-       // Registered listeners
-       List<UserAdminListener> listeners = new ArrayList<UserAdminListener>();
-
-       /**
-        * Starts a transaction if necessary. Should always been called together with
-        * {@link UserAdminWrapper#commitOrNotifyTransactionStateChange()} once the
-        * security model changes have been performed.
-        */
-       public WorkTransaction beginTransactionIfNeeded() {
-               try {
-                       // UserTransaction userTransaction = getUserTransaction();
-                       if (userTransaction.isNoTransactionStatus()) {
-                               userTransaction.begin();
-                               // UiAdminUtils.notifyTransactionStateChange(userTransaction);
-                       }
-                       return userTransaction;
-               } catch (Exception e) {
-                       throw new CmsException("Unable to begin transaction", e);
-               }
-       }
-
-       /**
-        * Depending on the current application configuration, it will either commit the
-        * current transaction or throw a notification that the transaction state has
-        * changed (In the later case, it must be called from the UI thread).
-        */
-       public void commitOrNotifyTransactionStateChange() {
-               try {
-                       // UserTransaction userTransaction = getUserTransaction();
-                       if (userTransaction.isNoTransactionStatus())
-                               return;
-
-                       if (UserAdminWrapper.COMMIT_ON_SAVE)
-                               userTransaction.commit();
-                       else
-                               UiAdminUtils.notifyTransactionStateChange(userTransaction);
-               } catch (Exception e) {
-                       throw new CmsException("Unable to clean transaction", e);
-               }
-       }
-
-       // TODO implement safer mechanism
-       public void addListener(UserAdminListener userAdminListener) {
-               if (!listeners.contains(userAdminListener))
-                       listeners.add(userAdminListener);
-       }
-
-       public void removeListener(UserAdminListener userAdminListener) {
-               if (listeners.contains(userAdminListener))
-                       listeners.remove(userAdminListener);
-       }
-
-       public void notifyListeners(UserAdminEvent event) {
-               for (UserAdminListener listener : listeners)
-                       listener.roleChanged(event);
-       }
-
-       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-               Map<String, String> dns = new HashMap<String, String>();
-               for (UserDirectory userDirectory : userDirectories.keySet()) {
-                       Boolean readOnly = userDirectory.isReadOnly();
-                       String baseDn = userDirectory.getBaseDn().toString();
-
-                       if (onlyWritable && readOnly)
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
-                               continue;
-                       dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
-
-               }
-//             for (String uri : uris) {
-//                     if (!uri.startsWith("/"))
-//                             continue;
-//                     Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
-//                     String readOnly = UserAdminConf.readOnly.getValue(props);
-//                     String baseDn = UserAdminConf.baseDn.getValue(props);
-//
-//                     if (onlyWritable && "true".equals(readOnly))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
-//                             continue;
-//                     dns.put(baseDn, uri);
-//             }
-               return dns;
-       }
-
-       public UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-       public WorkTransaction getUserTransaction() {
-               return userTransaction;
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdmin(UserAdmin userAdmin, Map<String, String> properties) {
-               this.userAdmin = userAdmin;
-//             this.uris = Collections.unmodifiableSortedSet(new TreeSet<>(properties.keySet()));
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-       public void addUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
-               userDirectories.put(userDirectory, new Hashtable<>(properties));
-       }
-
-       public void removeUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
-               userDirectories.remove(userDirectory);
-       }
-
-       // public void setUserAdminServiceReference(
-       // ServiceReference<UserAdmin> userAdminServiceReference) {
-       // this.userAdminServiceReference = userAdminServiceReference;
-       // }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java
deleted file mode 100644 (file)
index a38d171..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.providers.CommonNameLP;
-import org.argeo.cms.e4.users.providers.DomainNameLP;
-import org.argeo.cms.e4.users.providers.MailLP;
-import org.argeo.cms.e4.users.providers.UserNameLP;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.jface.dialogs.IPageChangeProvider;
-import org.eclipse.jface.dialogs.IPageChangedListener;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.dialogs.PageChangedEvent;
-import org.eclipse.jface.wizard.IWizardContainer;
-import org.eclipse.jface.wizard.Wizard;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Wizard to update users */
-public class UserBatchUpdateWizard extends Wizard {
-
-       private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class);
-       private UserAdminWrapper userAdminWrapper;
-
-       // pages
-       private ChooseCommandWizardPage chooseCommandPage;
-       private ChooseUsersWizardPage userListPage;
-       private ValidateAndLaunchWizardPage validatePage;
-
-       // Various implemented commands keys
-       private final static String CMD_UPDATE_PASSWORD = "resetPassword";
-       private final static String CMD_UPDATE_EMAIL = "resetEmail";
-       private final static String CMD_GROUP_MEMBERSHIP = "groupMembership";
-
-       private final Map<String, String> commands = new HashMap<String, String>() {
-               private static final long serialVersionUID = 1L;
-               {
-                       put("Reset password(s)", CMD_UPDATE_PASSWORD);
-                       put("Reset email(s)", CMD_UPDATE_EMAIL);
-                       // TODO implement role / group management
-                       // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP);
-               }
-       };
-
-       public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-
-       @Override
-       public void addPages() {
-               chooseCommandPage = new ChooseCommandWizardPage();
-               addPage(chooseCommandPage);
-               userListPage = new ChooseUsersWizardPage();
-               addPage(userListPage);
-               validatePage = new ValidateAndLaunchWizardPage();
-               addPage(validatePage);
-       }
-
-       @Override
-       public boolean performFinish() {
-               if (!canFinish())
-                       return false;
-               WorkTransaction ut = userAdminWrapper.getUserTransaction();
-               if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction",
-                               "A user transaction is already existing, " + "are you sure you want to proceed ?"))
-                       return false;
-
-               // We cannot use jobs, user modifications are still meant to be done in
-               // the UIThread
-               // UpdateJob job = null;
-               // if (job != null)
-               // job.schedule();
-
-               if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) {
-                       char[] newValue = chooseCommandPage.getPwdValue();
-                       if (newValue == null)
-                               throw new CmsException("Password cannot be null or an empty string");
-                       ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue);
-                       job.doUpdate();
-               } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) {
-                       String newValue = chooseCommandPage.getEmailValue();
-                       if (newValue == null)
-                               throw new CmsException("Password cannot be null or an empty string");
-                       ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue);
-                       job.doUpdate();
-               }
-               return true;
-       }
-
-       public boolean canFinish() {
-               if (this.getContainer().getCurrentPage() == validatePage)
-                       return true;
-               return false;
-       }
-
-       private class ResetPassword {
-               private char[] newPwd;
-               private UserAdminWrapper userAdminWrapper;
-               private List<User> usersToUpdate;
-
-               public ResetPassword(UserAdminWrapper userAdminWrapper, List<User> usersToUpdate, char[] newPwd) {
-                       this.newPwd = newPwd;
-                       this.usersToUpdate = usersToUpdate;
-                       this.userAdminWrapper = userAdminWrapper;
-               }
-
-               @SuppressWarnings("unchecked")
-               protected void doUpdate() {
-                       userAdminWrapper.beginTransactionIfNeeded();
-                       try {
-                               for (User user : usersToUpdate) {
-                                       // the char array is emptied after being used.
-                                       user.getCredentials().put(null, newPwd.clone());
-                               }
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                       } catch (Exception e) {
-                               throw new CmsException("Cannot perform batch update on users", e);
-                       } finally {
-                               WorkTransaction ut = userAdminWrapper.getUserTransaction();
-                               if (!ut.isNoTransactionStatus())
-                                       ut.rollback();
-                       }
-               }
-       }
-
-       private class ResetEmail {
-               private String newEmail;
-               private UserAdminWrapper userAdminWrapper;
-               private List<User> usersToUpdate;
-
-               public ResetEmail(UserAdminWrapper userAdminWrapper, List<User> usersToUpdate, String newEmail) {
-                       this.newEmail = newEmail;
-                       this.usersToUpdate = usersToUpdate;
-                       this.userAdminWrapper = userAdminWrapper;
-               }
-
-               @SuppressWarnings("unchecked")
-               protected void doUpdate() {
-                       userAdminWrapper.beginTransactionIfNeeded();
-                       try {
-                               for (User user : usersToUpdate) {
-                                       // the char array is emptied after being used.
-                                       user.getProperties().put(LdapAttrs.mail.name(), newEmail);
-                               }
-
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               if (!usersToUpdate.isEmpty())
-                                       userAdminWrapper.notifyListeners(
-                                                       new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0)));
-                       } catch (Exception e) {
-                               throw new CmsException("Cannot perform batch update on users", e);
-                       } finally {
-                               WorkTransaction ut = userAdminWrapper.getUserTransaction();
-                               if (!ut.isNoTransactionStatus())
-                                       ut.rollback();
-                       }
-               }
-       }
-
-       // @SuppressWarnings("unused")
-       // private class AddToGroup extends UpdateJob {
-       // private String groupID;
-       // private Session session;
-       //
-       // public AddToGroup(Session session, List<Node> nodesToUpdate,
-       // String groupID) {
-       // super(session, nodesToUpdate);
-       // this.session = session;
-       // this.groupID = groupID;
-       // }
-       //
-       // protected void doUpdate(Node node) {
-       // log.info("Add/Remove to group actions are not yet implemented");
-       // // TODO implement this
-       // // try {
-       // // throw new CmsException("Not yet implemented");
-       // // } catch (RepositoryException re) {
-       // // throw new CmsException(
-       // // "Unable to update boolean value for node " + node, re);
-       // // }
-       // }
-       // }
-
-       // /**
-       // * Base privileged job that will be run asynchronously to perform the
-       // batch
-       // * update
-       // */
-       // private abstract class UpdateJob extends PrivilegedJob {
-       //
-       // private final UserAdminWrapper userAdminWrapper;
-       // private final List<User> usersToUpdate;
-       //
-       // protected abstract void doUpdate(User user);
-       //
-       // public UpdateJob(UserAdminWrapper userAdminWrapper,
-       // List<User> usersToUpdate) {
-       // super("Perform update");
-       // this.usersToUpdate = usersToUpdate;
-       // this.userAdminWrapper = userAdminWrapper;
-       // }
-       //
-       // @Override
-       // protected IStatus doRun(IProgressMonitor progressMonitor) {
-       // try {
-       // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor);
-       // int total = usersToUpdate.size();
-       // monitor.beginTask("Performing change", total);
-       // userAdminWrapper.beginTransactionIfNeeded();
-       // for (User user : usersToUpdate) {
-       // doUpdate(user);
-       // monitor.worked(1);
-       // }
-       // userAdminWrapper.getUserTransaction().commit();
-       // } catch (Exception e) {
-       // throw new CmsException(
-       // "Cannot perform batch update on users", e);
-       // } finally {
-       // UserTransaction ut = userAdminWrapper.getUserTransaction();
-       // try {
-       // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION)
-       // ut.rollback();
-       // } catch (IllegalStateException | SecurityException
-       // | SystemException e) {
-       // log.error("Unable to rollback session in 'finally', "
-       // + "the system might be in a dirty state");
-       // e.printStackTrace();
-       // }
-       // }
-       // return Status.OK_STATUS;
-       // }
-       // }
-
-       // PAGES
-       /**
-        * Displays a combo box that enables user to choose which action to perform
-        */
-       private class ChooseCommandWizardPage extends WizardPage {
-               private static final long serialVersionUID = -8069434295293996633L;
-               private Combo chooseCommandCmb;
-               private Button trueChk;
-               private Text valueTxt;
-               private Text pwdTxt;
-               private Text pwd2Txt;
-
-               public ChooseCommandWizardPage() {
-                       super("Choose a command to run.");
-                       setTitle("Choose a command to run.");
-               }
-
-               @Override
-               public void createControl(Composite parent) {
-                       GridLayout gl = new GridLayout();
-                       Composite container = new Composite(parent, SWT.NO_FOCUS);
-                       container.setLayout(gl);
-
-                       chooseCommandCmb = new Combo(container, SWT.READ_ONLY);
-                       chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth());
-                       String[] values = commands.keySet().toArray(new String[0]);
-                       chooseCommandCmb.setItems(values);
-
-                       final Composite bottomPart = new Composite(container, SWT.NO_FOCUS);
-                       bottomPart.setLayoutData(EclipseUiUtils.fillAll());
-                       bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-                       chooseCommandCmb.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = 1L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       if (getCommand().equals(CMD_UPDATE_PASSWORD))
-                                               populatePasswordCmp(bottomPart);
-                                       else if (getCommand().equals(CMD_UPDATE_EMAIL))
-                                               populateEmailCmp(bottomPart);
-                                       else if (getCommand().equals(CMD_GROUP_MEMBERSHIP))
-                                               populateGroupCmp(bottomPart);
-                                       else
-                                               populateBooleanFlagCmp(bottomPart);
-                                       checkPageComplete();
-                                       bottomPart.layout(true, true);
-                               }
-                       });
-                       setControl(container);
-               }
-
-               private void populateBooleanFlagCmp(Composite parent) {
-                       EclipseUiUtils.clear(parent);
-                       trueChk = new Button(parent, SWT.CHECK);
-                       trueChk.setText("Do it. (It will to the contrary if unchecked)");
-                       trueChk.setSelection(true);
-                       trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
-               }
-
-               private void populatePasswordCmp(Composite parent) {
-                       EclipseUiUtils.clear(parent);
-                       Composite body = new Composite(parent, SWT.NO_FOCUS);
-
-                       ModifyListener ml = new ModifyListener() {
-                               private static final long serialVersionUID = -1558726363536729634L;
-
-                               @Override
-                               public void modifyText(ModifyEvent event) {
-                                       checkPageComplete();
-                               }
-                       };
-
-                       body.setLayout(new GridLayout(2, false));
-                       body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml);
-                       pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml);
-               }
-
-               private void populateEmailCmp(Composite parent) {
-                       EclipseUiUtils.clear(parent);
-                       Composite body = new Composite(parent, SWT.NO_FOCUS);
-
-                       ModifyListener ml = new ModifyListener() {
-                               private static final long serialVersionUID = 2147704227294268317L;
-
-                               @Override
-                               public void modifyText(ModifyEvent event) {
-                                       checkPageComplete();
-                               }
-                       };
-
-                       body.setLayout(new GridLayout(2, false));
-                       body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml);
-               }
-
-               private void checkPageComplete() {
-                       String errorMsg = null;
-                       if (chooseCommandCmb.getSelectionIndex() < 0)
-                               errorMsg = "Please select an action";
-                       else if (CMD_UPDATE_EMAIL.equals(getCommand())) {
-                               if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN))
-                                       errorMsg = "Not a valid e-mail address";
-                       } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) {
-                               if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4)
-                                       errorMsg = "Please enter a password that is at least 4 character long";
-                               else if (!pwdTxt.getText().equals(pwd2Txt.getText()))
-                                       errorMsg = "Passwords are different";
-                       }
-                       if (EclipseUiUtils.notEmpty(errorMsg)) {
-                               setMessage(errorMsg, WizardPage.ERROR);
-                               setPageComplete(false);
-                       } else {
-                               setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION);
-                               setPageComplete(true);
-                       }
-
-                       getContainer().updateButtons();
-               }
-
-               private void populateGroupCmp(Composite parent) {
-                       EclipseUiUtils.clear(parent);
-                       trueChk = new Button(parent, SWT.CHECK);
-                       trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)");
-                       trueChk.setSelection(true);
-                       trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
-               }
-
-               protected String getCommand() {
-                       return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()));
-               }
-
-               protected String getCommandLbl() {
-                       return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex());
-               }
-
-               @SuppressWarnings("unused")
-               protected boolean getBoleanValue() {
-                       // FIXME this is not consistent and will lead to errors.
-                       if ("argeo:enabled".equals(getCommand()))
-                               return trueChk.getSelection();
-                       else
-                               return !trueChk.getSelection();
-               }
-
-               @SuppressWarnings("unused")
-               protected String getStringValue() {
-                       String value = null;
-                       if (valueTxt != null) {
-                               value = valueTxt.getText();
-                               if ("".equals(value.trim()))
-                                       value = null;
-                       }
-                       return value;
-               }
-
-               protected char[] getPwdValue() {
-                       // We do not directly reset the password text fields: There is no
-                       // need to over secure this process: setting a pwd to multi users
-                       // at the same time is anyhow a bad practice and should be used only
-                       // in test environment or for temporary access
-                       if (pwdTxt == null || pwdTxt.isDisposed())
-                               return null;
-                       else
-                               return pwdTxt.getText().toCharArray();
-               }
-
-               protected String getEmailValue() {
-                       // We do not directly reset the password text fields: There is no
-                       // need to over secure this process: setting a pwd to multi users
-                       // at the same time is anyhow a bad practice and should be used only
-                       // in test environment or for temporary access
-                       if (valueTxt == null || valueTxt.isDisposed())
-                               return null;
-                       else
-                               return valueTxt.getText();
-               }
-       }
-
-       /**
-        * Displays a list of users with a check box to be able to choose some of them
-        */
-       private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener {
-               private static final long serialVersionUID = 7651807402211214274L;
-               private ChooseUserTableViewer userTableCmp;
-
-               public ChooseUsersWizardPage() {
-                       super("Choose Users");
-                       setTitle("Select users who will be impacted");
-               }
-
-               @Override
-               public void createControl(Composite parent) {
-                       Composite pageCmp = new Composite(parent, SWT.NONE);
-                       pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-                       // Define the displayed columns
-                       List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-                       columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
-                       columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
-                       columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
-
-                       // Only show technical DN to admin
-                       if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN))
-                               columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
-
-                       userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
-                       userTableCmp.setLayoutData(EclipseUiUtils.fillAll());
-                       userTableCmp.setColumnDefinitions(columnDefs);
-                       userTableCmp.populate(true, true);
-                       userTableCmp.refresh();
-
-                       setControl(pageCmp);
-
-                       // Add listener to update message when shown
-                       final IWizardContainer wContainer = this.getContainer();
-                       if (wContainer instanceof IPageChangeProvider) {
-                               ((IPageChangeProvider) wContainer).addPageChangedListener(this);
-                       }
-
-               }
-
-               @Override
-               public void pageChanged(PageChangedEvent event) {
-                       if (event.getSelectedPage() == this) {
-                               String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl();
-                               ((WizardPage) event.getSelectedPage()).setMessage(msg);
-                       }
-               }
-
-               protected List<User> getSelectedUsers() {
-                       return userTableCmp.getSelectedUsers();
-               }
-
-               private class ChooseUserTableViewer extends LdifUsersTable {
-                       private static final long serialVersionUID = 5080437561015853124L;
-                       private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(),
-                                       LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() };
-
-                       public ChooseUserTableViewer(Composite parent, int style) {
-                               super(parent, style);
-                       }
-
-                       @Override
-                       protected List<User> listFilteredElements(String filter) {
-                               Role[] roles;
-
-                               try {
-                                       StringBuilder builder = new StringBuilder();
-
-                                       StringBuilder tmpBuilder = new StringBuilder();
-                                       if (EclipseUiUtils.notEmpty(filter))
-                                               for (String prop : knownProps) {
-                                                       tmpBuilder.append("(");
-                                                       tmpBuilder.append(prop);
-                                                       tmpBuilder.append("=*");
-                                                       tmpBuilder.append(filter);
-                                                       tmpBuilder.append("*)");
-                                               }
-                                       if (tmpBuilder.length() > 1) {
-                                               builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                               .append(LdapObjs.inetOrgPerson.name()).append(")(|");
-                                               builder.append(tmpBuilder.toString());
-                                               builder.append("))");
-                                       } else
-                                               builder.append("(").append(LdapAttrs.objectClass.name()).append("=")
-                                                               .append(LdapObjs.inetOrgPerson.name()).append(")");
-                                       roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString());
-                               } catch (InvalidSyntaxException e) {
-                                       throw new CmsException("Unable to get roles with filter: " + filter, e);
-                               }
-                               List<User> users = new ArrayList<User>();
-                               for (Role role : roles)
-                                       // Prevent current logged in user to perform batch on
-                                       // himself
-                                       if (!UserAdminUtils.isCurrentUser((User) role))
-                                               users.add((User) role);
-                               return users;
-                       }
-               }
-       }
-
-       /** Summary of input data before launching the process */
-       private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener {
-               private static final long serialVersionUID = 7098918351451743853L;
-               private ChosenUsersTableViewer userTableCmp;
-
-               public ValidateAndLaunchWizardPage() {
-                       super("Validate and launch");
-                       setTitle("Validate and launch");
-               }
-
-               @Override
-               public void createControl(Composite parent) {
-                       Composite pageCmp = new Composite(parent, SWT.NO_FOCUS);
-                       pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-                       List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-                       columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
-                       columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
-                       columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
-                       // Only show technical DN to admin
-                       if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN))
-                               columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
-                       userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
-                       userTableCmp.setLayoutData(EclipseUiUtils.fillAll());
-                       userTableCmp.setColumnDefinitions(columnDefs);
-                       userTableCmp.populate(false, false);
-                       userTableCmp.refresh();
-                       setControl(pageCmp);
-                       // Add listener to update message when shown
-                       final IWizardContainer wContainer = this.getContainer();
-                       if (wContainer instanceof IPageChangeProvider) {
-                               ((IPageChangeProvider) wContainer).addPageChangedListener(this);
-                       }
-               }
-
-               @Override
-               public void pageChanged(PageChangedEvent event) {
-                       if (event.getSelectedPage() == this) {
-                               @SuppressWarnings({ "unchecked", "rawtypes" })
-                               Object[] values = ((ArrayList) userListPage.getSelectedUsers())
-                                               .toArray(new Object[userListPage.getSelectedUsers().size()]);
-                               userTableCmp.getTableViewer().setInput(values);
-                               String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl()
-                                               + "] will be perfomed on the users listed below.\n";
-                               // + "Are you sure you want to proceed?";
-                               setMessage(msg);
-                       }
-               }
-
-               private class ChosenUsersTableViewer extends LdifUsersTable {
-                       private static final long serialVersionUID = 7814764735794270541L;
-
-                       public ChosenUsersTableViewer(Composite parent, int style) {
-                               super(parent, style);
-                       }
-
-                       @Override
-                       protected List<User> listFilteredElements(String filter) {
-                               return userListPage.getSelectedUsers();
-                       }
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java
deleted file mode 100644 (file)
index 66f4420..0000000
+++ /dev/null
@@ -1,535 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import static org.argeo.cms.auth.UserAdminUtils.getProperty;
-import static org.argeo.util.naming.LdapAttrs.cn;
-import static org.argeo.util.naming.LdapAttrs.givenName;
-import static org.argeo.util.naming.LdapAttrs.mail;
-import static org.argeo.util.naming.LdapAttrs.sn;
-import static org.argeo.util.naming.LdapAttrs.uid;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.providers.CommonNameLP;
-import org.argeo.cms.e4.users.providers.DomainNameLP;
-import org.argeo.cms.e4.users.providers.RoleIconLP;
-import org.argeo.cms.e4.users.providers.UserFilter;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
-//import org.argeo.cms.ui.eclipse.forms.FormToolkit;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ToolBarManager;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.dialogs.TrayDialog;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerDropAdapter;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.dnd.TransferData;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Link;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.ToolBar;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Display/edit the properties of a given user */
-public class UserEditor extends AbstractRoleEditor {
-       // final static String ID = "UserEditor.mainPage";
-
-       @Inject
-       private EPartService partService;
-
-       // private final UserEditor editor;
-       // private UserAdminWrapper userAdminWrapper;
-
-       // Local configuration
-       // private final int PRE_TITLE_INDENT = 10;
-
-       // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) {
-       // super(editor, ID, "Main");
-       // this.editor = (UserEditor) editor;
-       // this.userAdminWrapper = userAdminWrapper;
-       // }
-
-       // protected void createFormContent(final IManagedForm mf) {
-       // ScrolledForm form = mf.getForm();
-       // Composite body = form.getBody();
-       // GridLayout mainLayout = new GridLayout();
-       // // mainLayout.marginRight = 10;
-       // body.setLayout(mainLayout);
-       // User user = editor.getDisplayedUser();
-       // appendOverviewPart(body, user);
-       // // Remove to ability to force the password for his own user. The user
-       // // must then use the change pwd feature
-       // appendMemberOfPart(body, user);
-       // }
-
-       @Override
-       protected void createUi(Composite body) {
-               // Composite body = new Composite(parent, SWT.BORDER);
-               GridLayout mainLayout = new GridLayout();
-               // mainLayout.marginRight = 10;
-               body.setLayout(mainLayout);
-               // body.getParent().setLayout(new GridLayout());
-               // body.setLayoutData(CmsUiUtils.fillAll());
-               User user = getDisplayedUser();
-               appendOverviewPart(body, user);
-               // Remove to ability to force the password for his own user. The user
-               // must then use the change pwd feature
-               appendMemberOfPart(body, user);
-       }
-
-       /** Creates the general section */
-       private void appendOverviewPart(final Composite parent, final User user) {
-               // FormToolkit tk = getManagedForm().getToolkit();
-
-               // Section section = tk.createSection(parent, SWT.NO_FOCUS);
-               // GridData gd = EclipseUiUtils.fillWidth();
-               // // gd.verticalAlignment = PRE_TITLE_INDENT;
-               // section.setLayoutData(gd);
-               Composite body = new Composite(parent, SWT.NONE);
-               body.setLayoutData(EclipseUiUtils.fillWidth());
-               // section.setClient(body);
-               // body.setLayout(new GridLayout(6, false));
-               body.setLayout(new GridLayout(2, false));
-
-               Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn));
-               Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid));
-               Text firstName = createLT(body, "First name", getProperty(user, givenName));
-               Text lastName = createLT(body, "Last name", getProperty(user, sn));
-               Text email = createLT(body, "Email", getProperty(user, mail));
-
-               Link resetPwdLk = new Link(body, SWT.NONE);
-               if (!UserAdminUtils.isCurrentUser(user)) {
-                       resetPwdLk.setText("<a>Reset password</a>");
-               }
-               resetPwdLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
-
-               // create form part (controller)
-               AbstractFormPart part = new AbstractFormPart() {
-                       private MainInfoListener listener;
-
-                       @Override
-                       public void initialize(IManagedForm form) {
-                               super.initialize(form);
-                               listener = new MainInfoListener(parent.getDisplay(), this);
-                               userAdminWrapper.addListener(listener);
-                       }
-
-                       @Override
-                       public void dispose() {
-                               userAdminWrapper.removeListener(listener);
-                               super.dispose();
-                       }
-
-                       @SuppressWarnings("unchecked")
-                       public void commit(boolean onSave) {
-                               // TODO Sanity checks (mail validity...)
-                               user.getProperties().put(LdapAttrs.givenName.name(), firstName.getText());
-                               user.getProperties().put(LdapAttrs.sn.name(), lastName.getText());
-                               user.getProperties().put(LdapAttrs.cn.name(), commonName.getText());
-                               user.getProperties().put(LdapAttrs.mail.name(), email.getText());
-                               super.commit(onSave);
-                       }
-
-                       @Override
-                       public void refresh() {
-                               distinguishedName.setText(UserAdminUtils.getProperty(user, LdapAttrs.uid.name()));
-                               commonName.setText(UserAdminUtils.getProperty(user, LdapAttrs.cn.name()));
-                               firstName.setText(UserAdminUtils.getProperty(user, LdapAttrs.givenName.name()));
-                               lastName.setText(UserAdminUtils.getProperty(user, LdapAttrs.sn.name()));
-                               email.setText(UserAdminUtils.getProperty(user, LdapAttrs.mail.name()));
-                               refreshFormTitle(user);
-                               super.refresh();
-                       }
-               };
-
-               // Improve this: automatically generate CN when first or last name
-               // changes
-               ModifyListener cnML = new ModifyListener() {
-                       private static final long serialVersionUID = 4298649222869835486L;
-
-                       @Override
-                       public void modifyText(ModifyEvent event) {
-                               String first = firstName.getText();
-                               String last = lastName.getText();
-                               String cn = first.trim() + " " + last.trim() + " ";
-                               cn = cn.trim();
-                               commonName.setText(cn);
-                               // getManagedForm().getForm().setText(cn);
-                               updateEditorTitle(cn);
-                       }
-               };
-               firstName.addModifyListener(cnML);
-               lastName.addModifyListener(cnML);
-
-               ModifyListener defaultListener = new FormPartML(part);
-               firstName.addModifyListener(defaultListener);
-               lastName.addModifyListener(defaultListener);
-               email.addModifyListener(defaultListener);
-
-               if (!UserAdminUtils.isCurrentUser(user))
-                       resetPwdLk.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = 5881800534589073787L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       new ChangePasswordDialog(user, "Reset password").open();
-                               }
-                       });
-
-               getManagedForm().addPart(part);
-       }
-
-       private class ChangePasswordDialog extends TrayDialog {
-               private static final long serialVersionUID = 2843538207460082349L;
-
-               private User user;
-               private Text password1;
-               private Text password2;
-               private String title;
-               // private FormToolkit tk;
-
-               public ChangePasswordDialog(User user, String title) {
-                       super(Display.getDefault().getActiveShell());
-                       // this.tk = tk;
-                       this.user = user;
-                       this.title = title;
-               }
-
-               protected Control createDialogArea(Composite parent) {
-                       Composite dialogarea = (Composite) super.createDialogArea(parent);
-                       dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       Composite body = new Composite(dialogarea, SWT.NO_FOCUS);
-                       body.setLayoutData(EclipseUiUtils.fillAll());
-                       GridLayout layout = new GridLayout(2, false);
-                       body.setLayout(layout);
-
-                       password1 = createLP(body, "New password", "");
-                       password2 = createLP(body, "Repeat password", "");
-                       parent.pack();
-                       return body;
-               }
-
-               @SuppressWarnings("unchecked")
-               @Override
-               protected void okPressed() {
-                       String msg = null;
-
-                       if (password1.getText().equals(""))
-                               msg = "Password cannot be empty";
-                       else if (password1.getText().equals(password2.getText())) {
-                               char[] newPassword = password1.getText().toCharArray();
-                               // userAdminWrapper.beginTransactionIfNeeded();
-                               userAdminWrapper.beginTransactionIfNeeded();
-                               user.getCredentials().put(null, newPassword);
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               super.okPressed();
-                       } else {
-                               msg = "Passwords are not equals";
-                       }
-
-                       if (EclipseUiUtils.notEmpty(msg))
-                               MessageDialog.openError(getParentShell(), "Cannot reset pasword", msg);
-               }
-
-               protected void configureShell(Shell shell) {
-                       super.configureShell(shell);
-                       shell.setText(title);
-               }
-       }
-
-       private LdifUsersTable appendMemberOfPart(final Composite parent, User user) {
-               // Section section = addSection(tk, parent, "Roles");
-               // Composite body = (Composite) section.getClient();
-               // Composite body= parent;
-               Composite body = new Composite(parent, SWT.BORDER);
-               body.setLayout(new GridLayout());
-               body.setLayoutData(CmsSwtUtils.fillAll());
-
-               // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN);
-
-               // Displayed columns
-               List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-               columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24));
-               columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150));
-               columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100));
-               // Only show technical DN to administrators
-               // if (isAdmin)
-               // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name",
-               // 300));
-
-               // Create and configure the table
-               final LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, user);
-
-               userViewerCmp.setColumnDefinitions(columnDefs);
-               // if (isAdmin)
-               // userViewerCmp.populateWithStaticFilters(false, false);
-               // else
-               userViewerCmp.populate(true, false);
-               GridData gd = EclipseUiUtils.fillAll();
-               gd.heightHint = 500;
-               userViewerCmp.setLayoutData(gd);
-
-               // Controllers
-               TableViewer userViewer = userViewerCmp.getTableViewer();
-               userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService));
-               int operations = DND.DROP_COPY | DND.DROP_MOVE;
-               Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
-               GroupDropListener dropL = new GroupDropListener(userAdminWrapper, userViewer, user);
-               userViewer.addDropSupport(operations, tt, dropL);
-
-               AbstractFormPart part = new AbstractFormPart() {
-
-                       private GroupChangeListener listener;
-
-                       @Override
-                       public void initialize(IManagedForm form) {
-                               super.initialize(form);
-                               listener = new GroupChangeListener(parent.getDisplay(), this);
-                               userAdminWrapper.addListener(listener);
-                       }
-
-                       public void commit(boolean onSave) {
-                               super.commit(onSave);
-                       }
-
-                       @Override
-                       public void dispose() {
-                               userAdminWrapper.removeListener(listener);
-                               super.dispose();
-                       }
-
-                       @Override
-                       public void refresh() {
-                               userViewerCmp.refresh();
-                               super.refresh();
-                       }
-               };
-               getManagedForm().addPart(part);
-               // addRemoveAbitily(body, userViewer, user);
-               // userViewerCmp.refresh();
-               String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + " from the below selected groups";
-               Action action = new RemoveMembershipAction(userViewer, user, tooltip, SecurityAdminImages.ICON_REMOVE_DESC);
-               ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
-               ToolBar toolBar = toolBarManager.createControl(body);
-               toolBar.setLayoutData(CmsSwtUtils.fillWidth());
-               toolBarManager.add(action);
-               toolBarManager.update(true);
-               return userViewerCmp;
-       }
-
-       private class MyUserTableViewer extends LdifUsersTable {
-               private static final long serialVersionUID = 2653790051461237329L;
-
-               private Button showSystemRoleBtn;
-
-               private final User user;
-               private final UserFilter userFilter;
-
-               public MyUserTableViewer(Composite parent, int style, User user) {
-                       super(parent, style, true);
-                       this.user = user;
-                       userFilter = new UserFilter();
-               }
-
-               protected void populateStaticFilters(Composite staticFilterCmp) {
-                       staticFilterCmp.setLayout(new GridLayout());
-                       showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK);
-                       showSystemRoleBtn.setText("Show system roles");
-                       boolean showSysRole = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN);
-                       showSystemRoleBtn.setSelection(showSysRole);
-                       userFilter.setShowSystemRole(showSysRole);
-                       showSystemRoleBtn.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = -7033424592697691676L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       userFilter.setShowSystemRole(showSystemRoleBtn.getSelection());
-                                       refresh();
-                               }
-                       });
-               }
-
-               @Override
-               protected List<User> listFilteredElements(String filter) {
-                       List<User> users = (List<User>) getFlatGroups(null);
-                       List<User> filteredUsers = new ArrayList<User>();
-                       if (users.contains(user))
-                               users.remove(user);
-                       userFilter.setSearchText(filter);
-                       for (User user : users)
-                               if (userFilter.select(null, null, user))
-                                       filteredUsers.add(user);
-                       return filteredUsers;
-               }
-       }
-
-       // private void addRemoveAbility(Composite parent, TableViewer userViewer, User
-       // user) {
-       // // Section section = sectionPart.getSection();
-       // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
-       // ToolBar toolbar = toolBarManager.createControl(parent);
-       // final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND);
-       // toolbar.setCursor(handCursor);
-       // toolbar.addDisposeListener(new DisposeListener() {
-       // private static final long serialVersionUID = 3882131405820522925L;
-       //
-       // public void widgetDisposed(DisposeEvent e) {
-       // if ((handCursor != null) && (handCursor.isDisposed() == false)) {
-       // handCursor.dispose();
-       // }
-       // }
-       // });
-       //
-       // String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) +
-       // " from the below selected groups";
-       // Action action = new RemoveMembershipAction(userViewer, user, tooltip,
-       // SecurityAdminImages.ICON_REMOVE_DESC);
-       // toolBarManager.add(action);
-       // toolBarManager.update(true);
-       // // section.setTextClient(toolbar);
-       // }
-
-       private class RemoveMembershipAction extends Action {
-               private static final long serialVersionUID = -1337713097184522588L;
-
-               private final TableViewer userViewer;
-               private final User user;
-
-               RemoveMembershipAction(TableViewer userViewer, User user, String name, ImageDescriptor img) {
-                       super(name, img);
-                       this.userViewer = userViewer;
-                       this.user = user;
-               }
-
-               @Override
-               public void run() {
-                       ISelection selection = userViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-
-                       @SuppressWarnings("unchecked")
-                       Iterator<Group> it = ((IStructuredSelection) selection).iterator();
-                       List<Group> groups = new ArrayList<Group>();
-                       while (it.hasNext()) {
-                               Group currGroup = it.next();
-                               groups.add(currGroup);
-                       }
-
-                       userAdminWrapper.beginTransactionIfNeeded();
-                       for (Group group : groups) {
-                               group.removeMember(user);
-                       }
-                       userAdminWrapper.commitOrNotifyTransactionStateChange();
-                       for (Group group : groups) {
-                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group));
-                       }
-               }
-       }
-
-       /**
-        * Defines the table as being a potential target to add group memberships
-        * (roles) to this user
-        */
-       private class GroupDropListener extends ViewerDropAdapter {
-               private static final long serialVersionUID = 2893468717831451621L;
-
-               private final UserAdminWrapper myUserAdminWrapper;
-               private final User myUser;
-
-               public GroupDropListener(UserAdminWrapper userAdminWrapper, Viewer userViewer, User user) {
-                       super(userViewer);
-                       this.myUserAdminWrapper = userAdminWrapper;
-                       this.myUser = user;
-               }
-
-               @Override
-               public boolean validateDrop(Object target, int operation, TransferData transferType) {
-                       // Target is always OK in a list only view
-                       // TODO check if not a string
-                       boolean validDrop = true;
-                       return validDrop;
-               }
-
-               @Override
-               public void drop(DropTargetEvent event) {
-                       String name = (String) event.data;
-                       UserAdmin myUserAdmin = myUserAdminWrapper.getUserAdmin();
-                       Role role = myUserAdmin.getRole(name);
-                       // TODO this check should be done before.
-                       if (role.getType() == Role.GROUP) {
-                               // TODO check if the user is already member of this group
-
-                               myUserAdminWrapper.beginTransactionIfNeeded();
-                               Group group = (Group) role;
-                               group.addMember(myUser);
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               myUserAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group));
-                       }
-                       super.drop(event);
-               }
-
-               @Override
-               public boolean performDrop(Object data) {
-                       // userTableViewerCmp.refresh();
-                       return true;
-               }
-       }
-
-       // LOCAL HELPERS
-       private void refreshFormTitle(User group) {
-               // getManagedForm().getForm().setText(UserAdminUtils.getProperty(group,
-               // LdapAttrs.cn.name()));
-       }
-
-       /** Appends a section with a title */
-       // private Section addSection(FormToolkit tk, Composite parent, String title) {
-       // Section section = tk.createSection(parent, Section.TITLE_BAR);
-       // GridData gd = EclipseUiUtils.fillWidth();
-       // gd.verticalAlignment = PRE_TITLE_INDENT;
-       // section.setLayoutData(gd);
-       // section.setText(title);
-       // // section.getMenu().setVisible(true);
-       //
-       // Composite body = tk.createComposite(section, SWT.WRAP);
-       // body.setLayoutData(EclipseUiUtils.fillAll());
-       // section.setClient(body);
-       //
-       // return section;
-       // }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java
deleted file mode 100644 (file)
index c6d024e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import org.argeo.cms.e4.CmsE4Utils;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.User;
-
-/**
- * Default double click listener for the various user tables, will open the
- * clicked item in the editor
- */
-public class UserTableDefaultDClickListener implements IDoubleClickListener {
-       private final EPartService partService;
-
-       public UserTableDefaultDClickListener(EPartService partService) {
-               this.partService = partService;
-       }
-
-       public void doubleClick(DoubleClickEvent evt) {
-               if (evt.getSelection().isEmpty())
-                       return;
-               Object obj = ((IStructuredSelection) evt.getSelection()).getFirstElement();
-               User user = (User) obj;
-
-               String editorId = getEditorId(user);
-               CmsE4Utils.openEditor(partService, editorId, LdapAttrs.uid.name(), user.getName());
-       }
-
-       protected String getEditorId(User user) {
-               if (user instanceof Group)
-                       return "org.argeo.cms.e4.partdescriptor.groupEditor";
-               else
-                       return "org.argeo.cms.e4.partdescriptor.userEditor";
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java
deleted file mode 100644 (file)
index 877a925..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import javax.inject.Inject;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.e4.users.providers.CommonNameLP;
-import org.argeo.cms.e4.users.providers.DomainNameLP;
-import org.argeo.cms.e4.users.providers.MailLP;
-import org.argeo.cms.e4.users.providers.UserDragListener;
-import org.argeo.cms.e4.users.providers.UserNameLP;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.e4.ui.di.Focus;
-import org.eclipse.e4.ui.workbench.modeling.EPartService;
-import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.TextTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-import org.osgi.service.useradmin.UserAdminListener;
-
-/** List all users with filter - based on Ldif userAdmin */
-public class UsersView {
-       // private final static Log log = LogFactory.getLog(UsersView.class);
-
-       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView";
-
-       @Inject
-       private UserAdminWrapper userAdminWrapper;
-       @Inject
-       private EPartService partService;
-
-       // UI Objects
-       private LdifUsersTable userTableViewerCmp;
-       private TableViewer userViewer;
-       private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-
-       private UserAdminListener listener;
-
-       @PostConstruct
-       public void createPartControl(Composite parent, ESelectionService selectionService) {
-
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               // Define the displayed columns
-               columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150));
-               columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150));
-               columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200));
-               // Only show technical DN to admin
-               if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN))
-                       columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300));
-
-               // Create and configure the table
-               userTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
-               userTableViewerCmp.setLayoutData(EclipseUiUtils.fillAll());
-               userTableViewerCmp.setColumnDefinitions(columnDefs);
-               userTableViewerCmp.populate(true, false);
-
-               // Links
-               userViewer = userTableViewerCmp.getTableViewer();
-               userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService));
-               userViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                       @Override
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) event.getSelection();
-                               selectionService.setSelection(selection.toList());
-                       }
-               });
-               // getViewSite().setSelectionProvider(userViewer);
-
-               // Really?
-               userTableViewerCmp.refresh();
-
-               // Drag and drop
-               int operations = DND.DROP_COPY | DND.DROP_MOVE;
-               Transfer[] tt = new Transfer[] { TextTransfer.getInstance() };
-               userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer));
-
-               // Register a useradmin listener
-               listener = new MyUiUAListener(parent.getDisplay());
-               userAdminWrapper.addListener(listener);
-       }
-
-       private class MyUiUAListener extends UiUserAdminListener {
-               public MyUiUAListener(Display display) {
-                       super(display);
-               }
-
-               @Override
-               public void roleChangedToUiThread(UserAdminEvent event) {
-                       if (userViewer != null && !userViewer.getTable().isDisposed())
-                               refresh();
-               }
-       }
-
-       private class MyUserTableViewer extends LdifUsersTable {
-               private static final long serialVersionUID = 8467999509931900367L;
-
-               private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.uid.name(), LdapAttrs.cn.name(),
-                               LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() };
-
-               public MyUserTableViewer(Composite parent, int style) {
-                       super(parent, style);
-               }
-
-               @Override
-               protected List<User> listFilteredElements(String filter) {
-                       Role[] roles;
-
-                       try {
-                               StringBuilder builder = new StringBuilder();
-
-                               StringBuilder tmpBuilder = new StringBuilder();
-                               if (EclipseUiUtils.notEmpty(filter))
-                                       for (String prop : knownProps) {
-                                               tmpBuilder.append("(");
-                                               tmpBuilder.append(prop);
-                                               tmpBuilder.append("=*");
-                                               tmpBuilder.append(filter);
-                                               tmpBuilder.append("*)");
-                                       }
-                               if (tmpBuilder.length() > 1) {
-                                       builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                       .append(LdapObjs.inetOrgPerson.name()).append(")(|");
-                                       builder.append(tmpBuilder.toString());
-                                       builder.append("))");
-                               } else
-                                       builder.append("(").append(LdapAttrs.objectClass.name()).append("=")
-                                                       .append(LdapObjs.inetOrgPerson.name()).append(")");
-                               roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString());
-                       } catch (InvalidSyntaxException e) {
-                               throw new CmsException("Unable to get roles with filter: " + filter, e);
-                       }
-                       List<User> users = new ArrayList<User>();
-                       for (Role role : roles)
-                               // if (role.getType() == Role.USER && role.getType() !=
-                               // Role.GROUP)
-                               users.add((User) role);
-                       return users;
-               }
-       }
-
-       public void refresh() {
-               userTableViewerCmp.refresh();
-       }
-
-       // Override generic view methods
-       @PreDestroy
-       public void dispose() {
-               userAdminWrapper.removeListener(listener);
-       }
-
-       @Focus
-       public void setFocus() {
-               userTableViewerCmp.setFocus();
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java
deleted file mode 100644 (file)
index 742bc3f..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java
deleted file mode 100644 (file)
index d1afd22..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java
deleted file mode 100644 (file)
index d2ffa79..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-package org.argeo.cms.e4.users.handlers;
-
-import java.util.Dictionary;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.jface.wizard.Wizard;
-import org.eclipse.jface.wizard.WizardDialog;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Create a new group */
-public class NewGroup {
-       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newGroup";
-
-       /* DEPENDENCY INJECTION */
-       @Inject
-       private UserAdminWrapper userAdminWrapper;
-
-       @Execute
-       public Object execute() {
-               NewGroupWizard newGroupWizard = new NewGroupWizard();
-               newGroupWizard.setWindowTitle("Group creation");
-               WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newGroupWizard);
-               dialog.open();
-               return null;
-       }
-
-       private class NewGroupWizard extends Wizard {
-
-               // Pages
-               private MainGroupInfoWizardPage mainGroupInfo;
-
-               // UI fields
-               private Text dNameTxt, commonNameTxt, descriptionTxt;
-               private Combo baseDnCmb;
-
-               public NewGroupWizard() {
-               }
-
-               @Override
-               public void addPages() {
-                       mainGroupInfo = new MainGroupInfoWizardPage();
-                       addPage(mainGroupInfo);
-               }
-
-               @SuppressWarnings({ "rawtypes", "unchecked" })
-               @Override
-               public boolean performFinish() {
-                       if (!canFinish())
-                               return false;
-                       String commonName = commonNameTxt.getText();
-                       try {
-                               userAdminWrapper.beginTransactionIfNeeded();
-                               String dn = getDn(commonName);
-                               Group group = (Group) userAdminWrapper.getUserAdmin().createRole(dn, Role.GROUP);
-                               Dictionary props = group.getProperties();
-                               String descStr = descriptionTxt.getText();
-                               if (EclipseUiUtils.notEmpty(descStr))
-                                       props.put(LdapAttrs.description.name(), descStr);
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group));
-                               return true;
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Cannot create new group " + commonName, e);
-                               return false;
-                       }
-               }
-
-               private class MainGroupInfoWizardPage extends WizardPage implements FocusListener {
-                       private static final long serialVersionUID = -3150193365151601807L;
-
-                       public MainGroupInfoWizardPage() {
-                               super("Main");
-                               setTitle("General information");
-                               setMessage("Please choose a domain, provide a common name " + "and a free description");
-                       }
-
-                       @Override
-                       public void createControl(Composite parent) {
-                               Composite bodyCmp = new Composite(parent, SWT.NONE);
-                               setControl(bodyCmp);
-                               bodyCmp.setLayout(new GridLayout(2, false));
-
-                               dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name");
-                               dNameTxt.setEnabled(false);
-
-                               baseDnCmb = createGridLC(bodyCmp, "Base DN");
-                               // Initialise before adding the listener to avoid NPE
-                               initialiseDnCmb(baseDnCmb);
-                               baseDnCmb.addFocusListener(this);
-
-                               commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name");
-                               commonNameTxt.addFocusListener(this);
-
-                               Label descLbl = new Label(bodyCmp, SWT.LEAD);
-                               descLbl.setText("Description");
-                               descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
-                               descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER);
-                               descriptionTxt.setLayoutData(EclipseUiUtils.fillAll());
-                               descriptionTxt.addFocusListener(this);
-
-                               // Initialize buttons
-                               setPageComplete(false);
-                               getContainer().updateButtons();
-                       }
-
-                       @Override
-                       public void focusLost(FocusEvent event) {
-                               String name = commonNameTxt.getText();
-                               if (EclipseUiUtils.isEmpty(name))
-                                       dNameTxt.setText("");
-                               else
-                                       dNameTxt.setText(getDn(name));
-
-                               String message = checkComplete();
-                               if (message != null) {
-                                       setMessage(message, WizardPage.ERROR);
-                                       setPageComplete(false);
-                               } else {
-                                       setMessage("Complete", WizardPage.INFORMATION);
-                                       setPageComplete(true);
-                               }
-                               getContainer().updateButtons();
-                       }
-
-                       @Override
-                       public void focusGained(FocusEvent event) {
-                       }
-
-                       /** @return the error message or null if complete */
-                       protected String checkComplete() {
-                               String name = commonNameTxt.getText();
-
-                               if (name.trim().equals(""))
-                                       return "Common name must not be empty";
-                               Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name));
-                               if (role != null)
-                                       return "Group " + name + " already exists";
-                               return null;
-                       }
-
-                       @Override
-                       public void setVisible(boolean visible) {
-                               super.setVisible(visible);
-                               if (visible)
-                                       if (baseDnCmb.getSelectionIndex() == -1)
-                                               baseDnCmb.setFocus();
-                                       else
-                                               commonNameTxt.setFocus();
-                       }
-               }
-
-               private Map<String, String> getDns() {
-                       return userAdminWrapper.getKnownBaseDns(true);
-               }
-
-               private String getDn(String cn) {
-                       Map<String, String> dns = getDns();
-                       String bdn = baseDnCmb.getText();
-                       if (EclipseUiUtils.notEmpty(bdn)) {
-                               Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(bdn));
-                               String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.groupBase.getValue(props) + "," + bdn;
-                               return dn;
-                       }
-                       return null;
-               }
-
-               private void initialiseDnCmb(Combo combo) {
-                       Map<String, String> dns = userAdminWrapper.getKnownBaseDns(true);
-                       if (dns.isEmpty())
-                               throw new CmsException("No writable base dn found. Cannot create group");
-                       combo.setItems(dns.keySet().toArray(new String[0]));
-                       if (dns.size() == 1)
-                               combo.select(0);
-               }
-       }
-
-       private Combo createGridLC(Composite parent, String label) {
-               Label lbl = new Label(parent, SWT.LEAD);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
-               Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY);
-               combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               return combo;
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java
deleted file mode 100644 (file)
index 07d82c7..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.argeo.cms.e4.users.handlers;
-
-import java.util.Dictionary;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.UiAdminUtils;
-import org.argeo.cms.e4.users.UserAdminWrapper;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.e4.core.di.annotations.Execute;
-import org.eclipse.jface.wizard.Wizard;
-import org.eclipse.jface.wizard.WizardDialog;
-import org.eclipse.jface.wizard.WizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdminEvent;
-
-/** Open a wizard that enables creation of a new user. */
-public class NewUser {
-       // private final static Log log = LogFactory.getLog(NewUser.class);
-       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newUser";
-
-       /* DEPENDENCY INJECTION */
-       @Inject
-       private UserAdminWrapper userAdminWrapper;
-
-       @Execute
-       public Object execute() {
-               NewUserWizard newUserWizard = new NewUserWizard();
-               newUserWizard.setWindowTitle("User creation");
-               WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newUserWizard);
-               dialog.open();
-               return null;
-       }
-
-       private class NewUserWizard extends Wizard {
-
-               // pages
-               private MainUserInfoWizardPage mainUserInfo;
-
-               // End user fields
-               private Text dNameTxt, usernameTxt, firstNameTxt, lastNameTxt, primaryMailTxt, pwd1Txt, pwd2Txt;
-               private Combo baseDnCmb;
-
-               public NewUserWizard() {
-
-               }
-
-               @Override
-               public void addPages() {
-                       mainUserInfo = new MainUserInfoWizardPage();
-                       addPage(mainUserInfo);
-                       String message = "Default wizard that also eases user creation tests:\n "
-                                       + "Mail and last name are automatically "
-                                       + "generated form the uid. Password are defauted to 'demo'.";
-                       mainUserInfo.setMessage(message, WizardPage.WARNING);
-               }
-
-               @SuppressWarnings({ "rawtypes", "unchecked" })
-               @Override
-               public boolean performFinish() {
-                       if (!canFinish())
-                               return false;
-                       String username = mainUserInfo.getUsername();
-                       userAdminWrapper.beginTransactionIfNeeded();
-                       try {
-                               User user = (User) userAdminWrapper.getUserAdmin().createRole(getDn(username), Role.USER);
-
-                               Dictionary props = user.getProperties();
-
-                               String lastNameStr = lastNameTxt.getText();
-                               if (EclipseUiUtils.notEmpty(lastNameStr))
-                                       props.put(LdapAttrs.sn.name(), lastNameStr);
-
-                               String firstNameStr = firstNameTxt.getText();
-                               if (EclipseUiUtils.notEmpty(firstNameStr))
-                                       props.put(LdapAttrs.givenName.name(), firstNameStr);
-
-                               String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr);
-                               if (EclipseUiUtils.notEmpty(cn))
-                                       props.put(LdapAttrs.cn.name(), cn);
-
-                               String mailStr = primaryMailTxt.getText();
-                               if (EclipseUiUtils.notEmpty(mailStr))
-                                       props.put(LdapAttrs.mail.name(), mailStr);
-
-                               char[] password = mainUserInfo.getPassword();
-                               user.getCredentials().put(null, password);
-                               userAdminWrapper.commitOrNotifyTransactionStateChange();
-                               userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user));
-                               return true;
-                       } catch (Exception e) {
-                               ErrorFeedback.show("Cannot create new user " + username, e);
-                               return false;
-                       }
-               }
-
-               private class MainUserInfoWizardPage extends WizardPage implements ModifyListener {
-                       private static final long serialVersionUID = -3150193365151601807L;
-
-                       public MainUserInfoWizardPage() {
-                               super("Main");
-                               setTitle("Required Information");
-                       }
-
-                       @Override
-                       public void createControl(Composite parent) {
-                               Composite composite = new Composite(parent, SWT.NONE);
-                               composite.setLayout(new GridLayout(2, false));
-                               dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this);
-                               dNameTxt.setEnabled(false);
-
-                               baseDnCmb = createGridLC(composite, "Base DN");
-                               initialiseDnCmb(baseDnCmb);
-                               baseDnCmb.addModifyListener(this);
-                               baseDnCmb.addModifyListener(new ModifyListener() {
-                                       private static final long serialVersionUID = -1435351236582736843L;
-
-                                       @Override
-                                       public void modifyText(ModifyEvent event) {
-                                               String name = usernameTxt.getText();
-                                               dNameTxt.setText(getDn(name));
-                                       }
-                               });
-
-                               usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this);
-                               usernameTxt.addModifyListener(new ModifyListener() {
-                                       private static final long serialVersionUID = -1435351236582736843L;
-
-                                       @Override
-                                       public void modifyText(ModifyEvent event) {
-                                               String name = usernameTxt.getText();
-                                               if (name.trim().equals("")) {
-                                                       dNameTxt.setText("");
-                                                       lastNameTxt.setText("");
-                                                       primaryMailTxt.setText("");
-                                                       pwd1Txt.setText("");
-                                                       pwd2Txt.setText("");
-                                               } else {
-                                                       dNameTxt.setText(getDn(name));
-                                                       lastNameTxt.setText(name.toUpperCase());
-                                                       primaryMailTxt.setText(getMail(name));
-                                                       pwd1Txt.setText("demo");
-                                                       pwd2Txt.setText("demo");
-                                               }
-                                       }
-                               });
-
-                               primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this);
-                               firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this);
-                               lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this);
-                               pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this);
-                               pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this);
-                               setControl(composite);
-
-                               // Initialize buttons
-                               setPageComplete(false);
-                               getContainer().updateButtons();
-                       }
-
-                       @Override
-                       public void modifyText(ModifyEvent event) {
-                               String message = checkComplete();
-                               if (message != null) {
-                                       setMessage(message, WizardPage.ERROR);
-                                       setPageComplete(false);
-                               } else {
-                                       setMessage("Complete", WizardPage.INFORMATION);
-                                       setPageComplete(true);
-                               }
-                               getContainer().updateButtons();
-                       }
-
-                       /** @return error message or null if complete */
-                       protected String checkComplete() {
-                               String name = usernameTxt.getText();
-
-                               if (name.trim().equals(""))
-                                       return "User name must not be empty";
-                               Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name));
-                               if (role != null)
-                                       return "User " + name + " already exists";
-                               if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN))
-                                       return "Not a valid email address";
-                               if (lastNameTxt.getText().trim().equals(""))
-                                       return "Specify a last name";
-                               if (pwd1Txt.getText().trim().equals(""))
-                                       return "Specify a password";
-                               if (pwd2Txt.getText().trim().equals(""))
-                                       return "Repeat the password";
-                               if (!pwd2Txt.getText().equals(pwd1Txt.getText()))
-                                       return "Passwords are different";
-                               return null;
-                       }
-
-                       @Override
-                       public void setVisible(boolean visible) {
-                               super.setVisible(visible);
-                               if (visible)
-                                       if (baseDnCmb.getSelectionIndex() == -1)
-                                               baseDnCmb.setFocus();
-                                       else
-                                               usernameTxt.setFocus();
-                       }
-
-                       public String getUsername() {
-                               return usernameTxt.getText();
-                       }
-
-                       public char[] getPassword() {
-                               return pwd1Txt.getTextChars();
-                       }
-
-               }
-
-               private Map<String, String> getDns() {
-                       return userAdminWrapper.getKnownBaseDns(true);
-               }
-
-               private String getDn(String uid) {
-                       Map<String, String> dns = getDns();
-                       String bdn = baseDnCmb.getText();
-                       if (EclipseUiUtils.notEmpty(bdn)) {
-                               Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(bdn));
-                               String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.userBase.getValue(props) + "," + bdn;
-                               return dn;
-                       }
-                       return null;
-               }
-
-               private void initialiseDnCmb(Combo combo) {
-                       Map<String, String> dns = userAdminWrapper.getKnownBaseDns(true);
-                       if (dns.isEmpty())
-                               throw new CmsException("No writable base dn found. Cannot create user");
-                       combo.setItems(dns.keySet().toArray(new String[0]));
-                       if (dns.size() == 1)
-                               combo.select(0);
-               }
-
-               private String getMail(String username) {
-                       if (baseDnCmb.getSelectionIndex() == -1)
-                               return null;
-                       String baseDn = baseDnCmb.getText();
-                       try {
-                               LdapName name = new LdapName(baseDn);
-                               List<Rdn> rdns = name.getRdns();
-                               return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue();
-                       } catch (InvalidNameException e) {
-                               throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e);
-                       }
-               }
-       }
-
-       private Combo createGridLC(Composite parent, String label) {
-               Label lbl = new Label(parent, SWT.LEAD);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
-               Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY);
-               combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               return combo;
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
-               this.userAdminWrapper = userAdminWrapper;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java
deleted file mode 100644 (file)
index cf3db1d..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Users management handlers. */
-package org.argeo.cms.e4.users.handlers;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java
deleted file mode 100644 (file)
index c6f14b0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Users management perspective. */
-package org.argeo.cms.e4.users;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java
deleted file mode 100644 (file)
index 2d8db67..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.service.useradmin.User;
-
-/** Simply declare a label provider that returns the common name of a user */
-public class CommonNameLP extends UserAdminAbstractLP {
-       private static final long serialVersionUID = 5256703081044911941L;
-
-       @Override
-       public String getText(User user) {
-               return UserAdminUtils.getProperty(user, LdapAttrs.cn.name());
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               return UserAdminUtils.getProperty((User) element, LdapAttrs.DN);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java
deleted file mode 100644 (file)
index e23729d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java
deleted file mode 100644 (file)
index 52d3b85..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.service.useradmin.User;
-
-/** Simply declare a label provider that returns the Primary Mail of a user */
-public class MailLP extends UserAdminAbstractLP {
-       private static final long serialVersionUID = 8329764452141982707L;
-
-       @Override
-       public String getText(User user) {
-               return UserAdminUtils.getProperty(user, LdapAttrs.mail.name());
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java
deleted file mode 100644 (file)
index 8c94093..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.SecurityAdminImages;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.swt.graphics.Image;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** Provide a bundle specific image depending on the current user type */
-public class RoleIconLP extends UserAdminAbstractLP {
-       private static final long serialVersionUID = 6550449442061090388L;
-
-       @Override
-       public String getText(User user) {
-               return "";
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               User user = (User) element;
-               String dn = user.getName();
-               if (dn.endsWith(CmsConstants.ROLES_BASEDN))
-                       return SecurityAdminImages.ICON_ROLE;
-               else if (user.getType() == Role.GROUP) {
-                       String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory);
-                       if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP))
-                               return SecurityAdminImages.ICON_WORKGROUP;
-                       return SecurityAdminImages.ICON_GROUP;
-               } else
-                       return SecurityAdminImages.ICON_USER;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java
deleted file mode 100644 (file)
index e33b153..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.service.useradmin.User;
-
-/**
- * Utility class that add font modifications to a column label provider
- * depending on the given user properties
- */
-public abstract class UserAdminAbstractLP extends ColumnLabelProvider {
-       private static final long serialVersionUID = 137336765024922368L;
-
-       // private Font italic;
-       private Font bold;
-
-       @Override
-       public Font getFont(Object element) {
-               // Self as bold
-               try {
-                       LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName();
-                       String userName = ((User) element).getName();
-                       LdapName userLdapName = new LdapName(userName);
-                       if (userLdapName.equals(selfUserName)) {
-                               if (bold == null)
-                                       bold = JFaceResources.getFontRegistry()
-                                                       .defaultFontDescriptor().setStyle(SWT.BOLD)
-                                                       .createFont(Display.getCurrent());
-                               return bold;
-                       }
-               } catch (InvalidNameException e) {
-                       throw new CmsException("cannot parse dn for " + element, e);
-               }
-
-               // Disabled as Italic
-               // Node userProfile = (Node) elem;
-               // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean())
-               // return italic;
-
-               return null;
-               // return super.getFont(element);
-       }
-
-       @Override
-       public String getText(Object element) {
-               User user = (User) element;
-               return getText(user);
-       }
-
-       public void setDisplay(Display display) {
-               // italic = JFaceResources.getFontRegistry().defaultFontDescriptor()
-               // .setStyle(SWT.ITALIC).createFont(display);
-               bold = JFaceResources.getFontRegistry().defaultFontDescriptor()
-                               .setStyle(SWT.BOLD).createFont(Display.getCurrent());
-       }
-
-       public abstract String getText(User user);
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java
deleted file mode 100644 (file)
index 56a2624..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java
deleted file mode 100644 (file)
index 154b047..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerFilter;
-import org.osgi.service.useradmin.User;
-
-/**
- * Filter user list using JFace mechanism on the client (yet on the server) side
- * rather than having the UserAdmin to process the search
- */
-public class UserFilter extends ViewerFilter {
-       private static final long serialVersionUID = 5082509381672880568L;
-
-       private String searchString;
-       private boolean showSystemRole = true;
-
-       private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(),
-                       LdapAttrs.sn.name(), LdapAttrs.uid.name(), LdapAttrs.description.name(), LdapAttrs.mail.name() };
-
-       public void setSearchText(String s) {
-               // ensure that the value can be used for matching
-               if (notEmpty(s))
-                       searchString = ".*" + s.toLowerCase() + ".*";
-               else
-                       searchString = ".*";
-       }
-
-       public void setShowSystemRole(boolean showSystemRole) {
-               this.showSystemRole = showSystemRole;
-       }
-
-       @Override
-       public boolean select(Viewer viewer, Object parentElement, Object element) {
-               User user = (User) element;
-               if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.ROLES_BASEDN + ")"))
-                       // UserAdminUtils.getProperty(user, LdifName.dn.name())
-                       // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN))
-                       return false;
-
-               if (searchString == null || searchString.length() == 0)
-                       return true;
-
-               if (user.getName().matches(searchString))
-                       return true;
-
-               for (String key : knownProps) {
-                       String currVal = UserAdminUtils.getProperty(user, key);
-                       if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString))
-                               return true;
-               }
-               return false;
-       }
-}
diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java
deleted file mode 100644 (file)
index 3cd00eb..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-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/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java
deleted file mode 100644 (file)
index 33bef8d..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Users management content providers. */
-package org.argeo.cms.e4.users.providers;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.servlet/.classpath b/eclipse/org.argeo.cms.servlet/.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/eclipse/org.argeo.cms.servlet/.project b/eclipse/org.argeo.cms.servlet/.project
deleted file mode 100644 (file)
index d39f974..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.servlet</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml
deleted file mode 100644 (file)
index c007351..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="Jetty Service Factory">
-   <implementation class="org.argeo.cms.servlet.internal.jetty.JettyServiceFactory"/>
-   <service>
-      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
-   </service>
-   <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml
deleted file mode 100644 (file)
index 00fcaff..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
-   <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
-   <service>
-      <provide interface="javax.servlet.Servlet"/>
-   </service>
-   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
-   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml
deleted file mode 100644 (file)
index 7540a2c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
-   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
-   <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
-   </service>
-   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
-   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
-</scr:component>
diff --git a/eclipse/org.argeo.cms.servlet/bnd.bnd b/eclipse/org.argeo.cms.servlet/bnd.bnd
deleted file mode 100644 (file)
index 7c537ba..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-Import-Package:\
-org.osgi.service.http;version=0.0.0,\
-org.osgi.service.http.whiteboard;version=0.0.0,\
-org.osgi.framework.namespace;version=0.0.0,\
-org.argeo.cms.osgi,\
-javax.servlet.*;version="[3,5)",\
-*
-
-Service-Component:\
-OSGI-INF/jettyServiceFactory.xml,\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml
diff --git a/eclipse/org.argeo.cms.servlet/build.properties b/eclipse/org.argeo.cms.servlet/build.properties
deleted file mode 100644 (file)
index ee94f53..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/jettyServiceFactory.xml
-source.. = src/
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java
deleted file mode 100644 (file)
index 1ae6286..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.PrivilegedAction;
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.internal.HttpUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.context.ServletContextHelper;
-
-/**
- * Default servlet context degrading to anonymous if the the session is not
- * pre-authenticated.
- */
-public class CmsServletContext extends ServletContextHelper {
-       private final static CmsLog log = CmsLog.getLog(CmsServletContext.class);
-       // use CMS bundle for resources
-       private Bundle bundle = FrameworkUtil.getBundle(getClass());
-
-       public void init(Map<String, String> properties) {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       @Override
-       public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
-               if (log.isTraceEnabled())
-                       HttpUtils.logRequestHeaders(log, request);
-               LoginContext lc;
-               try {
-                       lc = CmsAuth.USER.newLoginContext(
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
-                       lc.login();
-               } catch (LoginException e) {
-                       lc = processUnauthorized(request, response);
-                       if (log.isTraceEnabled())
-                               HttpUtils.logResponseHeaders(log, response);
-                       if (lc == null)
-                               return false;
-               }
-
-               Subject subject = lc.getSubject();
-               // log.debug("SERVLET CONTEXT: "+subject);
-               Subject.doAs(subject, new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
-                               return null;
-                       }
-
-               });
-               return true;
-       }
-
-       @Override
-       public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
-               RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
-       }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               // anonymous
-               try {
-                       LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
-                       lc.login();
-                       return lc;
-               } catch (LoginException e1) {
-                       if (log.isDebugEnabled())
-                               log.error("Cannot log in as anonymous", e1);
-                       return null;
-               }
-       }
-
-       @Override
-       public URL getResource(String name) {
-               // TODO make it more robust and versatile
-               // if used directly it can only load from within this bundle
-               return bundle.getResource(name);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
deleted file mode 100644 (file)
index 3bea0b4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.cms.servlet;
-
-import javax.security.auth.login.LoginContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.SpnegoLoginModule;
-import org.argeo.cms.servlet.internal.HttpUtils;
-
-/** Servlet context forcing authentication. */
-public class PrivateWwwAuthServletContext extends CmsServletContext {
-       // TODO make it configurable
-       private final String httpAuthRealm = "Argeo";
-       private final boolean forceBasic = false;
-
-       @Override
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               askForWwwAuth(request, response);
-               return null;
-       }
-
-       protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
-               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
-               // realm=\"" + httpAuthRealm + "\"");
-               if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
-                       response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate");
-               else
-                       response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\"");
-
-               // response.setDateHeader("Date", System.currentTimeMillis());
-               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
-               // 60 * 60 * 1000));
-               // response.setHeader("Accept-Ranges", "bytes");
-               // response.setHeader("Connection", "Keep-Alive");
-               // response.setHeader("Keep-Alive", "timeout=5, max=97");
-               // response.setContentType("text/html; charset=UTF-8");
-               response.setStatus(401);
-       }
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java
deleted file mode 100644 (file)
index 54c8804..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.util.Locale;
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpRequest implements RemoteAuthRequest {
-       private final HttpServletRequest request;
-
-       public ServletHttpRequest(HttpServletRequest request) {
-               Objects.requireNonNull(request);
-               this.request = request;
-       }
-
-       @Override
-       public RemoteAuthSession getSession() {
-               HttpSession httpSession = request.getSession(false);
-               if (httpSession == null)
-                       return null;
-               return new ServletHttpSession(httpSession);
-       }
-
-       @Override
-       public RemoteAuthSession createSession() {
-               return new ServletHttpSession(request.getSession(true));
-       }
-
-       @Override
-       public Locale getLocale() {
-               return request.getLocale();
-       }
-
-       @Override
-       public Object getAttribute(String key) {
-               return request.getAttribute(key);
-       }
-
-       @Override
-       public void setAttribute(String key, Object object) {
-               request.setAttribute(key, object);
-       }
-
-       @Override
-       public String getHeader(String key) {
-               return request.getHeader(key);
-       }
-
-       @Override
-       public String getRemoteAddr() {
-               return request.getRemoteAddr();
-       }
-
-       @Override
-       public int getLocalPort() {
-               return request.getLocalPort();
-       }
-
-       @Override
-       public int getRemotePort() {
-               return request.getRemotePort();
-       }
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java
deleted file mode 100644 (file)
index de47365..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.RemoteAuthResponse;
-
-public class ServletHttpResponse implements RemoteAuthResponse {
-       private final HttpServletResponse response;
-
-       public ServletHttpResponse(HttpServletResponse response) {
-               Objects.requireNonNull(response);
-               this.response = response;
-       }
-
-       @Override
-       public void setHeader(String keys, String value) {
-               response.setHeader(keys, value);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java
deleted file mode 100644 (file)
index 8d087da..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.cms.servlet;
-
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpSession implements RemoteAuthSession {
-       private javax.servlet.http.HttpSession session;
-
-       public ServletHttpSession(javax.servlet.http.HttpSession session) {
-               super();
-               this.session = session;
-       }
-
-       @Override
-       public boolean isValid() {
-               try {// test http session
-                       session.getCreationTime();
-                       return true;
-               } catch (IllegalStateException ise) {
-                       return false;
-               }
-       }
-
-       @Override
-       public String getId() {
-               return session.getId();
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java
deleted file mode 100644 (file)
index 70f2cc6..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.cms.servlet.internal;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-public class HttpUtils {
-       public final static String HEADER_AUTHORIZATION = "Authorization";
-       public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-       static boolean isBrowser(String userAgent) {
-               return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
-                               || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
-                               || userAgent.contains("opera") || userAgent.contains("browser");
-       }
-
-       public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
-               if (!log.isDebugEnabled())
-                       return;
-               for (String headerName : response.getHeaderNames()) {
-                       Object headerValue = response.getHeader(headerName);
-                       log.debug(headerName + ": " + headerValue);
-               }
-       }
-
-       public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
-               if (!log.isDebugEnabled())
-                       return;
-               for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
-                       String headerName = headerNames.nextElement();
-                       Object headerValue = request.getHeader(headerName);
-                       log.debug(headerName + ": " + headerValue);
-               }
-               log.debug(request.getRequestURI() + "\n");
-       }
-
-       public static void logRequest(CmsLog log, HttpServletRequest request) {
-               log.debug("contextPath=" + request.getContextPath());
-               log.debug("servletPath=" + request.getServletPath());
-               log.debug("requestURI=" + request.getRequestURI());
-               log.debug("queryString=" + request.getQueryString());
-               StringBuilder buf = new StringBuilder();
-               // headers
-               Enumeration<String> en = request.getHeaderNames();
-               while (en.hasMoreElements()) {
-                       String header = en.nextElement();
-                       Enumeration<String> values = request.getHeaders(header);
-                       while (values.hasMoreElements())
-                               buf.append("  " + header + ": " + values.nextElement());
-                       buf.append('\n');
-               }
-
-               // attributed
-               Enumeration<String> an = request.getAttributeNames();
-               while (an.hasMoreElements()) {
-                       String attr = an.nextElement();
-                       Object value = request.getAttribute(attr);
-                       buf.append("  " + attr + ": " + value);
-                       buf.append('\n');
-               }
-               log.debug("\n" + buf);
-       }
-
-       private HttpUtils() {
-
-       }
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java
deleted file mode 100644 (file)
index c762b67..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.cms.servlet.internal;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Collection;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.osgi.PublishNamespace;
-import org.argeo.osgi.util.FilterRequirement;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
-import org.osgi.framework.VersionRange;
-import org.osgi.framework.namespace.PackageNamespace;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.framework.wiring.FrameworkWiring;
-import org.osgi.resource.Requirement;
-
-public class PkgServlet extends HttpServlet {
-       private static final long serialVersionUID = 7660824185145214324L;
-
-       private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
-
-       @Override
-       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-               String pathInfo = req.getPathInfo();
-
-               String pkg, versionStr, file;
-               String[] parts = pathInfo.split("/");
-               // first is always empty
-               if (parts.length == 4) {
-                       pkg = parts[1];
-                       versionStr = parts[2];
-                       file = parts[3];
-               } else if (parts.length == 3) {
-                       pkg = parts[1];
-                       versionStr = null;
-                       file = parts[2];
-               } else {
-                       throw new IllegalArgumentException("Unsupported path length " + pathInfo);
-               }
-
-               FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
-               String filter;
-               if (versionStr == null) {
-                       filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
-               } else {
-                       if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
-                               VersionRange versionRange = new VersionRange(versionStr);
-                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
-                                               + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
-
-                       } else {
-                               Version version = new Version(versionStr);
-                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
-                                               + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
-                       }
-               }
-               Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
-               Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
-               if (packages.isEmpty()) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               // TODO verify that it works with multiple versions
-               SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
-               for (BundleCapability capability : packages) {
-                       sorted.put(capability.getRevision().getVersion(), capability);
-               }
-
-               Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
-               String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
-               URL internalURL = bundle.getResource(entryPath);
-               if (internalURL == null) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               // Resource found, we now check whether it can be published
-               boolean publish = false;
-               BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
-               capabilities: for (BundleCapability bundleCapability : bundleWiring
-                               .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
-                       Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
-                       if (publishedPkg != null) {
-                               if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
-                                       Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
-                                       if (publishedFile == null) {
-                                               publish = true;
-                                               break capabilities;
-                                       } else {
-                                               String[] publishedFiles = publishedFile.toString().split(",");
-                                               for (String pattern : publishedFiles) {
-                                                       if (pattern.startsWith("*.")) {
-                                                               String ext = pattern.substring(1);
-                                                               if (file.endsWith(ext)) {
-                                                                       publish = true;
-                                                                       break capabilities;
-                                                               }
-                                                       } else {
-                                                               if (publishedFile.equals(file)) {
-                                                                       publish = true;
-                                                                       break capabilities;
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-
-               if (!publish) {
-                       resp.sendError(404);
-                       return;
-               }
-
-               try (InputStream in = internalURL.openStream()) {
-                       IOUtils.copy(in, resp.getOutputStream());
-               }
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java
deleted file mode 100644 (file)
index 288ee26..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.cms.servlet.internal;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-public class RobotServlet extends HttpServlet {
-       private static final long serialVersionUID = 7935661175336419089L;
-
-       @Override
-       protected void service(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               PrintWriter writer = response.getWriter();
-               writer.append("User-agent: *\n");
-               writer.append("Disallow:\n");
-               response.setHeader("Content-Type", "text/plain");
-               writer.flush();
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java
deleted file mode 100644 (file)
index 05de32c..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.argeo.cms.servlet.internal.jetty;
-
-import java.util.Dictionary;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-public class JettyServiceFactory implements ManagedServiceFactory {
-       private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class);
-
-       public void start() {
-
-       }
-
-       @Override
-       public String getName() {
-               return "Jetty Service Factory";
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
-               // Explicitly configures Jetty so that the default server is not started by the
-               // activator of the Equinox Jetty bundle.
-
-//             if (!webServerConfig.isEmpty()) {
-//             webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS);
-//
-//             // TODO centralise with Jetty extender
-//             Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED);
-//             if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
-//                     bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
-//                     webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true");
-//             }
-//     }
-
-               int tryCount = 60;
-               try {
-                       tryGettyJetty: while (tryCount > 0) {
-                               try {
-                                       // FIXME deal with multiple ids
-                                       JettyConfigurator.startServer(CmsConstants.DEFAULT, properties);
-                                       // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
-                                       // configuration is not cleaned
-                                       FrameworkUtil.getBundle(JettyConfigurator.class).start();
-                                       break tryGettyJetty;
-                               } catch (IllegalStateException e) {
-                                       // Jetty may not be ready
-                                       try {
-                                               Thread.sleep(1000);
-                                       } catch (Exception e1) {
-                                               // silent
-                                       }
-                                       tryCount--;
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Cannot start default Jetty server with config " + properties, e);
-               }
-
-       }
-
-       @Override
-       public void deleted(String pid) {
-       }
-
-       public void stop() {
-               try {
-                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
-               } catch (Exception e) {
-                       log.error("Cannot stop default Jetty server.", e);
-               }
-
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/.classpath b/eclipse/org.argeo.cms.swt/.classpath
deleted file mode 100644 (file)
index e03d341..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="con"
-               path="org.eclipse.pde.core.requiredPlugins" />
-       <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
-       <classpathentry kind="output" path="bin" />
-</classpath>
diff --git a/eclipse/org.argeo.cms.swt/.project b/eclipse/org.argeo.cms.swt/.project
deleted file mode 100644 (file)
index 082112e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.swt</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/eclipse/org.argeo.cms.swt/META-INF/.gitignore b/eclipse/org.argeo.cms.swt/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/eclipse/org.argeo.cms.swt/bnd.bnd b/eclipse/org.argeo.cms.swt/bnd.bnd
deleted file mode 100644 (file)
index 29f820a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Import-Package: org.eclipse.swt,\
-org.eclipse.jface.window,\
-org.eclipse.core.commands.common,\
-javax.servlet.*;version="[3,5)",\
-*
-
-Bundle-ActivationPolicy: lazy
-                               
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/build.properties b/eclipse/org.argeo.cms.swt/build.properties
deleted file mode 100644 (file)
index 0e04387..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
-               
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/add.png b/eclipse/org.argeo.cms.swt/icons/actions/add.png
deleted file mode 100644 (file)
index 5c06bf0..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/add.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/close-all.png b/eclipse/org.argeo.cms.swt/icons/actions/close-all.png
deleted file mode 100644 (file)
index 81bfc95..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/close-all.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/delete.png b/eclipse/org.argeo.cms.swt/icons/actions/delete.png
deleted file mode 100644 (file)
index 9712723..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/delete.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/edit.png b/eclipse/org.argeo.cms.swt/icons/actions/edit.png
deleted file mode 100644 (file)
index ad3db9f..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/edit.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save-all.png b/eclipse/org.argeo.cms.swt/icons/actions/save-all.png
deleted file mode 100644 (file)
index f48ed32..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/save-all.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save.png b/eclipse/org.argeo.cms.swt/icons/actions/save.png
deleted file mode 100644 (file)
index 1c58ada..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/actions/save.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/active.gif b/eclipse/org.argeo.cms.swt/icons/active.gif
deleted file mode 100644 (file)
index 7d24707..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/active.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/add.gif b/eclipse/org.argeo.cms.swt/icons/add.gif
deleted file mode 100644 (file)
index 252d7eb..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/add.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/add.png b/eclipse/org.argeo.cms.swt/icons/add.png
deleted file mode 100644 (file)
index c7edfec..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/add.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addFolder.gif b/eclipse/org.argeo.cms.swt/icons/addFolder.gif
deleted file mode 100644 (file)
index d3f43d9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/addFolder.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addRepo.gif b/eclipse/org.argeo.cms.swt/icons/addRepo.gif
deleted file mode 100644 (file)
index 26d81c0..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/addRepo.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png
deleted file mode 100644 (file)
index bbee775..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/adminLog.gif b/eclipse/org.argeo.cms.swt/icons/adminLog.gif
deleted file mode 100644 (file)
index 6ef3bca..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/adminLog.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/batch.gif b/eclipse/org.argeo.cms.swt/icons/batch.gif
deleted file mode 100644 (file)
index b8ca14a..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/batch.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/begin.gif b/eclipse/org.argeo.cms.swt/icons/begin.gif
deleted file mode 100755 (executable)
index feb8e94..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/begin.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/binary.png b/eclipse/org.argeo.cms.swt/icons/binary.png
deleted file mode 100644 (file)
index fdf4f82..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/binary.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/browser.gif b/eclipse/org.argeo.cms.swt/icons/browser.gif
deleted file mode 100644 (file)
index 6c7320c..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/browser.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/bundles.gif b/eclipse/org.argeo.cms.swt/icons/bundles.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/bundles.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/changePassword.gif b/eclipse/org.argeo.cms.swt/icons/changePassword.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/changePassword.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/clear.gif b/eclipse/org.argeo.cms.swt/icons/clear.gif
deleted file mode 100644 (file)
index 6bc10f9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/clear.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/close-all.png b/eclipse/org.argeo.cms.swt/icons/close-all.png
deleted file mode 100644 (file)
index 85d4d42..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/close-all.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/commit.gif b/eclipse/org.argeo.cms.swt/icons/commit.gif
deleted file mode 100755 (executable)
index 876f3eb..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/commit.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/delete.png b/eclipse/org.argeo.cms.swt/icons/delete.png
deleted file mode 100644 (file)
index 676a39d..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/delete.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif
deleted file mode 100644 (file)
index 14eb1be..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/file.gif b/eclipse/org.argeo.cms.swt/icons/file.gif
deleted file mode 100644 (file)
index ef30288..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/file.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/folder.gif b/eclipse/org.argeo.cms.swt/icons/folder.gif
deleted file mode 100644 (file)
index 42e027c..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/folder.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/getSize.gif b/eclipse/org.argeo.cms.swt/icons/getSize.gif
deleted file mode 100644 (file)
index b05bf3e..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/getSize.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/group.png b/eclipse/org.argeo.cms.swt/icons/group.png
deleted file mode 100644 (file)
index cc6683a..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/group.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/home.gif b/eclipse/org.argeo.cms.swt/icons/home.gif
deleted file mode 100644 (file)
index fd0c669..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/home.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/home.png b/eclipse/org.argeo.cms.swt/icons/home.png
deleted file mode 100644 (file)
index 5eb0967..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/home.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/import_fs.png b/eclipse/org.argeo.cms.swt/icons/import_fs.png
deleted file mode 100644 (file)
index d7c890c..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/import_fs.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/installed.gif b/eclipse/org.argeo.cms.swt/icons/installed.gif
deleted file mode 100644 (file)
index 2988716..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/installed.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/log.gif b/eclipse/org.argeo.cms.swt/icons/log.gif
deleted file mode 100644 (file)
index e3ecc55..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/log.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/logout.png b/eclipse/org.argeo.cms.swt/icons/logout.png
deleted file mode 100644 (file)
index f2952fa..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/logout.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/maintenance.gif b/eclipse/org.argeo.cms.swt/icons/maintenance.gif
deleted file mode 100644 (file)
index e5690ec..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/maintenance.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/node.gif b/eclipse/org.argeo.cms.swt/icons/node.gif
deleted file mode 100644 (file)
index 364c0e7..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/node.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/nodes.gif b/eclipse/org.argeo.cms.swt/icons/nodes.gif
deleted file mode 100644 (file)
index bba3dbc..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/nodes.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/password.gif b/eclipse/org.argeo.cms.swt/icons/password.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/password.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/person-logged-in.png b/eclipse/org.argeo.cms.swt/icons/person-logged-in.png
deleted file mode 100644 (file)
index 87acc14..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/person-logged-in.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/person.png b/eclipse/org.argeo.cms.swt/icons/person.png
deleted file mode 100644 (file)
index 7d979a5..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/person.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/query.png b/eclipse/org.argeo.cms.swt/icons/query.png
deleted file mode 100644 (file)
index 54c089d..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/query.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/refresh.png b/eclipse/org.argeo.cms.swt/icons/refresh.png
deleted file mode 100644 (file)
index 71b3481..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/refresh.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif
deleted file mode 100644 (file)
index 1492b4e..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif
deleted file mode 100644 (file)
index 6c54da9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remove.gif b/eclipse/org.argeo.cms.swt/icons/remove.gif
deleted file mode 100644 (file)
index 0ae6dec..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/remove.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif
deleted file mode 100644 (file)
index aa78fd2..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/rename.gif b/eclipse/org.argeo.cms.swt/icons/rename.gif
deleted file mode 100644 (file)
index 8048405..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/rename.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repositories.gif b/eclipse/org.argeo.cms.swt/icons/repositories.gif
deleted file mode 100644 (file)
index c13bea1..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/repositories.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif
deleted file mode 100644 (file)
index a15fa55..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif
deleted file mode 100644 (file)
index 4576dc5..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/resolved.gif b/eclipse/org.argeo.cms.swt/icons/resolved.gif
deleted file mode 100644 (file)
index f4a1ea1..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/resolved.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/role.gif b/eclipse/org.argeo.cms.swt/icons/role.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/role.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/rollback.gif b/eclipse/org.argeo.cms.swt/icons/rollback.gif
deleted file mode 100755 (executable)
index c753995..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/rollback.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save-all.png b/eclipse/org.argeo.cms.swt/icons/save-all.png
deleted file mode 100644 (file)
index b68a29b..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/save-all.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save.gif b/eclipse/org.argeo.cms.swt/icons/save.gif
deleted file mode 100644 (file)
index 654ad7b..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/save.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save.png b/eclipse/org.argeo.cms.swt/icons/save.png
deleted file mode 100644 (file)
index f27ef2d..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/save.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save_security.png b/eclipse/org.argeo.cms.swt/icons/save_security.png
deleted file mode 100644 (file)
index ca41dc9..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/save_security.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png b/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png
deleted file mode 100644 (file)
index fb7d08d..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/security.gif b/eclipse/org.argeo.cms.swt/icons/security.gif
deleted file mode 100644 (file)
index 57fb95e..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/security.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/service_published.gif b/eclipse/org.argeo.cms.swt/icons/service_published.gif
deleted file mode 100644 (file)
index 17f771a..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/service_published.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif
deleted file mode 100644 (file)
index c24a95f..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/sort.gif b/eclipse/org.argeo.cms.swt/icons/sort.gif
deleted file mode 100644 (file)
index 23c5d0b..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/sort.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/starting.gif b/eclipse/org.argeo.cms.swt/icons/starting.gif
deleted file mode 100644 (file)
index 563743d..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/starting.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/sync.gif b/eclipse/org.argeo.cms.swt/icons/sync.gif
deleted file mode 100644 (file)
index b4fa052..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/sync.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/user.gif b/eclipse/org.argeo.cms.swt/icons/user.gif
deleted file mode 100644 (file)
index 90a0014..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/user.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/users.gif b/eclipse/org.argeo.cms.swt/icons/users.gif
deleted file mode 100644 (file)
index 2de7edd..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/users.gif and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.png b/eclipse/org.argeo.cms.swt/icons/workgroup.png
deleted file mode 100644 (file)
index 7fef996..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/workgroup.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf
deleted file mode 100644 (file)
index f517c82..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png
deleted file mode 100644 (file)
index 0430baa..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png
deleted file mode 100644 (file)
index fddcb8c..0000000
Binary files a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java
deleted file mode 100644 (file)
index 4ff89f2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.swt.graphics.Image;
-
-/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */
-public interface CmsIcon {
-       String name();
-
-       default Image getSmallIcon(CmsTheme theme) {
-               return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize());
-       }
-
-       default Image getBigIcon(CmsTheme theme) {
-               return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize());
-       }
-
-       default Integer getSmallIconSize() {
-               return 16;
-       }
-
-       default Integer getBigIconSize() {
-               return 32;
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java
deleted file mode 100644 (file)
index 9eba6f6..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.swt;
-
-/** Styles references in the CSS. */
-@Deprecated
-public interface CmsStyles {
-       // General
-       public final static String CMS_SHELL = "cms_shell";
-       public final static String CMS_MENU_LINK = "cms_menu_link";
-
-       // Header
-       public final static String CMS_HEADER = "cms_header";
-       public final static String CMS_HEADER_LEAD = "cms_header-lead";
-       public final static String CMS_HEADER_CENTER = "cms_header-center";
-       public final static String CMS_HEADER_END = "cms_header-end";
-
-       public final static String CMS_LEAD = "cms_lead";
-       public final static String CMS_END = "cms_end";
-       public final static String CMS_FOOTER = "cms_footer";
-
-       public final static String CMS_USER_MENU = "cms_user_menu";
-       public final static String CMS_USER_MENU_LINK = "cms_user_menu-link";
-       public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item";
-       public final static String CMS_LOGIN_DIALOG = "cms_login_dialog";
-       public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username";
-       public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password";
-
-       // Body
-       public final static String CMS_SCROLLED_AREA = "cms_scrolled_area";
-       public final static String CMS_BODY = "cms_body";
-       public final static String CMS_STATIC_TEXT = "cms_static-text";
-       public final static String CMS_LINK = "cms_link";
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java
deleted file mode 100644 (file)
index b5f7c0e..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.swt.graphics.Image;
-
-/** SWT specific {@link CmsTheme}. */
-public interface CmsSwtTheme extends CmsTheme {
-       /** The image registered at this path, or <code>null</code> if not found. */
-       Image getImage(String path);
-
-       /**
-        * And icon with this file name (without the extension), with a best effort to
-        * find the appropriate size, or <code>null</code> if not found.
-        * 
-        * @param name          An icon file name without path and extension.
-        * @param preferredSize the preferred size, if <code>null</code>,
-        *                      {@link #getDefaultIconSize()} will be tried.
-        */
-       Image getIcon(String name, Integer preferredSize);
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java
deleted file mode 100644 (file)
index 701de28..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-package org.argeo.cms.swt;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.api.cms.CmsStyle;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsView;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.Widget;
-
-/** SWT utilities. */
-public class CmsSwtUtils {
-
-       /** Singleton. */
-       private CmsSwtUtils() {
-       }
-
-       public static CmsTheme getCmsTheme(Composite parent) {
-               CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName());
-               if (theme == null) {
-                       // find parent shell
-                       Shell topShell = parent.getShell();
-                       while (topShell.getParent() != null)
-                               topShell = (Shell) topShell.getParent();
-                       theme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
-                       parent.setData(CmsTheme.class.getName(), theme);
-               }
-               return theme;
-       }
-
-       public static void registerCmsTheme(Shell shell, CmsTheme theme) {
-               // find parent shell
-               Shell topShell = shell;
-               while (topShell.getParent() != null)
-                       topShell = (Shell) topShell.getParent();
-               // check if already set
-               if (topShell.getData(CmsTheme.class.getName()) != null) {
-                       CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
-                       throw new IllegalArgumentException(
-                                       "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
-               }
-               topShell.setData(CmsTheme.class.getName(), theme);
-       }
-
-       public static CmsView getCmsView(Control parent) {
-               // find parent shell
-               Shell topShell = parent.getShell();
-               while (topShell.getParent() != null)
-                       topShell = (Shell) topShell.getParent();
-               return (CmsView) topShell.getData(CmsView.class.getName());
-       }
-
-       public static void registerCmsView(Shell shell, CmsView view) {
-               // find parent shell
-               Shell topShell = shell;
-               while (topShell.getParent() != null)
-                       topShell = (Shell) topShell.getParent();
-               // check if already set
-               if (topShell.getData(CmsView.class.getName()) != null) {
-                       CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
-                       throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
-               }
-               shell.setData(CmsView.class.getName(), view);
-       }
-
-       /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
-       public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
-               SelectionListener listener = (Selected) (e) -> {
-                       getCmsView(control.getParent()).sendEvent(topic, properties);
-               };
-               if (control instanceof Button) {
-                       ((Button) control).addSelectionListener(listener);
-               } else
-                       throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
-       }
-
-       /**
-        * Convenience method to sends an event via
-        * {@link CmsView#sendEvent(String, Map)}.
-        */
-       public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(key, value);
-               sendEventOnSelect(control, topic, properties);
-       }
-
-       /*
-        * GRID LAYOUT
-        */
-       /** A {@link GridLayout} without any spacing and one column. */
-       public static GridLayout noSpaceGridLayout() {
-               return noSpaceGridLayout(new GridLayout());
-       }
-
-       /**
-        * A {@link GridLayout} without any spacing and multiple columns of unequal
-        * width.
-        */
-       public static GridLayout noSpaceGridLayout(int columns) {
-               return noSpaceGridLayout(new GridLayout(columns, false));
-       }
-
-       /** @return the same layout, with spaces removed. */
-       public static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static GridData fillAll() {
-               return new GridData(SWT.FILL, SWT.FILL, true, true);
-       }
-
-       public static GridData fillWidth() {
-               return grabWidth(SWT.FILL, SWT.FILL);
-       }
-
-       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
-               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
-       }
-
-       public static GridData fillHeight() {
-               return grabHeight(SWT.FILL, SWT.FILL);
-       }
-
-       public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
-               return new GridData(horizontalAlignment, horizontalAlignment, false, true);
-       }
-
-       /*
-        * ROW LAYOUT
-        */
-       /** @return the same layout, with margins removed. */
-       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
-               rowLayout.marginTop = 0;
-               rowLayout.marginBottom = 0;
-               rowLayout.marginLeft = 0;
-               rowLayout.marginRight = 0;
-               return rowLayout;
-       }
-
-       public static RowLayout noMarginsRowLayout(int type) {
-               return noMarginsRowLayout(new RowLayout(type));
-       }
-
-       public static RowData rowData16px() {
-               return new RowData(16, 16);
-       }
-
-       /*
-        * FORM LAYOUT
-        */
-       public static FormData coverAll() {
-               FormData fdLabel = new FormData();
-               fdLabel.top = new FormAttachment(0, 0);
-               fdLabel.left = new FormAttachment(0, 0);
-               fdLabel.right = new FormAttachment(100, 0);
-               fdLabel.bottom = new FormAttachment(100, 0);
-               return fdLabel;
-       }
-
-       /*
-        * STYLING
-        */
-
-       /** Style widget */
-       public static <T extends Widget> T style(T widget, String style) {
-               if (style == null)
-                       return widget;// does nothing
-               EclipseUiSpecificUtils.setStyleData(widget, style);
-               if (widget instanceof Control) {
-                       CmsView cmsView = getCmsView((Control) widget);
-                       if (cmsView != null)
-                               cmsView.applyStyles(widget);
-               }
-               return widget;
-       }
-
-       /** Style widget */
-       public static <T extends Widget> T style(T widget, CmsStyle style) {
-               return style(widget, style.style());
-       }
-
-       /** Enable markups on widget */
-       public static <T extends Widget> T markup(T widget) {
-               EclipseUiSpecificUtils.setMarkupData(widget);
-               return widget;
-       }
-
-       /** Disable markup validation. */
-       public static <T extends Widget> T disableMarkupValidation(T widget) {
-               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
-               return widget;
-       }
-
-       /**
-        * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
-        * 
-        * @param widget the widget to style and to use in order to display text
-        * @param txt    the object to display via its <code>toString()</code> method.
-        *               This argument should not be null, but if it is null and
-        *               assertions are disabled "<null>" is displayed instead; if
-        *               assertions are enabled the call will fail.
-        * 
-        * @see markup
-        */
-       public static <T extends Widget> T text(T widget, Object txt) {
-               assert txt != null;
-               String str = txt != null ? txt.toString() : "<null>";
-               markup(widget);
-               if (widget instanceof Label)
-                       ((Label) widget).setText(str);
-               else if (widget instanceof Button)
-                       ((Button) widget).setText(str);
-               else if (widget instanceof Text)
-                       ((Text) widget).setText(str);
-               else
-                       throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
-               return widget;
-       }
-
-       /** A {@link Label} with markup activated. */
-       public static Label lbl(Composite parent, Object txt) {
-               return text(new Label(parent, SWT.NONE), txt);
-       }
-
-       /** A read-only {@link Text} whose content can be copy/pasted. */
-       public static Text txt(Composite parent, Object txt) {
-               return text(new Text(parent, SWT.NONE), txt);
-       }
-
-       /** Dispose all children of a Composite */
-       public static void clear(Composite composite) {
-               if (composite.isDisposed())
-                       return;
-               for (Control child : composite.getChildren())
-                       child.dispose();
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java
deleted file mode 100644 (file)
index b818b06..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-
-/**
- * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface
- * in order to use as a short lambda expression in UI code.
- * {@link MouseListener#mouseDownouseEvent)} and
- * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
- */
-@FunctionalInterface
-public interface MouseDoubleClick extends MouseListener {
-       @Override
-       void mouseDoubleClick(MouseEvent e);
-
-       @Override
-       default void mouseDown(MouseEvent e) {
-               // does nothing
-       }
-
-       @Override
-       default void mouseUp(MouseEvent e) {
-               // does nothing
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java
deleted file mode 100644 (file)
index baecb00..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-
-/**
- * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in
- * order to use as a short lambda expression in UI code.
- * {@link MouseListener#mouseDoubleClick(MouseEvent)} and
- * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
- */
-@FunctionalInterface
-public interface MouseDown extends MouseListener {
-       @Override
-       void mouseDown(MouseEvent e);
-
-       @Override
-       default void mouseDoubleClick(MouseEvent e) {
-               // does nothing
-       }
-
-       @Override
-       default void mouseUp(MouseEvent e) {
-               // does nothing
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java
deleted file mode 100644 (file)
index 03fbad0..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-
-/**
- * {@link SelectionListener} as a functional interface in order to use as a
- * short lambda expression in UI code.
- * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing
- * by default.
- */
-@FunctionalInterface
-public interface Selected extends SelectionListener {
-       @Override
-       public void widgetSelected(SelectionEvent e);
-
-       default public void widgetDefaultSelected(SelectionEvent e) {
-               // does nothing
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java
deleted file mode 100644 (file)
index 9c55e8b..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.argeo.cms.swt;
-
-import org.argeo.api.cms.UxContext;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Display;
-
-public class SimpleSwtUxContext implements UxContext {
-       private Point size;
-       private Point small = new Point(400, 400);
-
-       public SimpleSwtUxContext() {
-               this(Display.getCurrent().getBounds());
-       }
-
-       public SimpleSwtUxContext(Rectangle rect) {
-               this.size = new Point(rect.width, rect.height);
-       }
-
-       public SimpleSwtUxContext(Point size) {
-               this.size = size;
-       }
-
-       @Override
-       public boolean isPortrait() {
-               return size.x >= size.y;
-       }
-
-       @Override
-       public boolean isLandscape() {
-               return size.x < size.y;
-       }
-
-       @Override
-       public boolean isSquare() {
-               return size.x == size.y;
-       }
-
-       @Override
-       public boolean isSmall() {
-               return size.x <= small.x || size.y <= small.y;
-       }
-
-       @Override
-       public boolean isMasterData() {
-               // TODO make it configurable
-               return true;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java
deleted file mode 100644 (file)
index 43e5739..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import static org.argeo.cms.CmsMsg.password;
-import static org.argeo.cms.CmsMsg.username;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.LanguageCallback;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsView;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-public class CmsLogin implements CmsStyles, CallbackHandler {
-       private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
-
-       private Composite parent;
-       private Text usernameT, passwordT;
-       private Composite credentialsBlock;
-       private final SelectionListener loginSelectionListener;
-
-       private final Locale defaultLocale;
-       private LocaleChoice localeChoice = null;
-
-       private final CmsView cmsView;
-
-       // optional subject to be set explicitly
-       private Subject subject = null;
-
-       private CmsContext cmsContext;
-
-       public CmsLogin(CmsView cmsView, CmsContext cmsContext) {
-               this.cmsView = cmsView;
-               this.cmsContext = cmsContext;
-               if (this.cmsContext != null) {
-                       defaultLocale = this.cmsContext.getDefaultLocale();
-                       List<Locale> locales = this.cmsContext.getLocales();
-                       if (locales != null)
-                               localeChoice = new LocaleChoice(locales, defaultLocale);
-               } else {
-                       defaultLocale = Locale.getDefault();
-               }
-               loginSelectionListener = new SelectionListener() {
-                       private static final long serialVersionUID = -8832133363830973578L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               login();
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                       }
-               };
-       }
-
-       protected boolean isAnonymous() {
-               return cmsView.isAnonymous();
-       }
-
-       public final void createUi(Composite parent) {
-               this.parent = parent;
-               createContents(parent);
-       }
-
-       protected void createContents(Composite parent) {
-               defaultCreateContents(parent);
-       }
-
-       public final void defaultCreateContents(Composite parent) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite credentialsBlock = createCredentialsBlock(parent);
-               if (parent instanceof Shell) {
-                       credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-               }
-       }
-
-       public final Composite createCredentialsBlock(Composite parent) {
-               if (isAnonymous()) {
-                       return anonymousUi(parent);
-               } else {
-                       return userUi(parent);
-               }
-       }
-
-       public Composite getCredentialsBlock() {
-               return credentialsBlock;
-       }
-
-       protected Composite userUi(Composite parent) {
-               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
-               credentialsBlock = new Composite(parent, SWT.NONE);
-               credentialsBlock.setLayout(new GridLayout());
-               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
-
-               specificUserUi(credentialsBlock);
-
-               Label l = new Label(credentialsBlock, SWT.NONE);
-               CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
-               l.setText(CmsMsg.logout.lead(locale));
-               GridData lData = CmsSwtUtils.fillWidth();
-               lData.widthHint = 120;
-               l.setLayoutData(lData);
-
-               l.addMouseListener(new MouseAdapter() {
-                       private static final long serialVersionUID = 6444395812777413116L;
-
-                       public void mouseDown(MouseEvent e) {
-                               logout();
-                       }
-               });
-               return credentialsBlock;
-       }
-
-       /** To be overridden */
-       protected void specificUserUi(Composite parent) {
-
-       }
-
-       protected Composite anonymousUi(Composite parent) {
-               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
-               // We need a composite for the traversal
-               credentialsBlock = new Composite(parent, SWT.NONE);
-               credentialsBlock.setLayout(new GridLayout());
-               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
-               CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
-
-               Integer textWidth = 120;
-               if (parent instanceof Shell)
-                       CmsSwtUtils.style(parent, CMS_USER_MENU);
-               // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
-               usernameT = new Text(credentialsBlock, SWT.BORDER);
-               usernameT.setMessage(username.lead(locale));
-               CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
-               GridData gd = CmsSwtUtils.fillWidth();
-               gd.widthHint = textWidth;
-               usernameT.setLayoutData(gd);
-
-               // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
-               passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
-               passwordT.setMessage(password.lead(locale));
-               CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
-               gd = CmsSwtUtils.fillWidth();
-               gd.widthHint = textWidth;
-               passwordT.setLayoutData(gd);
-
-               TraverseListener tl = new TraverseListener() {
-                       private static final long serialVersionUID = -1158892811534971856L;
-
-                       public void keyTraversed(TraverseEvent e) {
-                               if (e.detail == SWT.TRAVERSE_RETURN)
-                                       login();
-                       }
-               };
-               credentialsBlock.addTraverseListener(tl);
-               usernameT.addTraverseListener(tl);
-               passwordT.addTraverseListener(tl);
-               parent.setTabList(new Control[] { credentialsBlock });
-               credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
-
-               // Button
-               Button loginButton = new Button(credentialsBlock, SWT.PUSH);
-               loginButton.setText(CmsMsg.login.lead(locale));
-               loginButton.setLayoutData(CmsSwtUtils.fillWidth());
-               loginButton.addSelectionListener(loginSelectionListener);
-
-               extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
-               if (localeChoice != null)
-                       createLocalesBlock(credentialsBlock);
-               return credentialsBlock;
-       }
-
-       /**
-        * To be overridden in order to provide custom login button and other links.
-        */
-       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
-                       SelectionListener loginSelectionListener) {
-
-       }
-
-       protected void updateLocale(Locale selectedLocale) {
-               // save already entered values
-               String usernameStr = usernameT.getText();
-               char[] pwd = passwordT.getTextChars();
-
-               for (Control child : parent.getChildren())
-                       child.dispose();
-               createContents(parent);
-               if (parent.getParent() != null)
-                       parent.getParent().layout(true, true);
-               else
-                       parent.layout();
-               usernameT.setText(usernameStr);
-               passwordT.setTextChars(pwd);
-       }
-
-       protected Composite createLocalesBlock(final Composite parent) {
-               Composite c = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
-               c.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               c.setLayoutData(CmsSwtUtils.fillAll());
-
-               SelectionListener selectionListener = new SelectionAdapter() {
-                       private static final long serialVersionUID = 4891637813567806762L;
-
-                       public void widgetSelected(SelectionEvent event) {
-                               Button button = (Button) event.widget;
-                               if (button.getSelection()) {
-                                       localeChoice.setSelectedIndex((Integer) event.widget.getData());
-                                       updateLocale(localeChoice.getSelectedLocale());
-                               }
-                       };
-               };
-
-               List<Locale> locales = localeChoice.getLocales();
-               for (Integer i = 0; i < locales.size(); i++) {
-                       Locale locale = locales.get(i);
-                       Button button = new Button(c, SWT.RADIO);
-                       CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
-                       button.setData(i);
-                       button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
-                       // button.addListener(SWT.Selection, listener);
-                       button.addSelectionListener(selectionListener);
-                       if (i == localeChoice.getSelectedIndex())
-                               button.setSelection(true);
-               }
-               return c;
-       }
-
-       protected boolean login() {
-               // TODO use CmsVie in order to retrieve subject?
-               // Subject subject = cmsView.getLoginContext().getSubject();
-               // LoginContext loginContext = cmsView.getLoginContext();
-               try {
-                       //
-                       // LOGIN
-                       //
-                       // loginContext.logout();
-                       LoginContext loginContext;
-                       if (subject == null)
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
-                       else
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
-                       loginContext.login();
-                       cmsView.authChange(loginContext);
-                       return true;
-               } catch (LoginException e) {
-                       if (log.isTraceEnabled())
-                               log.warn("Login failed: " + e.getMessage(), e);
-                       else
-                               log.warn("Login failed: " + e.getMessage());
-
-                       try {
-                               Thread.sleep(3000);
-                       } catch (InterruptedException e2) {
-                               // silent
-                       }
-                       // ErrorFeedback.show("Login failed", e);
-                       return false;
-               }
-               // catch (LoginException e) {
-               // log.error("Cannot login", e);
-               // return false;
-               // }
-       }
-
-       protected void logout() {
-               cmsView.logout();
-               cmsView.navigateTo("~");
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               for (Callback callback : callbacks) {
-                       if (callback instanceof NameCallback && usernameT != null)
-                               ((NameCallback) callback).setName(usernameT.getText());
-                       else if (callback instanceof PasswordCallback && passwordT != null)
-                               ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
-                       else if (callback instanceof RemoteAuthCallback) {
-                               ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
-                               ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
-                       } else if (callback instanceof LanguageCallback) {
-                               Locale toUse = null;
-                               if (localeChoice != null)
-                                       toUse = localeChoice.getSelectedLocale();
-                               else if (defaultLocale != null)
-                                       toUse = defaultLocale;
-
-                               if (toUse != null) {
-                                       ((LanguageCallback) callback).setLocale(toUse);
-                                       UiContext.setLocale(toUse);
-                               }
-
-                       }
-               }
-       }
-
-       public void setSubject(Subject subject) {
-               this.subject = subject;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java
deleted file mode 100644 (file)
index a4d7c07..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import org.argeo.api.cms.CmsContext;
-import org.argeo.api.cms.CmsView;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** The site-related user menu */
-public class CmsLoginShell extends CmsLogin {
-       private final Shell shell;
-
-       public CmsLoginShell(CmsView cmsView, CmsContext cmsContext) {
-               super(cmsView, cmsContext);
-               shell = createShell();
-//             createUi(shell);
-       }
-
-       /** To be overridden. */
-       protected Shell createShell() {
-               Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
-               shell.setMaximized(true);
-               return shell;
-       }
-
-       /** To be overridden. */
-       public void open() {
-               CmsSwtUtils.style(shell, CMS_USER_MENU);
-               shell.open();
-       }
-
-       @Override
-       protected boolean login() {
-               boolean success = false;
-               try {
-                       success = super.login();
-                       return success;
-               } finally {
-                       if (success)
-                               closeShell();
-                       else {
-                               for (Control child : shell.getChildren())
-                                       child.dispose();
-                               createUi(shell);
-                               shell.layout();
-                               // TODO error message
-                       }
-               }
-       }
-
-       @Override
-       protected void logout() {
-               closeShell();
-               super.logout();
-       }
-
-       protected void closeShell() {
-               if (!shell.isDisposed()) {
-                       shell.close();
-                       shell.dispose();
-               }
-       }
-
-       public Shell getShell() {
-               return shell;
-       }
-
-       public void createUi() {
-               createUi(shell);
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java
deleted file mode 100644 (file)
index 495007c..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Combo;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * A composite that can populate itself based on {@link Callback}s. It can be
- * used directly as a {@link CallbackHandler} or be used by one by calling the
- * {@link #createCallbackHandlers(Callback[])}. Supported standard
- * {@link Callback}s are:<br>
- * <ul>
- * <li>{@link PasswordCallback}</li>
- * <li>{@link NameCallback}</li>
- * <li>{@link TextOutputCallback}</li>
- * </ul>
- * Supported Argeo {@link Callback}s are:<br>
- * <ul>
- * <li>{@link LocaleChoice}</li>
- * </ul>
- */
-public class CompositeCallbackHandler extends Composite implements CallbackHandler {
-       private static final long serialVersionUID = -928223893722723777L;
-
-       private boolean wasUsedAlready = false;
-       private boolean isSubmitted = false;
-       private boolean isCanceled = false;
-
-       public CompositeCallbackHandler(Composite parent, int style) {
-               super(parent, style);
-       }
-
-       @Override
-       public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               // reset
-               if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
-                       cancel();
-                       for (Control control : getChildren())
-                               control.dispose();
-                       isSubmitted = false;
-                       isCanceled = false;
-               }
-
-               for (Callback callback : callbacks)
-                       checkCallbackSupported(callback);
-               // create controls synchronously in the UI thread
-               getDisplay().syncExec(new Runnable() {
-
-                       @Override
-                       public void run() {
-                               createCallbackHandlers(callbacks);
-                       }
-               });
-
-               if (!wasUsedAlready)
-                       wasUsedAlready = true;
-
-               // while (!isSubmitted() && !isCanceled()) {
-               // try {
-               // wait(1000l);
-               // } catch (InterruptedException e) {
-               // // silent
-               // }
-               // }
-
-               // cleanCallbacksAfterCancel(callbacks);
-       }
-
-       public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException {
-               if (callback instanceof TextOutputCallback || callback instanceof NameCallback
-                               || callback instanceof PasswordCallback || callback instanceof LocaleChoice) {
-                       return;
-               } else {
-                       throw new UnsupportedCallbackException(callback);
-               }
-       }
-
-       /**
-        * Set writable callbacks to null if the handle is canceled (check is done
-        * by the method)
-        */
-       public void cleanCallbacksAfterCancel(Callback[] callbacks) {
-               if (isCanceled()) {
-                       for (Callback callback : callbacks) {
-                               if (callback instanceof NameCallback) {
-                                       ((NameCallback) callback).setName(null);
-                               } else if (callback instanceof PasswordCallback) {
-                                       PasswordCallback pCallback = (PasswordCallback) callback;
-                                       char[] arr = pCallback.getPassword();
-                                       if (arr != null) {
-                                               Arrays.fill(arr, '*');
-                                               pCallback.setPassword(null);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       public void createCallbackHandlers(Callback[] callbacks) {
-               Composite composite = this;
-               for (int i = 0; i < callbacks.length; i++) {
-                       Callback callback = callbacks[i];
-                       if (callback instanceof TextOutputCallback) {
-                               createLabelTextoutputHandler(composite, (TextOutputCallback) callback);
-                       } else if (callback instanceof NameCallback) {
-                               createNameHandler(composite, (NameCallback) callback);
-                       } else if (callback instanceof PasswordCallback) {
-                               createPasswordHandler(composite, (PasswordCallback) callback);
-                       } else if (callback instanceof LocaleChoice) {
-                               createLocaleHandler(composite, (LocaleChoice) callback);
-                       }
-               }
-       }
-
-       protected Text createNameHandler(Composite composite, final NameCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getPrompt());
-               final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
-               if (callback.getDefaultName() != null) {
-                       // set default value, if provided
-                       text.setText(callback.getDefaultName());
-                       callback.setName(callback.getDefaultName());
-               }
-               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               text.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 7300032545287292973L;
-
-                       public void modifyText(ModifyEvent event) {
-                               callback.setName(text.getText());
-                       }
-               });
-               text.addSelectionListener(new SelectionListener() {
-                       private static final long serialVersionUID = 1820530045857665111L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                               submit();
-                       }
-               });
-
-               text.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = -8698107785092095713L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                       }
-               });
-               return text;
-       }
-
-       protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getPrompt());
-               final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
-               passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               passwordText.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = -7099363995047686732L;
-
-                       public void modifyText(ModifyEvent event) {
-                               callback.setPassword(passwordText.getTextChars());
-                       }
-               });
-               passwordText.addSelectionListener(new SelectionListener() {
-                       private static final long serialVersionUID = 1820530045857665111L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                               submit();
-                       }
-               });
-               return passwordText;
-       }
-
-       protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) {
-               String[] labels = callback.getSupportedLocalesLabels();
-               if (labels.length == 0)
-                       return null;
-               Label label = new Label(composite, SWT.NONE);
-               label.setText("Language");
-
-               final Combo combo = new Combo(composite, SWT.READ_ONLY);
-               combo.setItems(labels);
-               combo.select(callback.getDefaultIndex());
-               combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               combo.addSelectionListener(new SelectionListener() {
-                       private static final long serialVersionUID = 38678989091946277L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               callback.setSelectedIndex(combo.getSelectionIndex());
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                       }
-               });
-               return combo;
-       }
-
-       protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) {
-               Label label = new Label(composite, SWT.NONE);
-               label.setText(callback.getMessage());
-               GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
-               data.horizontalSpan = 2;
-               label.setLayoutData(data);
-               return label;
-               // TODO: find a way to pass this information
-               // int messageType = callback.getMessageType();
-               // int dialogMessageType = IMessageProvider.NONE;
-               // switch (messageType) {
-               // case TextOutputCallback.INFORMATION:
-               // dialogMessageType = IMessageProvider.INFORMATION;
-               // break;
-               // case TextOutputCallback.WARNING:
-               // dialogMessageType = IMessageProvider.WARNING;
-               // break;
-               // case TextOutputCallback.ERROR:
-               // dialogMessageType = IMessageProvider.ERROR;
-               // break;
-               // }
-               // setMessage(callback.getMessage(), dialogMessageType);
-       }
-
-       synchronized boolean isSubmitted() {
-               return isSubmitted;
-       }
-
-       synchronized boolean isCanceled() {
-               return isCanceled;
-       }
-
-       protected synchronized void submit() {
-               isSubmitted = true;
-               notifyAll();
-       }
-
-       protected synchronized void cancel() {
-               isCanceled = true;
-               notifyAll();
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java
deleted file mode 100644 (file)
index b0c36c6..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.eclipse.ui.dialogs.LightweightDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public class DynamicCallbackHandler implements CallbackHandler {
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               Shell activeShell = Display.getCurrent().getActiveShell();
-               LightweightDialog dialog = new LightweightDialog(activeShell) {
-
-                       @Override
-                       protected Control createDialogArea(Composite parent) {
-                               CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE);
-                               cch.createCallbackHandlers(callbacks);
-                               return cch;
-                       }
-               };
-               dialog.setBlockOnOpen(true);
-               dialog.open();
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java
deleted file mode 100644 (file)
index e98e390..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import javax.security.auth.callback.LanguageCallback;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.LocaleUtils;
-
-/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */
-public class LocaleChoice {
-       private final List<Locale> locales;
-
-       private Integer selectedIndex = null;
-       private final Integer defaultIndex;
-
-       public LocaleChoice(List<Locale> locales, Locale defaultLocale) {
-               Integer defaultIndex = null;
-               this.locales = Collections.unmodifiableList(locales);
-               for (int i = 0; i < locales.size(); i++)
-                       if (locales.get(i).equals(defaultLocale))
-                               defaultIndex = i;
-
-               // based on language only
-               if (defaultIndex == null)
-                       for (int i = 0; i < locales.size(); i++)
-                               if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage()))
-                                       defaultIndex = i;
-
-               if (defaultIndex == null)
-                       throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales);
-               this.defaultIndex = defaultIndex;
-
-               this.selectedIndex = defaultIndex;
-       }
-
-       /**
-        * Convenience constructor based on a comma separated list of iso codes (en,
-        * en_US, fr_CA, etc.). Default selection is default locale.
-        */
-       public LocaleChoice(String locales, Locale defaultLocale) {
-               this(LocaleUtils.asLocaleList(locales), defaultLocale);
-       }
-
-       public String[] getSupportedLocalesLabels() {
-               String[] labels = new String[locales.size()];
-               for (int i = 0; i < locales.size(); i++) {
-                       Locale locale = locales.get(i);
-                       if (locale.getCountry().equals(""))
-                               labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]";
-                       else
-                               labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") ["
-                                               + locale.getLanguage() + "_" + locale.getCountry() + "]";
-
-               }
-               return labels;
-       }
-
-       public Locale getSelectedLocale() {
-               if (selectedIndex == null)
-                       return null;
-               return locales.get(selectedIndex);
-       }
-
-       public void setSelectedIndex(Integer selectedIndex) {
-               this.selectedIndex = selectedIndex;
-       }
-
-       public Integer getSelectedIndex() {
-               return selectedIndex;
-       }
-
-       public Integer getDefaultIndex() {
-               return defaultIndex;
-       }
-
-       public List<Locale> getLocales() {
-               return locales;
-       }
-
-       public Locale getDefaultLocale() {
-               return locales.get(getDefaultIndex());
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java
deleted file mode 100644 (file)
index b431423..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS authentication widgets, based on SWT. */
-package org.argeo.cms.swt.auth;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java
deleted file mode 100644 (file)
index 8ff0862..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import java.security.PrivilegedAction;
-import java.util.Arrays;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** Dialog to change a password. */
-public class ChangePasswordDialog extends CmsMessageDialog {
-       private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class);
-
-       private CmsUserManager cmsUserManager;
-       private CmsView cmsView;
-
-       private PrivilegedAction<Integer> doIt;
-
-       public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) {
-               super(parentShell, message, kind);
-               this.cmsUserManager = cmsUserManager;
-               cmsView = CmsSwtUtils.getCmsView(parentShell);
-       }
-
-       @Override
-       protected Control createInputArea(Composite userSection) {
-               addFormLabel(userSection, CmsMsg.currentPassword.lead());
-               Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               previousPassword.setLayoutData(CmsSwtUtils.fillWidth());
-               addFormLabel(userSection, CmsMsg.newPassword.lead());
-               Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               newPassword.setLayoutData(CmsSwtUtils.fillWidth());
-               addFormLabel(userSection, CmsMsg.repeatNewPassword.lead());
-               Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               confirmPassword.setLayoutData(CmsSwtUtils.fillWidth());
-
-               doIt = () -> {
-                       if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) {
-                               try {
-                                       cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars());
-                                       return OK;
-                               } catch (Exception e1) {
-                                       log.error("Could not change password", e1);
-                                       cancel();
-                                       CmsMessageDialog.openError(CmsMsg.invalidPassword.lead());
-                                       return CANCEL;
-                               }
-                       } else {
-                               cancel();
-                               CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead());
-                               return CANCEL;
-                       }
-               };
-
-               pack();
-               return previousPassword;
-       }
-
-       @Override
-       protected void okPressed() {
-               Integer returnCode = cmsView.doAs(doIt);
-               if (returnCode.equals(OK)) {
-                       super.okPressed();
-                       CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead());
-               }
-       }
-
-       private static Label addFormLabel(Composite parent, String label) {
-               Label lbl = new Label(parent, SWT.WRAP);
-               lbl.setText(label);
-//             CmsUiUtils.style(lbl, SuiteStyle.simpleLabel);
-               return lbl;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java
deleted file mode 100644 (file)
index a01c919..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.Selected;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A dialog feedback based on a {@link LightweightDialog}. */
-public class CmsFeedback extends LightweightDialog {
-       private final static CmsLog log = CmsLog.getLog(CmsFeedback.class);
-
-       private String message;
-       private Throwable exception;
-
-       public CmsFeedback(Shell parentShell, String message, Throwable e) {
-               super(parentShell);
-               this.message = message;
-               this.exception = e;
-               log.error(message, e);
-       }
-
-       public static CmsFeedback show(String message, Throwable e) {
-               // rethrow ThreaDeath in order to make sure that RAP will properly clean
-               // up the UI thread
-               if (e instanceof ThreadDeath)
-                       throw (ThreadDeath) e;
-
-               try {
-                       CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
-                       cmsFeedback.setBlockOnOpen(false);
-                       cmsFeedback.open();
-                       return cmsFeedback;
-               } catch (Throwable e1) {
-                       log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e);
-                       return null;
-               }
-       }
-
-       public static CmsFeedback show(String message) {
-               CmsFeedback cmsFeedback = new CmsFeedback(null, message, null);
-               cmsFeedback.open();
-               return cmsFeedback;
-       }
-
-       /** Tries to find a display */
-       // private static Display getDisplay() {
-       // try {
-       // Display display = Display.getCurrent();
-       // if (display != null)
-       // return display;
-       // else
-       // return Display.getDefault();
-       // } catch (Exception e) {
-       // return Display.getCurrent();
-       // }
-       // }
-
-       protected Control createDialogArea(Composite parent) {
-               parent.setLayout(new GridLayout(2, false));
-
-               Label messageLbl = new Label(parent, SWT.WRAP);
-               if (message != null)
-                       messageLbl.setText(message);
-               else if (exception != null)
-                       messageLbl.setText(exception.getLocalizedMessage());
-
-               Button close = new Button(parent, SWT.FLAT);
-               close.setText(CmsMsg.close.lead());
-               close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
-               close.addSelectionListener((Selected) (e) -> closeShell(OK));
-
-               // Composite composite = new Composite(dialogarea, SWT.NONE);
-               // composite.setLayout(new GridLayout(2, false));
-               // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               if (exception != null) {
-                       Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
-                       stack.setEditable(false);
-                       stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
-                       StringWriter sw = new StringWriter();
-                       exception.printStackTrace(new PrintWriter(sw));
-                       stack.setText(sw.toString());
-               }
-
-               // parent.pack();
-               return messageLbl;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java
deleted file mode 100644 (file)
index 66e6405..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** Base class for dialogs displaying messages or small forms. */
-public class CmsMessageDialog extends LightweightDialog {
-       public final static int NONE = 0;
-       public final static int ERROR = 1;
-       public final static int INFORMATION = 2;
-       public final static int QUESTION = 3;
-       public final static int WARNING = 4;
-       public final static int CONFIRM = 5;
-       public final static int QUESTION_WITH_CANCEL = 6;
-
-       private int kind;
-       private String message;
-
-       public CmsMessageDialog(Shell parentShell, String message, int kind) {
-               super(parentShell);
-               this.kind = kind;
-               this.message = message;
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               parent.setLayout(new GridLayout());
-
-               TraverseListener traverseListener = new TraverseListener() {
-                       private static final long serialVersionUID = -1158892811534971856L;
-
-                       public void keyTraversed(TraverseEvent e) {
-                               if (e.detail == SWT.TRAVERSE_RETURN)
-                                       okPressed();
-                               else if (e.detail == SWT.TRAVERSE_ESCAPE)
-                                       cancelPressed();
-                       }
-               };
-
-               // message
-               Composite body = new Composite(parent, SWT.NONE);
-               body.addTraverseListener(traverseListener);
-               GridLayout bodyGridLayout = new GridLayout();
-               bodyGridLayout.marginHeight = 20;
-               bodyGridLayout.marginWidth = 20;
-               body.setLayout(bodyGridLayout);
-               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               if (message != null) {
-                       Label messageLbl = new Label(body, SWT.WRAP);
-                       CmsSwtUtils.markup(messageLbl);
-                       messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-                       messageLbl.setFont(EclipseUiUtils.getBoldFont(parent));
-                       messageLbl.setText(message);
-               }
-
-               // buttons
-               Composite buttons = new Composite(parent, SWT.NONE);
-               buttons.addTraverseListener(traverseListener);
-               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
-               if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) {
-                       GridLayout layout = new GridLayout(1, true);
-                       layout.marginWidth = 0;
-                       layout.marginHeight = 0;
-                       buttons.setLayout(layout);
-
-                       Button close = new Button(buttons, SWT.FLAT);
-                       close.setText(CmsMsg.close.lead());
-                       close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                       close.addSelectionListener((Selected) (e) -> closeShell(OK));
-                       close.setFocus();
-                       close.addTraverseListener(traverseListener);
-
-                       buttons.setTabList(new Control[] { close });
-               } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) {
-                       Control input = createInputArea(body);
-                       if (input != null) {
-                               input.addTraverseListener(traverseListener);
-                               body.setTabList(new Control[] { input });
-                       }
-                       GridLayout layout = new GridLayout(2, true);
-                       layout.marginWidth = 0;
-                       layout.marginHeight = 0;
-                       buttons.setLayout(layout);
-
-                       Button cancel = new Button(buttons, SWT.FLAT);
-                       cancel.setText(CmsMsg.cancel.lead());
-                       cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                       cancel.addSelectionListener((Selected) (e) -> cancelPressed());
-                       cancel.addTraverseListener(traverseListener);
-
-                       Button ok = new Button(buttons, SWT.FLAT);
-                       ok.setText(CmsMsg.ok.lead());
-                       ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                       ok.addSelectionListener((Selected) (e) -> okPressed());
-                       ok.addTraverseListener(traverseListener);
-                       if (input == null)
-                               ok.setFocus();
-                       else
-                               input.setFocus();
-
-                       buttons.setTabList(new Control[] { ok, cancel });
-               }
-               // pack();
-               parent.setTabList(new Control[] { body, buttons });
-               return body;
-       }
-
-       protected Control createInputArea(Composite parent) {
-               return null;
-       }
-
-       protected void okPressed() {
-               closeShell(OK);
-       }
-
-       protected void cancelPressed() {
-               closeShell(CANCEL);
-       }
-
-       protected void cancel() {
-               closeShell(CANCEL);
-       }
-
-       protected Point getInitialSize() {
-               return new Point(400, 200);
-       }
-
-       public static boolean open(int kind, Shell parent, String message) {
-               CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind);
-               return dialog.open() == 0;
-       }
-
-       public static boolean openConfirm(String message) {
-               return open(CONFIRM, Display.getCurrent().getActiveShell(), message);
-       }
-
-       public static void openInformation(String message) {
-               open(INFORMATION, Display.getCurrent().getActiveShell(), message);
-       }
-
-       public static boolean openQuestion(String message) {
-               return open(QUESTION, Display.getCurrent().getActiveShell(), message);
-       }
-
-       public static void openWarning(String message) {
-               open(WARNING, Display.getCurrent().getActiveShell(), message);
-       }
-
-       public static void openError(String message) {
-               open(ERROR, Display.getCurrent().getActiveShell(), message);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java
deleted file mode 100644 (file)
index 59d9ab7..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import java.lang.reflect.InvocationTargetException;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.wizard.IWizard;
-import org.eclipse.jface.wizard.IWizardContainer2;
-import org.eclipse.jface.wizard.IWizardPage;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.FormLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** A wizard dialog based on {@link LightweightDialog}. */
-public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 {
-       private static final long serialVersionUID = -2123153353654812154L;
-
-       private IWizard wizard;
-       private IWizardPage currentPage;
-       private int currentPageIndex;
-
-       private Label titleBar;
-       private Label message;
-       private Composite[] pageBodies;
-       private Composite buttons;
-       private Button back;
-       private Button next;
-       private Button finish;
-
-       public CmsWizardDialog(Shell parentShell, IWizard wizard) {
-               super(parentShell);
-               this.wizard = wizard;
-               wizard.setContainer(this);
-               // create the pages
-               wizard.addPages();
-               currentPage = wizard.getStartingPage();
-               if (currentPage == null)
-                       throw new IllegalArgumentException("At least one wizard page is required");
-       }
-
-       @Override
-       protected Control createDialogArea(Composite parent) {
-               updateWindowTitle();
-
-               Composite messageArea = new Composite(parent, SWT.NONE);
-               messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               {
-                       messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
-                       titleBar = new Label(messageArea, SWT.WRAP);
-                       titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
-                       titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
-                       updateTitleBar();
-                       Button cancelButton = new Button(messageArea, SWT.FLAT);
-                       cancelButton.setText(CmsMsg.cancel.lead());
-                       cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
-                       cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL));
-                       message = new Label(messageArea, SWT.WRAP);
-                       message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
-                       updateMessage();
-               }
-
-               Composite body = new Composite(parent, SWT.BORDER);
-               body.setLayout(new FormLayout());
-               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               pageBodies = new Composite[wizard.getPageCount()];
-               IWizardPage[] pages = wizard.getPages();
-               for (int i = 0; i < pages.length; i++) {
-                       pageBodies[i] = new Composite(body, SWT.NONE);
-                       pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
-                       setSwitchingFormData(pageBodies[i]);
-                       pages[i].createControl(pageBodies[i]);
-               }
-               showPage(currentPage);
-
-               buttons = new Composite(parent, SWT.NONE);
-               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
-               {
-                       boolean singlePage = wizard.getPageCount() == 1;
-                       // singlePage = false;// dev
-                       GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
-                       layout.marginWidth = 0;
-                       layout.marginHeight = 0;
-                       buttons.setLayout(layout);
-                       // TODO revert order for right-to-left languages
-
-                       if (!singlePage) {
-                               back = new Button(buttons, SWT.PUSH);
-                               back.setText(CmsMsg.wizardBack.lead());
-                               back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                               back.addSelectionListener((Selected) (e) -> backPressed());
-
-                               next = new Button(buttons, SWT.PUSH);
-                               next.setText(CmsMsg.wizardNext.lead());
-                               next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                               next.addSelectionListener((Selected) (e) -> nextPressed());
-                       }
-                       finish = new Button(buttons, SWT.PUSH);
-                       finish.setText(CmsMsg.wizardFinish.lead());
-                       finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-                       finish.addSelectionListener((Selected) (e) -> finishPressed());
-
-                       updateButtons();
-               }
-               return body;
-       }
-
-       @Override
-       public IWizardPage getCurrentPage() {
-               return currentPage;
-       }
-
-       @Override
-       public Shell getShell() {
-               return getForegoundShell();
-       }
-
-       @Override
-       public void showPage(IWizardPage page) {
-               IWizardPage[] pages = wizard.getPages();
-               int index = -1;
-               for (int i = 0; i < pages.length; i++) {
-                       if (page == pages[i]) {
-                               index = i;
-                               break;
-                       }
-               }
-               if (index < 0)
-                       throw new IllegalArgumentException("Cannot find index of wizard page " + page);
-               pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
-
-               // // clear
-               // for (Control c : body.getChildren())
-               // c.dispose();
-               // page.createControl(body);
-               // body.layout(true, true);
-               currentPageIndex = index;
-               currentPage = page;
-       }
-
-       @Override
-       public void updateButtons() {
-               if (back != null)
-                       back.setEnabled(wizard.getPreviousPage(currentPage) != null);
-               if (next != null)
-                       next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
-               if (finish != null) {
-                       finish.setEnabled(wizard.canFinish());
-               }
-       }
-
-       @Override
-       public void updateMessage() {
-               if (currentPage.getMessage() != null)
-                       message.setText(currentPage.getMessage());
-       }
-
-       @Override
-       public void updateTitleBar() {
-               if (currentPage.getTitle() != null)
-                       titleBar.setText(currentPage.getTitle());
-       }
-
-       @Override
-       public void updateWindowTitle() {
-               setTitle(wizard.getWindowTitle());
-       }
-
-       @Override
-       public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
-                       throws InvocationTargetException, InterruptedException {
-               runnable.run(null);
-       }
-
-       @Override
-       public void updateSize() {
-               // TODO pack?
-       }
-
-       protected boolean onCancel() {
-               return wizard.performCancel();
-       }
-
-       protected void nextPressed() {
-               IWizardPage page = wizard.getNextPage(currentPage);
-               showPage(page);
-               updateButtons();
-       }
-
-       protected void backPressed() {
-               IWizardPage page = wizard.getPreviousPage(currentPage);
-               showPage(page);
-               updateButtons();
-       }
-
-       protected void finishPressed() {
-               if (wizard.performFinish())
-                       closeShell(OK);
-       }
-
-       private static void setSwitchingFormData(Composite composite) {
-               FormData fdLabel = new FormData();
-               fdLabel.top = new FormAttachment(0, 0);
-               fdLabel.left = new FormAttachment(0, 0);
-               fdLabel.right = new FormAttachment(100, 0);
-               fdLabel.bottom = new FormAttachment(100, 0);
-               composite.setLayoutData(fdLabel);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java
deleted file mode 100644 (file)
index bf6417b..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic lightweight dialog, not based on JFace. */
-public class LightweightDialog {
-       private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
-
-       // must be the same value as org.eclipse.jface.window.Window#OK
-       public final static int OK = 0;
-       // must be the same value as org.eclipse.jface.window.Window#CANCEL
-       public final static int CANCEL = 1;
-
-       private Shell parentShell;
-       private Shell backgroundShell;
-       private Shell foregoundShell;
-
-       private Integer returnCode = null;
-       private boolean block = true;
-
-       private String title;
-
-       /** Tries to find a display */
-       private static Display getDisplay() {
-               try {
-                       Display display = Display.getCurrent();
-                       if (display != null)
-                               return display;
-                       else
-                               return Display.getDefault();
-               } catch (Exception e) {
-                       return Display.getCurrent();
-               }
-       }
-
-       public LightweightDialog(Shell parentShell) {
-               this.parentShell = parentShell;
-       }
-
-       public int open() {
-               if (foregoundShell != null)
-                       throw new EclipseUiException("There is already a shell");
-               backgroundShell = new Shell(parentShell, SWT.ON_TOP);
-               backgroundShell.setFullScreen(true);
-               // if (parentShell != null) {
-               // backgroundShell.setBounds(parentShell.getBounds());
-               // } else
-               // backgroundShell.setMaximized(true);
-               backgroundShell.setAlpha(128);
-               backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
-               foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
-               if (title != null)
-                       setTitle(title);
-               foregoundShell.setLayout(new GridLayout());
-               foregoundShell.setSize(getInitialSize());
-               createDialogArea(foregoundShell);
-               // shell.pack();
-               // shell.layout();
-
-               Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
-               Point dialogSize = foregoundShell.getSize();
-               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
-               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
-               foregoundShell.setLocation(x, y);
-
-               foregoundShell.addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = -2701270481953688763L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               if (hasChildShells())
-                                       return;
-                               if (returnCode == null)// not yet closed
-                                       closeShell(CANCEL);
-                       }
-
-                       @Override
-                       public void shellClosed(ShellEvent e) {
-                               notifyClose();
-                       }
-
-               });
-
-               backgroundShell.open();
-               foregoundShell.open();
-               // after the foreground shell has been opened
-               backgroundShell.addFocusListener(new FocusListener() {
-                       private static final long serialVersionUID = 3137408447474661070L;
-
-                       @Override
-                       public void focusLost(FocusEvent event) {
-                       }
-
-                       @Override
-                       public void focusGained(FocusEvent event) {
-                               if (hasChildShells())
-                                       return;
-                               if (returnCode == null)// not yet closed
-                                       closeShell(CANCEL);
-                       }
-               });
-
-               if (block) {
-                       block();
-               }
-               if (returnCode == null)
-                       returnCode = OK;
-               return returnCode;
-       }
-
-       public void block() {
-               try {
-                       runEventLoop(foregoundShell);
-               } catch (ThreadDeath t) {
-                       returnCode = CANCEL;
-                       if (log.isTraceEnabled())
-                               log.error("Thread death, canceling dialog", t);
-               } catch (Throwable t) {
-                       returnCode = CANCEL;
-                       log.error("Cannot open blocking lightweight dialog", t);
-               }
-       }
-
-       private boolean hasChildShells() {
-               if (foregoundShell == null)
-                       return false;
-               return foregoundShell.getShells().length != 0;
-       }
-
-       // public synchronized int openAndWait() {
-       // open();
-       // while (returnCode == null)
-       // try {
-       // wait(100);
-       // } catch (InterruptedException e) {
-       // // silent
-       // }
-       // return returnCode;
-       // }
-
-       private synchronized void notifyClose() {
-               if (returnCode == null)
-                       returnCode = CANCEL;
-               notifyAll();
-       }
-
-       protected void closeShell(int returnCode) {
-               this.returnCode = returnCode;
-               if (CANCEL == returnCode)
-                       onCancel();
-               if (foregoundShell != null && !foregoundShell.isDisposed()) {
-                       foregoundShell.close();
-                       foregoundShell.dispose();
-                       foregoundShell = null;
-               }
-
-               if (backgroundShell != null && !backgroundShell.isDisposed()) {
-                       backgroundShell.close();
-                       backgroundShell.dispose();
-               }
-       }
-
-       protected Point getInitialSize() {
-               // if (exception != null)
-               // return new Point(800, 600);
-               // else
-               return new Point(600, 400);
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = new Composite(parent, SWT.NONE);
-               dialogarea.setLayout(new GridLayout());
-               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               return dialogarea;
-       }
-
-       protected Shell getBackgroundShell() {
-               return backgroundShell;
-       }
-
-       protected Shell getForegoundShell() {
-               return foregoundShell;
-       }
-
-       public void setBlockOnOpen(boolean shouldBlock) {
-               block = shouldBlock;
-       }
-
-       public void pack() {
-               foregoundShell.pack();
-       }
-
-       private void runEventLoop(Shell loopShell) {
-               Display display;
-               if (foregoundShell == null) {
-                       display = Display.getCurrent();
-               } else {
-                       display = loopShell.getDisplay();
-               }
-
-               while (loopShell != null && !loopShell.isDisposed()) {
-                       try {
-                               if (!display.readAndDispatch()) {
-                                       display.sleep();
-                               }
-                       } catch (UnsupportedOperationException e) {
-                               throw e;
-                       } catch (Throwable e) {
-                               handleException(e);
-                       }
-               }
-               if (!display.isDisposed())
-                       display.update();
-       }
-
-       protected void handleException(Throwable t) {
-               if (t instanceof ThreadDeath) {
-                       // Don't catch ThreadDeath as this is a normal occurrence when
-                       // the thread dies
-                       throw (ThreadDeath) t;
-               }
-               // Try to keep running.
-               t.printStackTrace();
-       }
-
-       /** @return false, if the dialog should not be closed. */
-       protected boolean onCancel() {
-               return true;
-       }
-
-       public void setTitle(String title) {
-               this.title = title;
-               if (title != null && getForegoundShell() != null)
-                       getForegoundShell().setText(title);
-       }
-
-       public Integer getReturnCode() {
-               return returnCode;
-       }
-
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java
deleted file mode 100644 (file)
index 9404b81..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.swt.dialogs;
-
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A dialog asking a for a single value. */
-public class SingleValueDialog extends CmsMessageDialog {
-       private Text valueT;
-       private String value;
-       private String defaultValue;
-
-       public SingleValueDialog(Shell parentShell, String message) {
-               super(parentShell, message, QUESTION);
-       }
-
-       public SingleValueDialog(Shell parentShell, String message, String defaultValue) {
-               super(parentShell, message, QUESTION);
-               this.defaultValue = defaultValue;
-       }
-
-       @Override
-       protected Control createInputArea(Composite parent) {
-               valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
-               valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
-               if (defaultValue != null)
-                       valueT.setText(defaultValue);
-               return valueT;
-       }
-
-       @Override
-       protected void okPressed() {
-               value = valueT.getText();
-               super.okPressed();
-       }
-
-       public String getString() {
-               return value;
-       }
-
-       public Long getLong() {
-               return Long.valueOf(getString());
-       }
-
-       public Double getDouble() {
-               return Double.valueOf(getString());
-       }
-
-       public static String ask(String message) {
-               return ask(message, null);
-       }
-
-       public static String ask(String message, String defaultValue) {
-               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue);
-               if (svd.open() == Window.OK)
-                       return svd.getString();
-               else
-                       return null;
-       }
-
-       public static Long askLong(String message) {
-               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
-               if (svd.open() == Window.OK)
-                       return svd.getLong();
-               else
-                       return null;
-       }
-
-       public static Double askDouble(String message) {
-               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
-               if (svd.open() == Window.OK)
-                       return svd.getDouble();
-               else
-                       return null;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java
deleted file mode 100644 (file)
index ac76dba..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace dialogs. */
-package org.argeo.cms.swt.dialogs;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java
deleted file mode 100644 (file)
index 4039f2b..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.cms.swt.gcr;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.cms.acr.fs.FsContentProvider;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeItem;
-
-public class GcrContentTreeView extends Composite {
-       private Tree tree;
-       private Table table;
-       private Content rootContent;
-
-       private Content selected;
-
-       public GcrContentTreeView(Composite parent, int style, Content content) {
-               super(parent, style);
-               this.rootContent = content;
-               this.selected = rootContent;
-               setLayout(new GridLayout(2, false));
-               initTree();
-               GridData treeGd = CmsSwtUtils.fillHeight();
-               treeGd.widthHint = 300;
-               tree.setLayoutData(treeGd);
-               initTable();
-
-               table.setLayoutData(CmsSwtUtils.fillAll());
-       }
-
-       protected void initTree() {
-               tree = new Tree(this, 0);
-               for (Content c : rootContent) {
-                       TreeItem root = new TreeItem(tree, 0);
-                       root.setText(c.getName().toString());
-                       root.setData(c);
-                       new TreeItem(root, 0);
-               }
-               tree.addListener(SWT.Expand, event -> {
-                       final TreeItem root = (TreeItem) event.item;
-                       TreeItem[] items = root.getItems();
-                       for (TreeItem item : items) {
-                               if (item.getData() != null)
-                                       return;
-                               item.dispose();
-                       }
-                       Content content = (Content) root.getData();
-                       for (Content c : content) {
-                               TreeItem item = new TreeItem(root, 0);
-                               item.setText(c.getName().toString());
-                               item.setData(c);
-                               boolean hasChildren = true;
-                               if (hasChildren) {
-                                       new TreeItem(item, 0);
-                               }
-                       }
-               });
-               tree.addListener(SWT.Selection, event -> {
-                       TreeItem item = (TreeItem) event.item;
-                       selected = (Content) item.getData();
-                       refreshTable();
-               });
-       }
-
-       protected void initTable() {
-               table = new Table(this, 0);
-               table.setLinesVisible(true);
-               table.setHeaderVisible(true);
-               TableColumn keyCol = new TableColumn(table, SWT.NONE);
-               keyCol.setText("Attribute");
-               keyCol.setWidth(200);
-               TableColumn valueCol = new TableColumn(table, SWT.NONE);
-               valueCol.setText("Value");
-               keyCol.setWidth(300);
-               refreshTable();
-       }
-
-       protected void refreshTable() {
-               for (TableItem item : table.getItems()) {
-                       item.dispose();
-               }
-               for (QName key : selected.keySet()) {
-                       TableItem item = new TableItem(table, 0);
-                       item.setText(0, key.toString());
-                       Object value = selected.get(key);
-                       item.setText(1, value.toString());
-               }
-               table.getColumn(0).pack();
-               table.getColumn(1).pack();
-       }
-
-       public static void main(String[] args) {
-               Path basePath;
-               if (args.length > 0) {
-                       basePath = Paths.get(args[0]);
-               } else {
-                       basePath = Paths.get(System.getProperty("user.home"));
-               }
-
-               final Display display = new Display();
-               final Shell shell = new Shell(display);
-               shell.setText(basePath.toString());
-               shell.setLayout(new FillLayout());
-
-               FsContentProvider contentSession = new FsContentProvider(basePath);
-//             GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
-
-               shell.setSize(shell.computeSize(800, 600));
-               shell.open();
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-               display.dispose();
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java
deleted file mode 100644 (file)
index 8109c40..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.cms.swt.gcr;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.cms.MvcProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-@FunctionalInterface
-public interface SwtUiProvider extends MvcProvider<Composite, Content, Control> {
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java
deleted file mode 100644 (file)
index b9b2751..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-package org.argeo.cms.swt.osgi;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.osgi.BundleCmsTheme;
-import org.argeo.cms.swt.CmsSwtTheme;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.widgets.Display;
-
-/** Centralises some generic {@link CmsSwtTheme} patterns. */
-public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme {
-       private Map<String, ImageData> imageCache = new HashMap<>();
-
-       private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
-
-       public Image getImage(String path) {
-               if (!imageCache.containsKey(path)) {
-                       try (InputStream in = getResourceAsStream(path)) {
-                               if (in == null)
-                                       return null;
-                               ImageData imageData = new ImageData(in);
-                               imageCache.put(path, imageData);
-                       } catch (IOException e) {
-                               throw new IllegalStateException(e);
-                       }
-               }
-               ImageData imageData = imageCache.get(path);
-               Image image = new Image(Display.getCurrent(), imageData);
-               return image;
-       }
-
-       /**
-        * And icon with this file name (without the extension), with a best effort to
-        * find the appropriate size, or <code>null</code> if not found.
-        * 
-        * @param name          An icon file name without path and extension.
-        * @param preferredSize the preferred size, if <code>null</code>,
-        *                      {@link #getDefaultIconSize()} will be tried.
-        */
-       public Image getIcon(String name, Integer preferredSize) {
-               if (preferredSize == null)
-                       preferredSize = getDefaultIconSize();
-               Map<Integer, String> subCache;
-               if (!iconPaths.containsKey(name))
-                       subCache = new HashMap<>();
-               else
-                       subCache = iconPaths.get(name);
-               Image image = null;
-               if (!subCache.containsKey(preferredSize)) {
-                       Image bestMatchSoFar = null;
-                       paths: for (String p : getImagesPaths()) {
-                               int lastSlash = p.lastIndexOf('/');
-                               String fileName = p;
-                               if (lastSlash >= 0)
-                                       fileName = p.substring(lastSlash + 1);
-                               int lastDot = fileName.lastIndexOf('.');
-                               if (lastDot >= 0)
-                                       fileName = fileName.substring(0, lastDot);
-                               if (fileName.equals(name)) {// matched
-                                       Image img = getImage(p);
-                                       int width = img.getBounds().width;
-                                       if (width == preferredSize) {// perfect match
-                                               subCache.put(preferredSize, p);
-                                               image = img;
-                                               break paths;
-                                       }
-                                       if (bestMatchSoFar == null) {
-                                               bestMatchSoFar = img;
-                                       } else {
-                                               if (Math.abs(width - preferredSize) < Math
-                                                               .abs(bestMatchSoFar.getBounds().width - preferredSize))
-                                                       bestMatchSoFar = img;
-                                       }
-                               }
-                       }
-
-                       if (image == null)
-                               image = bestMatchSoFar;
-               } else {
-                       image = getImage(subCache.get(preferredSize));
-               }
-
-               if (image != null && !iconPaths.containsKey(name))
-                       iconPaths.put(name, subCache);
-
-               return image;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java
deleted file mode 100644 (file)
index ed1bfd8..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-package org.argeo.cms.swt.useradmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.LdapObjs;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.dialogs.TrayDialog;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Dialog with a user (or group) list to pick up one */
-public class PickUpUserDialog extends TrayDialog {
-       private static final long serialVersionUID = -1420106871173920369L;
-
-       // Business objects
-       private final UserAdmin userAdmin;
-       private User selectedUser;
-
-       // this page widgets and UI objects
-       private String title;
-       private LdifUsersTable userTableViewerCmp;
-       private TableViewer userViewer;
-       private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-
-       /**
-        * A dialog to pick up a group or a user, showing a table with default
-        * columns
-        */
-       public PickUpUserDialog(Shell parentShell, String title, UserAdmin userAdmin) {
-               super(parentShell);
-               this.title = title;
-               this.userAdmin = userAdmin;
-
-               columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_ICON), "",
-                               24, 24));
-               columnDefs.add(new ColumnDefinition(
-                               new UserLP(UserLP.COL_DISPLAY_NAME), "Common Name", 150, 100));
-               columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DOMAIN),
-                               "Domain", 100, 120));
-               columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DN),
-                               "Distinguished Name", 300, 100));
-       }
-
-       /** A dialog to pick up a group or a user */
-       public PickUpUserDialog(Shell parentShell, String title,
-                       UserAdmin userAdmin, List<ColumnDefinition> columnDefs) {
-               super(parentShell);
-               this.title = title;
-               this.userAdmin = userAdmin;
-               this.columnDefs = columnDefs;
-       }
-
-       @Override
-       protected void okPressed() {
-               if (getSelected() == null)
-                       MessageDialog.openError(getShell(), "No user chosen",
-                                       "Please, choose a user or press Cancel.");
-               else
-                       super.okPressed();
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogArea = (Composite) super.createDialogArea(parent);
-               dialogArea.setLayout(new FillLayout());
-
-               Composite bodyCmp = new Composite(dialogArea, SWT.NO_FOCUS);
-               bodyCmp.setLayout(new GridLayout());
-
-               // Create and configure the table
-               userTableViewerCmp = new MyUserTableViewer(bodyCmp, SWT.MULTI
-                               | SWT.H_SCROLL | SWT.V_SCROLL);
-
-               userTableViewerCmp.setColumnDefinitions(columnDefs);
-               userTableViewerCmp.populateWithStaticFilters(false, false);
-               GridData gd = EclipseUiUtils.fillAll();
-               gd.minimumHeight = 300;
-               userTableViewerCmp.setLayoutData(gd);
-               userTableViewerCmp.refresh();
-
-               // Controllers
-               userViewer = userTableViewerCmp.getTableViewer();
-               userViewer.addDoubleClickListener(new MyDoubleClickListener());
-               userViewer
-                               .addSelectionChangedListener(new MySelectionChangedListener());
-
-               parent.pack();
-               return dialogArea;
-       }
-
-       public User getSelected() {
-               if (selectedUser == null)
-                       return null;
-               else
-                       return selectedUser;
-       }
-
-       protected void configureShell(Shell shell) {
-               super.configureShell(shell);
-               shell.setText(title);
-       }
-
-       class MyDoubleClickListener implements IDoubleClickListener {
-               public void doubleClick(DoubleClickEvent evt) {
-                       if (evt.getSelection().isEmpty())
-                               return;
-
-                       Object obj = ((IStructuredSelection) evt.getSelection())
-                                       .getFirstElement();
-                       if (obj instanceof User) {
-                               selectedUser = (User) obj;
-                               okPressed();
-                       }
-               }
-       }
-
-       class MySelectionChangedListener implements ISelectionChangedListener {
-               @Override
-               public void selectionChanged(SelectionChangedEvent event) {
-                       if (event.getSelection().isEmpty()) {
-                               selectedUser = null;
-                               return;
-                       }
-                       Object obj = ((IStructuredSelection) event.getSelection())
-                                       .getFirstElement();
-                       if (obj instanceof Group) {
-                               selectedUser = (Group) obj;
-                       }
-               }
-       }
-
-       private class MyUserTableViewer extends LdifUsersTable {
-               private static final long serialVersionUID = 8467999509931900367L;
-
-               private final String[] knownProps = { LdapAttrs.uid.name(),
-                               LdapAttrs.cn.name(), LdapAttrs.DN };
-
-               private Button showSystemRoleBtn;
-               private Button showUserBtn;
-
-               public MyUserTableViewer(Composite parent, int style) {
-                       super(parent, style);
-               }
-
-               protected void populateStaticFilters(Composite staticFilterCmp) {
-                       staticFilterCmp.setLayout(new GridLayout());
-                       showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK);
-                       showSystemRoleBtn.setText("Show system roles  ");
-
-                       showUserBtn = new Button(staticFilterCmp, SWT.CHECK);
-                       showUserBtn.setText("Show users  ");
-
-                       SelectionListener sl = new SelectionAdapter() {
-                               private static final long serialVersionUID = -7033424592697691676L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       refresh();
-                               }
-                       };
-
-                       showSystemRoleBtn.addSelectionListener(sl);
-                       showUserBtn.addSelectionListener(sl);
-               }
-
-               @Override
-               protected List<User> listFilteredElements(String filter) {
-                       Role[] roles;
-                       try {
-                               StringBuilder builder = new StringBuilder();
-
-                               StringBuilder filterBuilder = new StringBuilder();
-                               if (notNull(filter))
-                                       for (String prop : knownProps) {
-                                               filterBuilder.append("(");
-                                               filterBuilder.append(prop);
-                                               filterBuilder.append("=*");
-                                               filterBuilder.append(filter);
-                                               filterBuilder.append("*)");
-                                       }
-
-                               String typeStr = "(" + LdapAttrs.objectClass.name() + "="
-                                               + LdapObjs.groupOfNames.name() + ")";
-                               if ((showUserBtn.getSelection()))
-                                       typeStr = "(|(" + LdapAttrs.objectClass.name() + "="
-                                                       + LdapObjs.inetOrgPerson.name() + ")" + typeStr
-                                                       + ")";
-
-                               if (!showSystemRoleBtn.getSelection())
-                                       typeStr = "(& " + typeStr + "(!(" + LdapAttrs.DN + "=*"
-                                                       + CmsConstants.ROLES_BASEDN + ")))";
-
-                               if (filterBuilder.length() > 1) {
-                                       builder.append("(&" + typeStr);
-                                       builder.append("(|");
-                                       builder.append(filterBuilder.toString());
-                                       builder.append("))");
-                               } else {
-                                       builder.append(typeStr);
-                               }
-                               roles = userAdmin.getRoles(builder.toString());
-                       } catch (InvalidSyntaxException e) {
-                               throw new EclipseUiException(
-                                               "Unable to get roles with filter: " + filter, e);
-                       }
-                       List<User> users = new ArrayList<User>();
-                       for (Role role : roles)
-                               if (!users.contains(role))
-                                       users.add((User) role);
-                       return users;
-               }
-       }
-
-       private boolean notNull(String string) {
-               if (string == null)
-                       return false;
-               else
-                       return !"".equals(string.trim());
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java
deleted file mode 100644 (file)
index d1c90a4..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.swt.useradmin;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** Centralize label providers for the group table */
-class UserLP extends ColumnLabelProvider {
-       private static final long serialVersionUID = -4645930210988368571L;
-
-       final static String COL_ICON = "colID.icon";
-       final static String COL_DN = "colID.dn";
-       final static String COL_DISPLAY_NAME = "colID.displayName";
-       final static String COL_DOMAIN = "colID.domain";
-
-       final String currType;
-
-       // private Font italic;
-       private Font bold;
-
-       UserLP(String colId) {
-               this.currType = colId;
-       }
-
-       @Override
-       public Font getFont(Object element) {
-               // Current user as bold
-               if (UserAdminUtils.isCurrentUser(((User) element))) {
-                       if (bold == null)
-                               bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
-                                               .createFont(Display.getCurrent());
-                       return bold;
-               }
-               return null;
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               if (COL_ICON.equals(currType)) {
-                       User user = (User) element;
-                       String dn = user.getName();
-                       if (dn.endsWith(CmsConstants.ROLES_BASEDN))
-                               return UsersImages.ICON_ROLE;
-                       else if (user.getType() == Role.GROUP)
-                               return UsersImages.ICON_GROUP;
-                       else
-                               return UsersImages.ICON_USER;
-               } else
-                       return null;
-       }
-
-       @Override
-       public String getText(Object element) {
-               User user = (User) element;
-               return getText(user);
-
-       }
-
-       public String getText(User user) {
-               if (COL_DN.equals(currType))
-                       return user.getName();
-               else if (COL_DISPLAY_NAME.equals(currType))
-                       return UserAdminUtils.getCommonName(user);
-               else if (COL_DOMAIN.equals(currType))
-                       return UserAdminUtils.getDomainName(user);
-               else
-                       return "";
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java
deleted file mode 100644 (file)
index 21fc5af..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java
deleted file mode 100644 (file)
index 3597bfc..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace users management components. */
-package org.argeo.cms.swt.useradmin;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java
deleted file mode 100644 (file)
index 1c4d79e..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-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/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java
deleted file mode 100644 (file)
index 7d3a260..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS core theme images. */
-package org.argeo.cms.ui.theme;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java
deleted file mode 100644 (file)
index c882eb7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * Tree content provider dealing with tree objects and providing reasonable
- * defaults.
- */
-public abstract class AbstractTreeContentProvider implements
-               ITreeContentProvider {
-       private static final long serialVersionUID = 8246126401957763868L;
-
-       /** Does nothing */
-       public void dispose() {
-       }
-
-       /** Does nothing */
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-       }
-
-       public Object[] getChildren(Object element) {
-               if (element instanceof TreeParent) {
-                       return ((TreeParent) element).getChildren();
-               }
-               return new Object[0];
-       }
-
-       public Object getParent(Object element) {
-               if (element instanceof TreeParent) {
-                       return ((TreeParent) element).getParent();
-               }
-               return null;
-       }
-
-       public boolean hasChildren(Object element) {
-               if (element instanceof TreeParent) {
-                       return ((TreeParent) element).hasChildren();
-               }
-               return false;
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java
deleted file mode 100644 (file)
index a38552c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/**
- * Wraps the definition of a column to be used in the various JFace viewers
- * (typically tree and table). It enables definition of generic viewers which
- * column can be then defined externally. Also used to generate export.
- */
-public class ColumnDefinition {
-       private ColumnLabelProvider labelProvider;
-       private String label;
-       private int weight = 0;
-       private int minWidth = 120;
-
-       public ColumnDefinition(ColumnLabelProvider labelProvider, String label) {
-               this.labelProvider = labelProvider;
-               this.label = label;
-       }
-
-       public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
-                       int weight) {
-               this.labelProvider = labelProvider;
-               this.label = label;
-               this.weight = weight;
-               this.minWidth = weight;
-       }
-
-       public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
-                       int weight, int minimumWidth) {
-               this.labelProvider = labelProvider;
-               this.label = label;
-               this.weight = weight;
-               this.minWidth = minimumWidth;
-       }
-
-       public ColumnLabelProvider getLabelProvider() {
-               return labelProvider;
-       }
-
-       public void setLabelProvider(ColumnLabelProvider labelProvider) {
-               this.labelProvider = labelProvider;
-       }
-
-       public String getLabel() {
-               return label;
-       }
-
-       public void setLabel(String label) {
-               this.label = label;
-       }
-
-       public int getWeight() {
-               return weight;
-       }
-
-       public void setWeight(int weight) {
-               this.weight = weight;
-       }
-
-       public int getMinWidth() {
-               return minWidth;
-       }
-
-       public void setMinWidth(int minWidth) {
-               this.minWidth = minWidth;
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java
deleted file mode 100644 (file)
index 9430a20..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-
-/** Generic column viewer sorter */
-public class ColumnViewerComparator extends ViewerComparator {
-       private static final long serialVersionUID = -2266218906355859909L;
-
-       public static final int ASC = 1;
-
-       public static final int NONE = 0;
-
-       public static final int DESC = -1;
-
-       private int direction = 0;
-
-       private TableViewerColumn column;
-
-       private ColumnViewer viewer;
-
-       public ColumnViewerComparator(TableViewerColumn column) {
-               super(null);
-               this.column = column;
-               this.viewer = column.getViewer();
-               this.column.getColumn().addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = 7586796298965472189L;
-
-                       public void widgetSelected(SelectionEvent e) {
-                               if (ColumnViewerComparator.this.viewer.getComparator() != null) {
-                                       if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) {
-                                               int tdirection = ColumnViewerComparator.this.direction;
-
-                                               if (tdirection == ASC) {
-                                                       setSortDirection(DESC);
-                                               } else if (tdirection == DESC) {
-                                                       setSortDirection(NONE);
-                                               }
-                                       } else {
-                                               setSortDirection(ASC);
-                                       }
-                               } else {
-                                       setSortDirection(ASC);
-                               }
-                       }
-               });
-       }
-
-       private void setSortDirection(int direction) {
-               if (direction == NONE) {
-                       column.getColumn().getParent().setSortColumn(null);
-                       column.getColumn().getParent().setSortDirection(SWT.NONE);
-                       viewer.setComparator(null);
-               } else {
-                       column.getColumn().getParent().setSortColumn(column.getColumn());
-                       this.direction = direction;
-
-                       if (direction == ASC) {
-                               column.getColumn().getParent().setSortDirection(SWT.DOWN);
-                       } else {
-                               column.getColumn().getParent().setSortDirection(SWT.UP);
-                       }
-
-                       if (viewer.getComparator() == this) {
-                               viewer.refresh();
-                       } else {
-                               viewer.setComparator(this);
-                       }
-
-               }
-       }
-
-       public int compare(Viewer viewer, Object e1, Object e2) {
-               return direction * super.compare(viewer, e1, e2);
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java
deleted file mode 100644 (file)
index 37a36e8..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.eclipse.ui;
-
-/** CMS specific exceptions. */
-public class EclipseUiException extends RuntimeException {
-       private static final long serialVersionUID = -5341764743356771313L;
-
-       public EclipseUiException(String message) {
-               super(message);
-       }
-
-       public EclipseUiException(String message, Throwable e) {
-               super(message, e);
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java
deleted file mode 100644 (file)
index 95b45fe..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.resource.JFaceResources;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Utilities to simplify UI development. */
-public class EclipseUiUtils {
-
-       /** Dispose all children of a Composite */
-       public static void clear(Composite composite) {
-               for (Control child : composite.getChildren())
-                       child.dispose();
-       }
-
-       /**
-        * Enables efficient call to the layout method of a composite, refreshing only
-        * some of the children controls.
-        */
-       public static void layout(Composite parent, Control... toUpdateControls) {
-               parent.layout(toUpdateControls);
-       }
-
-       //
-       // FONTS
-       //
-       /** Shortcut to retrieve default italic font from display */
-       public static Font getItalicFont(Composite parent) {
-               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC)
-                               .createFont(parent.getDisplay());
-       }
-
-       /** Shortcut to retrieve default bold font from display */
-       public static Font getBoldFont(Composite parent) {
-               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
-                               .createFont(parent.getDisplay());
-       }
-
-       /** Shortcut to retrieve default bold italic font from display */
-       public static Font getBoldItalicFont(Composite parent) {
-               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC)
-                               .createFont(parent.getDisplay());
-       }
-
-       //
-       // Simplify grid layouts management
-       //
-       public static GridLayout noSpaceGridLayout() {
-               return noSpaceGridLayout(new GridLayout());
-       }
-
-       public static GridLayout noSpaceGridLayout(int columns) {
-               return noSpaceGridLayout(new GridLayout(columns, false));
-       }
-
-       public static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static GridData fillWidth() {
-               return grabWidth(SWT.FILL, SWT.FILL);
-       }
-
-       public static GridData fillWidth(int colSpan) {
-               GridData gd = grabWidth(SWT.FILL, SWT.FILL);
-               gd.horizontalSpan = colSpan;
-               return gd;
-       }
-
-       public static GridData fillAll() {
-               return new GridData(SWT.FILL, SWT.FILL, true, true);
-       }
-
-       public static GridData fillAll(int colSpan, int rowSpan) {
-               return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan);
-       }
-
-       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
-               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
-       }
-
-       //
-       // Simplify Form layout management
-       //
-
-       /**
-        * Creates a basic form data that is attached to the 4 corners of the parent
-        * composite
-        */
-       public static FormData fillFormData() {
-               FormData formData = new FormData();
-               formData.top = new FormAttachment(0, 0);
-               formData.left = new FormAttachment(0, 0);
-               formData.right = new FormAttachment(100, 0);
-               formData.bottom = new FormAttachment(100, 0);
-               return formData;
-       }
-
-       /**
-        * Create a label and a text field for a grid layout, the text field grabbing
-        * excess horizontal
-        * 
-        * @param parent
-        *            the parent composite
-        * @param label
-        *            the label to display
-        * @param modifyListener
-        *            a {@link ModifyListener} to listen on events on the text, can be
-        *            null
-        * @return the created text
-        * 
-        */
-       // FIXME why was this deprecated.
-       // * @ deprecated use { @ link #createGridLT(Composite, String)} instead
-       // @ Deprecated
-       public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) {
-               Label lbl = new Label(parent, SWT.LEAD);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
-               Text txt = new Text(parent, SWT.LEAD | SWT.BORDER);
-               txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               if (modifyListener != null)
-                       txt.addModifyListener(modifyListener);
-               return txt;
-       }
-
-       /**
-        * Create a label and a text field for a grid layout, the text field grabbing
-        * excess horizontal
-        */
-       public static Text createGridLT(Composite parent, String label) {
-               return createGridLT(parent, label, null);
-       }
-
-       /**
-        * Creates one label and a text field not editable with background colour of the
-        * parent (like a label but with selectable text)
-        */
-       public static Text createGridLL(Composite parent, String label, String text) {
-               Text txt = createGridLT(parent, label);
-               txt.setText(text);
-               txt.setEditable(false);
-               txt.setBackground(parent.getBackground());
-               return txt;
-       }
-
-       /**
-        * Create a label and a text field with password display for a grid layout, the
-        * text field grabbing excess horizontal
-        */
-       public static Text createGridLP(Composite parent, String label) {
-               return createGridLP(parent, label, null);
-       }
-
-       /**
-        * Create a label and a text field with password display for a grid layout, the
-        * text field grabbing excess horizontal. The given modify listener will be
-        * added to the newly created text field if not null.
-        */
-       public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) {
-               Label lbl = new Label(parent, SWT.LEAD);
-               lbl.setText(label);
-               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
-               Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD);
-               txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
-               if (modifyListener != null)
-                       txt.addModifyListener(modifyListener);
-               return txt;
-       }
-
-       // MISCELLANEOUS
-
-       /** Simply checks if a string is not null nor empty */
-       public static boolean notEmpty(String stringToTest) {
-               return !(stringToTest == null || "".equals(stringToTest.trim()));
-       }
-
-       /** Simply checks if a string is null or empty */
-       public static boolean isEmpty(String stringToTest) {
-               return stringToTest == null || "".equals(stringToTest.trim());
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java
deleted file mode 100644 (file)
index e82505d..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import java.io.InputStream;
-
-/**
- * Used for file download : subclasses must implement model specific methods to
- * get a byte array representing a file given is ID.
- */
-@Deprecated
-public interface FileProvider {
-
-       public byte[] getByteArrayFileFromId(String fileId);
-
-       public InputStream getInputStreamFromFileId(String fileId);
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java
deleted file mode 100644 (file)
index e1d8b05..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-
-public abstract class GenericTableComparator extends ViewerComparator {
-       private static final long serialVersionUID = -1175894935075325810L;
-       protected int propertyIndex;
-       public static final int ASCENDING = 0, DESCENDING = 1;
-       protected int direction = DESCENDING;
-
-       /**
-        * Creates an instance of a sorter for TableViewer.
-        * 
-        * @param defaultColumnIndex
-        *            the default sorter column
-        */
-
-       public GenericTableComparator(int defaultColumnIndex, int direction) {
-               propertyIndex = defaultColumnIndex;
-               this.direction = direction;
-       }
-
-       public void setColumn(int column) {
-               if (column == this.propertyIndex) {
-                       // Same column as last sort; toggle the direction
-                       direction = 1 - direction;
-               } else {
-                       // New column; do a descending sort
-                       this.propertyIndex = column;
-                       direction = DESCENDING;
-               }
-       }
-
-       /**
-        * Must be Overriden in each view.
-        */
-       public abstract int compare(Viewer viewer, Object e1, Object e2);
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java
deleted file mode 100644 (file)
index ac7b2d8..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import java.util.List;
-
-/**
- * Views and editors can implement this interface so that one of the list that
- * is displayed in the part (For instance in a Table or a Tree Viewer) can be
- * rebuilt externally. Typically to generate csv or calc extract.
- */
-public interface IListProvider {
-       /**
-        * Returns an array of current and relevant elements
-        */
-       public Object[] getElements(String extractId);
-
-       /**
-        * Returns the column definition for passed ID
-        */
-       public List<ColumnDefinition> getColumnDefinition(String extractId);
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java
deleted file mode 100644 (file)
index cf3c157..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.eclipse.ui;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Parent / children semantic to be used for simple UI Tree structure */
-public class TreeParent {
-       private String name;
-       private TreeParent parent;
-
-       private List<Object> children;
-
-       /**
-        * Unique id within the context of a tree display. If set, equals() and
-        * hashCode() methods will be based on it
-        */
-       private String path = null;
-
-       /** False until at least one child has been added, then true until cleared */
-       private boolean loaded = false;
-
-       public TreeParent(String name) {
-               this.name = name;
-               children = new ArrayList<Object>();
-       }
-
-       public synchronized void addChild(Object child) {
-               loaded = true;
-               children.add(child);
-               if (child instanceof TreeParent)
-                       ((TreeParent) child).setParent(this);
-       }
-
-       /**
-        * Remove this child. The child is disposed.
-        */
-       public synchronized void removeChild(Object child) {
-               children.remove(child);
-               if (child instanceof TreeParent) {
-                       ((TreeParent) child).dispose();
-               }
-       }
-
-       public synchronized void clearChildren() {
-               for (Object obj : children) {
-                       if (obj instanceof TreeParent)
-                               ((TreeParent) obj).dispose();
-               }
-               loaded = false;
-               children.clear();
-       }
-
-       /**
-        * If overridden, <code>super.dispose()</code> must be called, typically
-        * after custom cleaning.
-        */
-       public synchronized void dispose() {
-               clearChildren();
-               parent = null;
-               children = null;
-       }
-
-       public synchronized Object[] getChildren() {
-               return children.toArray(new Object[children.size()]);
-       }
-
-       @SuppressWarnings("unchecked")
-       public synchronized <T> List<T> getChildrenOfType(Class<T> clss) {
-               List<T> lst = new ArrayList<T>();
-               for (Object obj : children) {
-                       if (clss.isAssignableFrom(obj.getClass()))
-                               lst.add((T) obj);
-               }
-               return lst;
-       }
-
-       public synchronized boolean hasChildren() {
-               return children.size() > 0;
-       }
-
-       public Object getChildByName(String name) {
-               for (Object child : children) {
-                       if (child.toString().equals(name))
-                               return child;
-               }
-               return null;
-       }
-
-       public synchronized Boolean isLoaded() {
-               return loaded;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setParent(TreeParent parent) {
-               this.parent = parent;
-               if (parent != null && parent.path != null)
-                       this.path = parent.path + '/' + name;
-               else
-                       this.path = '/' + name;
-       }
-
-       public TreeParent getParent() {
-               return parent;
-       }
-
-       public String toString() {
-               return getName();
-       }
-
-       public int compareTo(TreeParent o) {
-               return name.compareTo(o.name);
-       }
-
-       @Override
-       public int hashCode() {
-               if (path != null)
-                       return path.hashCode();
-               else
-                       return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (path != null && obj instanceof TreeParent)
-                       return path.equals(((TreeParent) obj).path);
-               else
-                       return name.equals(obj.toString());
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java
deleted file mode 100644 (file)
index a388e74..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.jface.dialogs.IMessageProvider;
-import org.eclipse.jface.dialogs.TitleAreaDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Generic error dialog to be used in try/catch blocks.
- * 
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class ErrorFeedback extends TitleAreaDialog {
-       private static final long serialVersionUID = -8918084784628179044L;
-
-       private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class);
-
-       private final String message;
-       private final Throwable exception;
-
-       public static void show(String message, Throwable e) {
-               // rethrow ThreaDeath in order to make sure that RAP will properly clean
-               // up the UI thread
-               if (e instanceof ThreadDeath)
-                       throw (ThreadDeath) e;
-
-               new ErrorFeedback(newShell(), message, e).open();
-       }
-
-       public static void show(String message) {
-               new ErrorFeedback(newShell(), message, null).open();
-       }
-
-       private static Shell newShell() {
-               return new Shell(getDisplay(), SWT.NO_TRIM);
-       }
-
-       /** Tries to find a display */
-       private static Display getDisplay() {
-               try {
-                       Display display = Display.getCurrent();
-                       if (display != null)
-                               return display;
-                       else
-                               return Display.getDefault();
-               } catch (Exception e) {
-                       return Display.getCurrent();
-               }
-       }
-
-       public ErrorFeedback(Shell parentShell, String message, Throwable e) {
-               super(parentShell);
-               setShellStyle(SWT.NO_TRIM);
-               this.message = message;
-               this.exception = e;
-               log.error(message, e);
-       }
-
-       protected Point getInitialSize() {
-               if (exception != null)
-                       return new Point(800, 600);
-               else
-                       return new Point(400, 300);
-       }
-
-       @Override
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = (Composite) super.createDialogArea(parent);
-               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               Composite composite = new Composite(dialogarea, SWT.NONE);
-               composite.setLayout(new GridLayout(2, false));
-               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "")
-                               : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR);
-
-               if (exception != null) {
-                       Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
-                       stack.setEditable(false);
-                       stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       StringWriter sw = new StringWriter();
-                       exception.printStackTrace(new PrintWriter(sw));
-                       stack.setText(sw.toString());
-               }
-
-               parent.pack();
-               return composite;
-       }
-
-       protected void configureShell(Shell shell) {
-               super.configureShell(shell);
-               shell.setText("Error");
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java
deleted file mode 100644 (file)
index f2715bc..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Generic lightweight dialog, not based on JFace.
- * 
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class FeedbackDialog extends LightweightDialog {
-       private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class);
-
-       private String message;
-       private Throwable exception;
-
-//     private Shell parentShell;
-       private Shell shell;
-
-       public static void show(String message, Throwable e) {
-               // rethrow ThreaDeath in order to make sure that RAP will properly clean
-               // up the UI thread
-               if (e instanceof ThreadDeath)
-                       throw (ThreadDeath) e;
-
-               new FeedbackDialog(getDisplay().getActiveShell(), message, e).open();
-       }
-
-       public static void show(String message) {
-               new FeedbackDialog(getDisplay().getActiveShell(), message, null).open();
-       }
-
-       /** Tries to find a display */
-       private static Display getDisplay() {
-               try {
-                       Display display = Display.getCurrent();
-                       if (display != null)
-                               return display;
-                       else
-                               return Display.getDefault();
-               } catch (Exception e) {
-                       return Display.getCurrent();
-               }
-       }
-
-       public FeedbackDialog(Shell parentShell, String message, Throwable e) {
-               super(parentShell);
-               this.message = message;
-               this.exception = e;
-               log.error(message, e);
-       }
-
-       public int open() {
-               if (shell != null)
-                       throw new EclipseUiException("There is already a shell");
-               shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               shell.setLayout(new GridLayout());
-               // shell.setText("Error");
-               shell.setSize(getInitialSize());
-               createDialogArea(shell);
-               // shell.pack();
-               // shell.layout();
-
-               Rectangle shellBounds = Display.getCurrent().getBounds();// RAP
-               Point dialogSize = shell.getSize();
-               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
-               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
-               shell.setLocation(x, y);
-
-               shell.addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = -2701270481953688763L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               closeShell();
-                       }
-               });
-
-               shell.open();
-               return OK;
-       }
-
-       protected void closeShell() {
-               shell.close();
-               shell.dispose();
-               shell = null;
-       }
-
-       protected Point getInitialSize() {
-               // if (exception != null)
-               // return new Point(800, 600);
-               // else
-               return new Point(400, 300);
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = new Composite(parent, SWT.NONE);
-               dialogarea.setLayout(new GridLayout());
-               // Composite dialogarea = (Composite) super.createDialogArea(parent);
-               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               Label messageLbl = new Label(dialogarea, SWT.NONE);
-               if (message != null)
-                       messageLbl.setText(message);
-               else if (exception != null)
-                       messageLbl.setText(exception.getLocalizedMessage());
-
-               Composite composite = new Composite(dialogarea, SWT.NONE);
-               composite.setLayout(new GridLayout(2, false));
-               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-               if (exception != null) {
-                       Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
-                       stack.setEditable(false);
-                       stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-                       StringWriter sw = new StringWriter();
-                       exception.printStackTrace(new PrintWriter(sw));
-                       stack.setText(sw.toString());
-               }
-
-               // parent.pack();
-               return composite;
-       }
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java
deleted file mode 100644 (file)
index 615e141..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic lightweight dialog, not based on JFace. */
-@Deprecated
-public class LightweightDialog {
-       private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
-
-       // must be the same value as org.eclipse.jface.window.Window#OK
-       public final static int OK = 0;
-       // must be the same value as org.eclipse.jface.window.Window#CANCEL
-       public final static int CANCEL = 1;
-
-       private Shell parentShell;
-       private Shell backgroundShell;
-       private Shell foregoundShell;
-
-       private Integer returnCode = null;
-       private boolean block = true;
-
-       private String title;
-
-       /** Tries to find a display */
-       private static Display getDisplay() {
-               try {
-                       Display display = Display.getCurrent();
-                       if (display != null)
-                               return display;
-                       else
-                               return Display.getDefault();
-               } catch (Exception e) {
-                       return Display.getCurrent();
-               }
-       }
-
-       public LightweightDialog(Shell parentShell) {
-               this.parentShell = parentShell;
-       }
-
-       public int open() {
-               if (foregoundShell != null)
-                       throw new EclipseUiException("There is already a shell");
-               backgroundShell = new Shell(parentShell, SWT.ON_TOP);
-               backgroundShell.setFullScreen(true);
-               // if (parentShell != null) {
-               // backgroundShell.setBounds(parentShell.getBounds());
-               // } else
-               // backgroundShell.setMaximized(true);
-               backgroundShell.setAlpha(128);
-               backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
-               foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
-               if (title != null)
-                       setTitle(title);
-               foregoundShell.setLayout(new GridLayout());
-               foregoundShell.setSize(getInitialSize());
-               createDialogArea(foregoundShell);
-               // shell.pack();
-               // shell.layout();
-
-               Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP
-               Point dialogSize = foregoundShell.getSize();
-               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
-               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
-               foregoundShell.setLocation(x, y);
-
-               foregoundShell.addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = -2701270481953688763L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               if (hasChildShells())
-                                       return;
-                               if (returnCode == null)// not yet closed
-                                       closeShell(CANCEL);
-                       }
-
-                       @Override
-                       public void shellClosed(ShellEvent e) {
-                               notifyClose();
-                       }
-
-               });
-
-               backgroundShell.open();
-               foregoundShell.open();
-               // after the foreground shell has been opened
-               backgroundShell.addFocusListener(new FocusListener() {
-                       private static final long serialVersionUID = 3137408447474661070L;
-
-                       @Override
-                       public void focusLost(FocusEvent event) {
-                       }
-
-                       @Override
-                       public void focusGained(FocusEvent event) {
-                               if (hasChildShells())
-                                       return;
-                               if (returnCode == null)// not yet closed
-                                       closeShell(CANCEL);
-                       }
-               });
-
-               if (block) {
-                       block();
-               }
-               if (returnCode == null)
-                       returnCode = OK;
-               return returnCode;
-       }
-
-       public void block() {
-               try {
-                       runEventLoop(foregoundShell);
-               } catch (ThreadDeath t) {
-                       returnCode = CANCEL;
-                       if (log.isTraceEnabled())
-                               log.error("Thread death, canceling dialog", t);
-               } catch (Throwable t) {
-                       returnCode = CANCEL;
-                       log.error("Cannot open blocking lightweight dialog", t);
-               }
-       }
-
-       private boolean hasChildShells() {
-               if (foregoundShell == null)
-                       return false;
-               return foregoundShell.getShells().length != 0;
-       }
-
-       // public synchronized int openAndWait() {
-       // open();
-       // while (returnCode == null)
-       // try {
-       // wait(100);
-       // } catch (InterruptedException e) {
-       // // silent
-       // }
-       // return returnCode;
-       // }
-
-       private synchronized void notifyClose() {
-               if (returnCode == null)
-                       returnCode = CANCEL;
-               notifyAll();
-       }
-
-       protected void closeShell(int returnCode) {
-               this.returnCode = returnCode;
-               if (CANCEL == returnCode)
-                       onCancel();
-               if (foregoundShell != null && !foregoundShell.isDisposed()) {
-                       foregoundShell.close();
-                       foregoundShell.dispose();
-                       foregoundShell = null;
-               }
-
-               if (backgroundShell != null && !backgroundShell.isDisposed()) {
-                       backgroundShell.close();
-                       backgroundShell.dispose();
-               }
-       }
-
-       protected Point getInitialSize() {
-               // if (exception != null)
-               // return new Point(800, 600);
-               // else
-               return new Point(600, 400);
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = new Composite(parent, SWT.NONE);
-               dialogarea.setLayout(new GridLayout());
-               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               return dialogarea;
-       }
-
-       protected Shell getBackgroundShell() {
-               return backgroundShell;
-       }
-
-       protected Shell getForegoundShell() {
-               return foregoundShell;
-       }
-
-       public void setBlockOnOpen(boolean shouldBlock) {
-               block = shouldBlock;
-       }
-
-       public void pack() {
-               foregoundShell.pack();
-       }
-
-       private void runEventLoop(Shell loopShell) {
-               Display display;
-               if (foregoundShell == null) {
-                       display = Display.getCurrent();
-               } else {
-                       display = loopShell.getDisplay();
-               }
-
-               while (loopShell != null && !loopShell.isDisposed()) {
-                       try {
-                               if (!display.readAndDispatch()) {
-                                       display.sleep();
-                               }
-                       } catch (UnsupportedOperationException e) {
-                               throw e;
-                       } catch (Throwable e) {
-                               handleException(e);
-                       }
-               }
-               if (!display.isDisposed())
-                       display.update();
-       }
-
-       protected void handleException(Throwable t) {
-               if (t instanceof ThreadDeath) {
-                       // Don't catch ThreadDeath as this is a normal occurrence when
-                       // the thread dies
-                       throw (ThreadDeath) t;
-               }
-               // Try to keep running.
-               t.printStackTrace();
-       }
-
-       /** @return false, if the dialog should not be closed. */
-       protected boolean onCancel() {
-               return true;
-       }
-
-       public void setTitle(String title) {
-               this.title = title;
-               if (title != null && getForegoundShell() != null)
-                       getForegoundShell().setText(title);
-       }
-
-       public Integer getReturnCode() {
-               return returnCode;
-       }
-
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java
deleted file mode 100644 (file)
index 8ce9b44..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.dialogs.IMessageProvider;
-import org.eclipse.jface.dialogs.TitleAreaDialog;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Dialog to retrieve a single value.
- * 
- * @deprecated Use CMS dialogs instead.
- */
-@Deprecated
-public class SingleValue extends TitleAreaDialog {
-       private static final long serialVersionUID = 2843538207460082349L;
-
-       private Text valueT;
-       private String value;
-       private final String title, message, label;
-       private final Boolean multiline;
-
-       public static String ask(String label, String message) {
-               SingleValue svd = new SingleValue(label, message);
-               if (svd.open() == Window.OK)
-                       return svd.getString();
-               else
-                       return null;
-       }
-
-       public static Long askLong(String label, String message) {
-               SingleValue svd = new SingleValue(label, message);
-               if (svd.open() == Window.OK)
-                       return svd.getLong();
-               else
-                       return null;
-       }
-
-       public static Double askDouble(String label, String message) {
-               SingleValue svd = new SingleValue(label, message);
-               if (svd.open() == Window.OK)
-                       return svd.getDouble();
-               else
-                       return null;
-       }
-
-       public SingleValue(String label, String message) {
-               this(Display.getDefault().getActiveShell(), label, message, label, false);
-       }
-
-       public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) {
-               super(parentShell);
-               this.title = title;
-               this.message = message;
-               this.label = label;
-               this.multiline = multiline;
-       }
-
-       protected Point getInitialSize() {
-               if (multiline)
-                       return new Point(450, 350);
-
-               else
-                       return new Point(400, 270);
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = (Composite) super.createDialogArea(parent);
-               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               Composite composite = new Composite(dialogarea, SWT.NONE);
-               composite.setLayoutData(EclipseUiUtils.fillAll());
-               GridLayout layout = new GridLayout(2, false);
-               layout.marginWidth = layout.marginHeight = 20;
-               composite.setLayout(layout);
-
-               valueT = createLT(composite, label);
-
-               setMessage(message, IMessageProvider.NONE);
-
-               parent.pack();
-               valueT.setFocus();
-               return composite;
-       }
-
-       @Override
-       protected void okPressed() {
-               value = valueT.getText();
-               super.okPressed();
-       }
-
-       /** Creates label and text. */
-       protected Text createLT(Composite parent, String label) {
-               new Label(parent, SWT.NONE).setText(label);
-               Text text;
-               if (multiline) {
-                       text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI);
-                       text.setLayoutData(EclipseUiUtils.fillAll());
-               } else {
-                       text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
-                       text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
-               }
-               return text;
-       }
-
-       protected void configureShell(Shell shell) {
-               super.configureShell(shell);
-               shell.setText(title);
-       }
-
-       public String getString() {
-               return value;
-       }
-
-       public Long getLong() {
-               return Long.valueOf(getString());
-       }
-
-       public Double getDouble() {
-               return Double.valueOf(getString());
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java
deleted file mode 100644 (file)
index d6ab148..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace dialogs. */
-package org.argeo.eclipse.ui.dialogs;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java
deleted file mode 100644 (file)
index c01b2d7..0000000
+++ /dev/null
@@ -1,450 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.LinkedHashMap;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Text;
-
-/** Simple UI provider that populates a composite parent given a NIO path */
-public class AdvancedFsBrowser {
-       private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class);
-
-       // Some local constants to experiment. should be cleaned
-       // private final static int THUMBNAIL_WIDTH = 400;
-       // private Point imageWidth = new Point(250, 0);
-       private final static int COLUMN_WIDTH = 160;
-
-       private Path initialPath;
-       private Path currEdited;
-       // Filter
-       private Composite displayBoxCmp;
-       private Text parentPathTxt;
-       private Text filterTxt;
-       // Browser columns
-       private ScrolledComposite scrolledCmp;
-       // Keep a cache of the opened directories
-       private LinkedHashMap<Path, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<>();
-       private Composite scrolledCmpBody;
-
-       public Control createUi(Composite parent, Path basePath) {
-               if (basePath == null)
-                       throw new IllegalArgumentException("Context cannot be null");
-               parent.setLayout(new GridLayout());
-
-               // top filter
-               Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
-               filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
-               addFilterPanel(filterCmp);
-
-               // Bottom part a sash with browser on the left
-               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
-               // form.setLayout(new FillLayout());
-               form.setLayoutData(EclipseUiUtils.fillAll());
-               Composite leftCmp = new Composite(form, SWT.NO_FOCUS);
-               displayBoxCmp = new Composite(form, SWT.NONE);
-               form.setWeights(new int[] { 3, 1 });
-
-               createBrowserPart(leftCmp, basePath);
-               // leftCmp.addControlListener(new ControlAdapter() {
-               // @Override
-               // public void controlResized(ControlEvent e) {
-               // Rectangle r = leftCmp.getClientArea();
-               // log.warn("Browser resized: " + r.toString());
-               // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
-               // SWT.DEFAULT);
-               // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
-               // // r.height));
-               // }
-               // });
-
-               populateCurrEditedDisplay(displayBoxCmp, basePath);
-
-               // INIT
-               setEdited(basePath);
-               initialPath = basePath;
-               // form.layout(true, true);
-               return parent;
-       }
-
-       private void createBrowserPart(Composite parent, Path context) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               // scrolled composite
-               scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
-               scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               scrolledCmp.setExpandVertical(true);
-               scrolledCmp.setExpandHorizontal(true);
-               scrolledCmp.setShowFocusedControl(true);
-
-               scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS);
-               scrolledCmp.setContent(scrolledCmpBody);
-               scrolledCmpBody.addControlListener(new ControlAdapter() {
-                       private static final long serialVersionUID = 183238447102854553L;
-
-                       @Override
-                       public void controlResized(ControlEvent e) {
-                               Rectangle r = scrolledCmp.getClientArea();
-                               scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height));
-                       }
-               });
-               initExplorer(scrolledCmpBody, context);
-               scrolledCmpBody.layout(true, true);
-               scrolledCmp.layout();
-
-       }
-
-       private Control initExplorer(Composite parent, Path context) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               return createBrowserColumn(parent, context);
-       }
-
-       private Control createBrowserColumn(Composite parent, Path context) {
-               // TODO style is not correctly managed.
-               FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
-               // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
-               table.filterList("*");
-               table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
-               browserCols.put(context, table);
-               parent.layout(true, true);
-               return table;
-       }
-
-       public void addFilterPanel(Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
-
-               parentPathTxt = new Text(parent, SWT.NO_FOCUS);
-               parentPathTxt.setEditable(false);
-
-               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setMessage("Filter current list");
-               filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void modifyText(ModifyEvent event) {
-                               modifyFilter(false);
-                       }
-               });
-               filterTxt.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = 2533535233583035527L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
-                               // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
-                               FilterEntitiesVirtualTable currTable = null;
-                               if (currEdited != null) {
-                                       FilterEntitiesVirtualTable table = browserCols.get(currEdited);
-                                       if (table != null && !table.isDisposed())
-                                               currTable = table;
-                               }
-
-                               if (e.keyCode == SWT.ARROW_DOWN)
-                                       currTable.setFocus();
-                               else if (e.keyCode == SWT.BS) {
-                                       if (filterTxt.getText().equals("")
-                                                       && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) {
-                                               Path oldEdited = currEdited;
-                                               Path parentPath = currEdited.getParent();
-                                               setEdited(parentPath);
-                                               if (browserCols.containsKey(parentPath))
-                                                       browserCols.get(parentPath).setSelected(oldEdited);
-                                               filterTxt.setFocus();
-                                               e.doit = false;
-                                       }
-                               } else if (e.keyCode == SWT.TAB && !shiftPressed) {
-                                       Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText());
-                                       if (uniqueChild != null) {
-                                               // Highlight the unique chosen child
-                                               currTable.setSelected(uniqueChild);
-                                               setEdited(uniqueChild);
-                                       }
-                                       filterTxt.setFocus();
-                                       e.doit = false;
-                               }
-                       }
-               });
-       }
-
-       private Path getOnlyChild(Path parent, String filter) {
-               try (DirectoryStream<Path> stream = Files.newDirectoryStream(currEdited, filter + "*")) {
-                       Path uniqueChild = null;
-                       boolean moreThanOne = false;
-                       loop: for (Path entry : stream) {
-                               if (uniqueChild == null) {
-                                       uniqueChild = entry;
-                               } else {
-                                       moreThanOne = true;
-                                       break loop;
-                               }
-                       }
-                       if (!moreThanOne)
-                               return uniqueChild;
-                       return null;
-               } catch (IOException ioe) {
-                       throw new FsUiException(
-                                       "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
-                                       ioe);
-               }
-       }
-
-       private void setEdited(Path path) {
-               currEdited = path;
-               EclipseUiUtils.clear(displayBoxCmp);
-               populateCurrEditedDisplay(displayBoxCmp, currEdited);
-               refreshFilters(path);
-               refreshBrowser(path);
-       }
-
-       private void refreshFilters(Path path) {
-               parentPathTxt.setText(path.toUri().toString());
-               filterTxt.setText("");
-               filterTxt.getParent().layout();
-       }
-
-       private void refreshBrowser(Path currPath) {
-               Path currParPath = currPath.getParent();
-               Object[][] colMatrix = new Object[browserCols.size()][2];
-
-               int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1;
-               for (Path path : browserCols.keySet()) {
-                       colMatrix[i][0] = path;
-                       colMatrix[i][1] = browserCols.get(path);
-                       if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) {
-                               boolean leaveOpened = path.startsWith(currPath);
-                               if (!leaveOpened)
-                                       lastLeftOpenedIndex = i;
-                       }
-                       if (currParPath.equals(path))
-                               currPathIndex = i;
-                       i++;
-               }
-
-               if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) {
-                       // dispose and remove useless cols
-                       for (int l = i - 1; l >= lastLeftOpenedIndex; l--) {
-                               ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
-                               browserCols.remove(colMatrix[l][0]);
-                       }
-               }
-
-               if (browserCols.containsKey(currPath)) {
-                       FilterEntitiesVirtualTable currCol = browserCols.get(currPath);
-                       if (currCol.isDisposed()) {
-                               // Does it still happen ?
-                               log.warn(currPath + " browser column was disposed and still listed");
-                               browserCols.remove(currPath);
-                       }
-               }
-
-               if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath))
-                       createBrowserColumn(scrolledCmpBody, currPath);
-
-               scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
-               scrolledCmpBody.layout(true, true);
-               // also resize the scrolled composite
-               scrolledCmp.layout();
-       }
-
-       private void modifyFilter(boolean fromOutside) {
-               if (!fromOutside)
-                       if (currEdited != null) {
-                               String filter = filterTxt.getText() + "*";
-                               FilterEntitiesVirtualTable table = browserCols.get(currEdited);
-                               if (table != null && !table.isDisposed())
-                                       table.filterList(filter);
-                       }
-       }
-
-       /**
-        * Recreates the content of the box that displays information about the current
-        * selected node.
-        */
-       private void populateCurrEditedDisplay(Composite parent, Path context) {
-               parent.setLayout(new GridLayout());
-
-               // if (isImg(context)) {
-               // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
-               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
-               // 2, 1));
-               // }
-
-               try {
-                       Label contextL = new Label(parent, SWT.NONE);
-                       contextL.setText(context.getFileName().toString());
-                       contextL.setFont(EclipseUiUtils.getBoldFont(parent));
-                       addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString());
-                       addProperty(parent, "Owner", Files.getOwner(context).getName());
-                       if (Files.isDirectory(context)) {
-                               addProperty(parent, "Type", "Folder");
-                       } else {
-                               String mimeType = Files.probeContentType(context);
-                               if (EclipseUiUtils.isEmpty(mimeType))
-                                       mimeType = "<i>Unknown</i>";
-                               addProperty(parent, "Type", mimeType);
-                               addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false));
-                       }
-                       parent.layout(true, true);
-               } catch (IOException e) {
-                       throw new FsUiException("Cannot display details for " + context, e);
-               }
-       }
-
-       private void addProperty(Composite parent, String propName, String value) {
-               Label contextL = new Label(parent, SWT.NONE);
-               contextL.setText(propName + ": " + value);
-       }
-
-       /**
-        * Almost canonical implementation of a table that displays the content of a
-        * directory
-        */
-       private class FilterEntitiesVirtualTable extends Composite {
-               private static final long serialVersionUID = 2223410043691844875L;
-
-               // Context
-               private Path context;
-               private Path currSelected = null;
-
-               // UI Objects
-               private FsTableViewer viewer;
-
-               @Override
-               public boolean setFocus() {
-                       if (viewer.getTable().isDisposed())
-                               return false;
-                       if (currSelected != null)
-                               viewer.setSelection(new StructuredSelection(currSelected), true);
-                       else if (viewer.getSelection().isEmpty()) {
-                               Object first = viewer.getElementAt(0);
-                               if (first != null)
-                                       viewer.setSelection(new StructuredSelection(first), true);
-                       }
-                       return viewer.getTable().setFocus();
-               }
-
-               /**
-                * Enable highlighting the correct element in the table when externally browsing
-                * (typically via the command-line-like Text field)
-                */
-               void setSelected(Path selected) {
-                       // to prevent change selection event to be thrown
-                       currSelected = selected;
-                       viewer.setSelection(new StructuredSelection(currSelected), true);
-               }
-
-               void filterList(String filter) {
-                       viewer.setInput(context, filter);
-               }
-
-               public FilterEntitiesVirtualTable(Composite parent, int style, Path context) {
-                       super(parent, SWT.NO_FOCUS);
-                       this.context = context;
-                       createTableViewer(this);
-               }
-
-               private void createTableViewer(final Composite parent) {
-                       parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-                       // We must limit the size of the table otherwise the full list is
-                       // loaded before the layout happens
-                       // Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
-                       // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
-                       // gd.widthHint = COLUMN_WIDTH;
-                       // listCmp.setLayoutData(gd);
-                       // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
-                       // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
-                       // SWT.V_SCROLL);
-                       // Table table = viewer.getTable();
-                       // table.setLayoutData(EclipseUiUtils.fillAll());
-
-                       viewer = new FsTableViewer(parent, SWT.MULTI);
-                       Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH);
-
-                       viewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                               @Override
-                               public void selectionChanged(SelectionChangedEvent event) {
-                                       IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
-                                       if (selection.isEmpty())
-                                               return;
-                                       Object obj = selection.getFirstElement();
-                                       Path newSelected;
-                                       if (obj instanceof Path)
-                                               newSelected = (Path) obj;
-                                       else if (obj instanceof ParentDir)
-                                               newSelected = ((ParentDir) obj).getPath();
-                                       else
-                                               return;
-                                       if (newSelected.equals(currSelected))
-                                               return;
-                                       currSelected = newSelected;
-                                       setEdited(newSelected);
-
-                               }
-                       });
-
-                       table.addKeyListener(new KeyListener() {
-                               private static final long serialVersionUID = -8083424284436715709L;
-
-                               @Override
-                               public void keyReleased(KeyEvent e) {
-                               }
-
-                               @Override
-                               public void keyPressed(KeyEvent e) {
-                                       IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
-                                       Path selected = null;
-                                       if (!selection.isEmpty())
-                                               selected = ((Path) selection.getFirstElement());
-                                       if (e.keyCode == SWT.ARROW_RIGHT) {
-                                               if (!Files.isDirectory(selected))
-                                                       return;
-                                               if (selected != null) {
-                                                       setEdited(selected);
-                                                       browserCols.get(selected).setFocus();
-                                               }
-                                       } else if (e.keyCode == SWT.ARROW_LEFT) {
-                                               if (context.equals(initialPath))
-                                                       return;
-                                               Path parent = context.getParent();
-                                               if (parent == null)
-                                                       return;
-
-                                               setEdited(parent);
-                                               browserCols.get(parent).setFocus();
-                                       }
-                               }
-                       });
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java
deleted file mode 100644 (file)
index d3fc1c9..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/** Basic label provider with icon for NIO file viewers */
-public class FileIconNameLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = 8187902187946523148L;
-
-       private Image folderIcon;
-       private Image fileIcon;
-
-       public FileIconNameLabelProvider() {
-               // if (!PlatformUI.isWorkbenchRunning()) {
-               folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage();
-               fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage();
-               // }
-       }
-
-       @Override
-       public void dispose() {
-               if (folderIcon != null)
-                       folderIcon.dispose();
-               if (fileIcon != null)
-                       fileIcon.dispose();
-               super.dispose();
-       }
-
-       @Override
-       public String getText(Object element) {
-               if (element instanceof Path) {
-                       Path curr = ((Path) element);
-                       Path name = curr.getFileName();
-                       if (name == null)
-                               return "[No name]";
-                       else
-                               return name.toString();
-               } else if (element instanceof ParentDir) {
-                       return "..";
-               }
-               return null;
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               if (element instanceof Path) {
-                       Path curr = ((Path) element);
-                       if (Files.isDirectory(curr))
-                               // if (folderIcon != null)
-                               return folderIcon;
-                       // else
-                       // return
-                       // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
-                       // else if (fileIcon != null)
-                       return fileIcon;
-                       // else
-                       // return
-                       // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
-               } else if (element instanceof ParentDir) {
-                       return folderIcon;
-               }
-               return null;
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               if (element instanceof Path) {
-                       Path curr = ((Path) element);
-                       Path name = curr.getFileName();
-                       if (name == null)
-                               return "[No name]";
-                       else
-                               return name.toAbsolutePath().toString();
-               } else if (element instanceof ParentDir) {
-                       return ((ParentDir) element).getPath().toAbsolutePath().toString();
-               }
-               return null;
-       }
-
-}
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java
deleted file mode 100644 (file)
index 3b126e9..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Path;
-import java.util.List;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.eclipse.jface.viewers.CellLabelProvider;
-import org.eclipse.jface.viewers.ILazyContentProvider;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-
-/**
- * Canonical implementation of a JFace table viewer to display the content of a
- * file folder
- */
-public class FsTableViewer extends TableViewer {
-       private static final long serialVersionUID = -5632407542678477234L;
-
-       private boolean showHiddenItems = false;
-       private boolean folderFirst = true;
-       private boolean reverseOrder = false;
-       private String orderProperty = FsUiConstants.PROPERTY_NAME;
-
-       private Path initialPath = null;
-
-       public FsTableViewer(Composite parent, int style) {
-               super(parent, style | SWT.VIRTUAL);
-       }
-
-       public Table configureDefaultSingleColumnTable(int tableWidthHint) {
-
-               return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider());
-       }
-
-       public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) {
-               Table table = this.getTable();
-               table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-               table.setLinesVisible(false);
-               table.setHeaderVisible(false);
-               // CmsUtils.markup(table);
-               // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
-
-               TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
-               TableColumn tcol = column.getColumn();
-               tcol.setWidth(tableWidthHint);
-               column.setLabelProvider(labelProvider);
-               this.setContentProvider(new MyLazyCP());
-               return table;
-       }
-
-       public Table configureDefaultTable(List<ColumnDefinition> columns) {
-               this.setContentProvider(new MyLazyCP());
-               Table table = this.getTable();
-               table.setLinesVisible(true);
-               table.setHeaderVisible(true);
-               // CmsUtils.markup(table);
-               // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
-               for (ColumnDefinition colDef : columns) {
-                       TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
-                       column.setLabelProvider(colDef.getLabelProvider());
-                       TableColumn tcol = column.getColumn();
-                       tcol.setResizable(true);
-                       tcol.setText(colDef.getLabel());
-                       tcol.setWidth(colDef.getMinWidth());
-               }
-               return table;
-       }
-
-       public void setInput(Path dir, String filter) {
-               Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder);
-               if (rows == null) {
-                       this.setInput(null);
-                       this.setItemCount(0);
-                       return;
-               }
-               boolean isRoot;
-               try {
-                       isRoot = dir.getRoot().equals(dir);
-               } catch (Exception e) {
-                       // FIXME Workaround for JCR root node access
-                       isRoot = dir.toString().equals("/");
-               }
-               final Object[] res;
-               if (isRoot)
-                       res = rows;
-               else if (initialPath != null && initialPath.equals(dir))
-                       res = rows;
-               else {
-                       res = new Object[rows.length + 1];
-                       res[0] = new ParentDir(dir.getParent());
-                       for (int i = 1; i < res.length; i++) {
-                               res[i] = rows[i - 1];
-                       }
-               }
-               this.setInput(res);
-               int length = res.length;
-               this.setItemCount(length);
-               this.refresh();
-       }
-
-       /** Directly displays bookmarks **/
-       public void setPathsInput(Path... paths) {
-               this.setInput((Object[]) paths);
-               this.setItemCount(paths.length);
-               this.refresh();
-       }
-
-       /**
-        * A path which is to be considered as root (and thus provide no link to a
-        * parent directory)
-        */
-       public void setInitialPath(Path initialPath) {
-               this.initialPath = initialPath;
-       }
-
-       private class MyLazyCP implements ILazyContentProvider {
-               private static final long serialVersionUID = 9096550041395433128L;
-               private Object[] elements;
-
-               public void dispose() {
-               }
-
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-                       // IMPORTANT: don't forget this: an exception will be thrown if
-                       // a selected object is not part of the results anymore.
-                       viewer.setSelection(null);
-                       this.elements = (Object[]) newInput;
-               }
-
-               public void updateElement(int index) {
-                       if (index < elements.length)
-                               FsTableViewer.this.replace(elements[index], index);
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java
deleted file mode 100644 (file)
index f55ead7..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.jface.viewers.TreeViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeColumn;
-
-/**
- * Canonical implementation of a JFace TreeViewer to display the content of a
- * repository
- */
-public class FsTreeViewer extends TreeViewer {
-       private static final long serialVersionUID = -5632407542678477234L;
-
-       private boolean showHiddenItems = false;
-       private boolean showDirectoryFirst = true;
-       private String orderingProperty = FsUiConstants.PROPERTY_NAME;
-
-       public FsTreeViewer(Composite parent, int style) {
-               super(parent, style | SWT.VIRTUAL);
-       }
-
-       public Tree configureDefaultSingleColumnTable(int tableWidthHint) {
-               Tree tree = this.getTree();
-               tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
-               tree.setLinesVisible(true);
-               tree.setHeaderVisible(false);
-//             CmsUtils.markup(tree);
-
-               TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
-               TreeColumn tcol = column.getColumn();
-               tcol.setWidth(tableWidthHint);
-               column.setLabelProvider(new FileIconNameLabelProvider());
-
-               this.setContentProvider(new MyCP());
-               return tree;
-       }
-
-       public Tree configureDefaultTable(List<ColumnDefinition> columns) {
-               this.setContentProvider(new MyCP());
-               Tree tree = this.getTree();
-               tree.setLinesVisible(true);
-               tree.setHeaderVisible(true);
-//             CmsUtils.markup(tree);
-//             CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN);
-               for (ColumnDefinition colDef : columns) {
-                       TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
-                       column.setLabelProvider(colDef.getLabelProvider());
-                       TreeColumn tcol = column.getColumn();
-                       tcol.setResizable(true);
-                       tcol.setText(colDef.getLabel());
-                       tcol.setWidth(colDef.getMinWidth());
-               }
-               return tree;
-       }
-
-       public void setInput(Path dir, String filter) {
-               try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
-                       // TODO make this lazy
-                       List<Path> paths = new ArrayList<>();
-                       for (Path entry : stream) {
-                               paths.add(entry);
-                       }
-                       Object[] rows = paths.toArray(new Object[0]);
-                       this.setInput(rows);
-                       // this.setItemCount(rows.length);
-                       this.refresh();
-               } catch (IOException | DirectoryIteratorException e) {
-                       throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e);
-               }
-       }
-
-       /** Directly displays bookmarks **/
-       public void setPathsInput(Path... paths) {
-               this.setInput((Object[]) paths);
-               // this.setItemCount(paths.length);
-               this.refresh();
-       }
-
-       private class MyCP implements ITreeContentProvider {
-               private static final long serialVersionUID = 9096550041395433128L;
-               private Object[] elements;
-
-               public void dispose() {
-               }
-
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-                       // IMPORTANT: don't forget this: an exception will be thrown if
-                       // a selected object is not part of the results anymore.
-                       viewer.setSelection(null);
-                       this.elements = (Object[]) newInput;
-               }
-
-               @Override
-               public Object[] getElements(Object inputElement) {
-                       return elements;
-               }
-
-               @Override
-               public Object[] getChildren(Object parentElement) {
-                       Path path = (Path) parentElement;
-                       if (!Files.isDirectory(path))
-                               return null;
-                       else
-                               return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false);
-               }
-
-               @Override
-               public Object getParent(Object element) {
-                       Path path = (Path) element;
-                       return path.getParent();
-               }
-
-               @Override
-               public boolean hasChildren(Object element) {
-                       Path path = (Path) element;
-                       try {
-                               if (!Files.isDirectory(path))
-                                       return false;
-                               else
-                                       try (DirectoryStream<Path> children = Files.newDirectoryStream(path, "*")) {
-                                               return children.iterator().hasNext();
-                                       }
-                       } catch (IOException e) {
-                               throw new FsUiException("Unable to check child existence on " + path, e);
-                       }
-               }
-
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java
deleted file mode 100644 (file)
index 2b51e71..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-/** Centralize constants used by the Nio FS UI parts */
-public interface FsUiConstants {
-
-       // TODO use standard properties
-       String PROPERTY_NAME = "name";
-       String PROPERTY_SIZE = "size";
-       String PROPERTY_LAST_MODIFIED = "last-modified";
-       String PROPERTY_TYPE = "type";
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java
deleted file mode 100644 (file)
index 422b0e1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-/** Files specific exception */
-public class FsUiException extends RuntimeException {
-       private static final long serialVersionUID = 1L;
-
-       public FsUiException(String message) {
-               super(message);
-       }
-
-       public FsUiException(String message, Throwable e) {
-               super(message, e);
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java
deleted file mode 100644 (file)
index 956d96b..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Centralise additional utilitary methods to manage Java7 NIO files */
-public class FsUiUtils {
-
-       /**
-        * thanks to
-        * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html
-        */
-       public static String humanReadableByteCount(long bytes, boolean si) {
-               int unit = si ? 1000 : 1024;
-               if (bytes < unit)
-                       return bytes + " B";
-               int exp = (int) (Math.log(bytes) / Math.log(unit));
-               String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
-               return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
-       }
-
-       public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst,
-                       String orderProperty, boolean reverseOrder) {
-               if (!Files.isDirectory(parent))
-                       return null;
-               List<Pair> pairs = new ArrayList<>();
-               try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent, filter)) {
-                       loop: for (Path entry : stream) {
-                               if (!showHiddenItems)
-                                       if (Files.isHidden(entry))
-                                               continue loop;
-                               switch (orderProperty) {
-                               case FsUiConstants.PROPERTY_SIZE:
-                                       if (folderFirst)
-                                               pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry)));
-                                       else
-                                               pairs.add(new LPair(entry, Files.size(entry)));
-                                       break;
-                               case FsUiConstants.PROPERTY_LAST_MODIFIED:
-                                       if (folderFirst)
-                                               pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(),
-                                                               Files.isDirectory(entry)));
-                                       else
-                                               pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis()));
-                                       break;
-                               case FsUiConstants.PROPERTY_NAME:
-                                       if (folderFirst)
-                                               pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry)));
-                                       else
-                                               pairs.add(new SPair(entry, entry.getFileName().toString()));
-                                       break;
-                               default:
-                                       throw new FsUiException("Unable to prepare sort for property " + orderProperty);
-                               }
-                       }
-                       Pair[] rows = pairs.toArray(new Pair[0]);
-                       Arrays.sort(rows);
-                       Path[] results = new Path[rows.length];
-                       if (reverseOrder) {
-                               int j = rows.length - 1;
-                               for (int i = 0; i < rows.length; i++)
-                                       results[i] = rows[j - i].p;
-                       } else
-                               for (int i = 0; i < rows.length; i++)
-                                       results[i] = rows[i].p;
-                       return results;
-               } catch (IOException | DirectoryIteratorException e) {
-                       throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e);
-               }
-       }
-
-       static abstract class Pair implements Comparable<Object> {
-               Path p;
-               Boolean i;
-       };
-
-       static class LPair extends Pair {
-               long v;
-
-               public LPair(Path path, long propValue) {
-                       p = path;
-                       v = propValue;
-               }
-
-               public LPair(Path path, long propValue, boolean isDir) {
-                       p = path;
-                       v = propValue;
-                       i = isDir;
-               }
-
-               public int compareTo(Object o) {
-                       if (i != null) {
-                               Boolean j = ((LPair) o).i;
-                               if (i.booleanValue() != j.booleanValue())
-                                       return i.booleanValue() ? -1 : 1;
-                       }
-                       long u = ((LPair) o).v;
-                       return v < u ? -1 : v == u ? 0 : 1;
-               }
-       };
-
-       static class SPair extends Pair {
-               String v;
-
-               public SPair(Path path, String propValue) {
-                       p = path;
-                       v = propValue;
-               }
-
-               public SPair(Path path, String propValue, boolean isDir) {
-                       p = path;
-                       v = propValue;
-                       i = isDir;
-               }
-
-               public int compareTo(Object o) {
-                       if (i != null) {
-                               Boolean j = ((SPair) o).i;
-                               if (i.booleanValue() != j.booleanValue())
-                                       return i.booleanValue() ? -1 : 1;
-                       }
-                       String u = ((SPair) o).v;
-                       return v.compareTo(u);
-               }
-       };
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java
deleted file mode 100644 (file)
index 2bb65ee..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/** Expect a {@link Path} as input element */
-public class NioFileLabelProvider extends ColumnLabelProvider {
-       private final static FileTime EPOCH = FileTime.fromMillis(0);
-       private static final long serialVersionUID = 2160026425187796930L;
-       private final String propName;
-       private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
-
-       // TODO use new formatting
-       // DateTimeFormatter formatter =
-       // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT )
-       // .withLocale( Locale.UK )
-       // .withZone( ZoneId.systemDefault() );
-       public NioFileLabelProvider(String propName) {
-               this.propName = propName;
-       }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       Path path;
-                       if (element instanceof ParentDir) {
-//                             switch (propName) {
-//                             case FsUiConstants.PROPERTY_SIZE:
-//                                     return "-";
-//                             case FsUiConstants.PROPERTY_LAST_MODIFIED:
-//                                     return "-";
-//                             // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString();
-//                             case FsUiConstants.PROPERTY_TYPE:
-//                                     return "Folder";
-//                             }
-                               path = ((ParentDir) element).getPath();
-                       } else
-                               path = (Path) element;
-                       switch (propName) {
-                       case FsUiConstants.PROPERTY_SIZE:
-                               if (Files.isDirectory(path))
-                                       return "-";
-                               else
-                                       return FsUiUtils.humanReadableByteCount(Files.size(path), false);
-                       case FsUiConstants.PROPERTY_LAST_MODIFIED:
-                               if (Files.isDirectory(path))
-                                       return "-";
-                               FileTime time = Files.getLastModifiedTime(path);
-                               if (time.equals(EPOCH))
-                                       return "-";
-                               else
-                                       return dateFormat.format(new Date(time.toMillis()));
-                       case FsUiConstants.PROPERTY_TYPE:
-                               if (Files.isDirectory(path))
-                                       return "Folder";
-                               else {
-                                       String mimeType = Files.probeContentType(path);
-                                       if (EclipseUiUtils.isEmpty(mimeType))
-                                               return "Unknown";
-                                       else
-                                               return mimeType;
-                               }
-                       default:
-                               throw new IllegalArgumentException("Unsupported property " + propName);
-                       }
-               } catch (IOException ioe) {
-                       throw new FsUiException("Cannot get property " + propName + " on " + element);
-               }
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java
deleted file mode 100644 (file)
index 6f09c29..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Path;
-
-/** A parent directory (..) reference. */
-public class ParentDir {
-       Path path;
-
-       public ParentDir(Path path) {
-               super();
-               this.path = path;
-       }
-
-       public Path getPath() {
-               return path;
-       }
-
-       @Override
-       public int hashCode() {
-               return path.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return "Parent dir " + path;
-       }
-
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java
deleted file mode 100644 (file)
index 2e3d6b4..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-
-/**
- * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on
- * the left hand side and a simple table on the right hand side.
- */
-public class SimpleFsBrowser extends Composite {
-       private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class);
-       private static final long serialVersionUID = -40347919096946585L;
-
-       private Path currSelected;
-       private FsTableViewer bookmarksViewer;
-       private FsTableViewer directoryDisplayViewer;
-
-       public SimpleFsBrowser(Composite parent, int style) {
-               super(parent, style);
-               createContent(this);
-               // parent.layout(true, true);
-       }
-
-       public Viewer getViewer() {
-               return directoryDisplayViewer;
-       }
-
-       private void createContent(Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
-               Composite leftCmp = new Composite(form, SWT.NONE);
-               populateBookmarks(leftCmp);
-
-               Composite rightCmp = new Composite(form, SWT.BORDER);
-               populateDisplay(rightCmp);
-               form.setLayoutData(EclipseUiUtils.fillAll());
-               form.setWeights(new int[] { 1, 3 });
-       }
-
-       public void setInput(Path... paths) {
-               bookmarksViewer.setPathsInput(paths);
-               bookmarksViewer.getTable().getParent().layout(true, true);
-       }
-
-       private void populateBookmarks(final Composite parent) {
-               // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
-               // layout.verticalSpacing = 5;
-               parent.setLayout(new GridLayout());
-
-               ISelectionChangedListener selList = new MySelectionChangedListener();
-
-               appendTitle(parent, "My bookmarks");
-               bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
-               Table table = bookmarksViewer.configureDefaultSingleColumnTable(500);
-               GridData gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 10;
-               table.setLayoutData(gd);
-               bookmarksViewer.addSelectionChangedListener(selList);
-
-               appendTitle(parent, "Jcr + File");
-
-               FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
-               table = jcrFilesViewers.configureDefaultSingleColumnTable(500);
-               gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 10;
-               table.setLayoutData(gd);
-               jcrFilesViewers.addSelectionChangedListener(selList);
-
-               // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
-               // try {
-               // Path testPath = fsProvider.getPath(new URI("jcr+memory:/"));
-               // jcrFilesViewers.setPathsInput(testPath);
-               // } catch (URISyntaxException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
-       }
-
-       private Label appendTitle(Composite parent, String value) {
-               Label titleLbl = new Label(parent, SWT.NONE);
-               titleLbl.setText(value);
-               titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
-               GridData gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 5;
-               gd.verticalIndent = 5;
-               titleLbl.setLayoutData(gd);
-               return titleLbl;
-       }
-
-       private class MySelectionChangedListener implements ISelectionChangedListener {
-               @Override
-               public void selectionChanged(SelectionChangedEvent event) {
-                       IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-                       else {
-                               Path newSelected = (Path) selection.getFirstElement();
-                               if (newSelected.equals(currSelected))
-                                       return;
-                               currSelected = newSelected;
-                               directoryDisplayViewer.setInput(currSelected, "*");
-                       }
-               }
-       }
-
-       private void populateDisplay(final Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
-               List<ColumnDefinition> colDefs = new ArrayList<>();
-               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
-                               "Last modified", 200));
-               Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
-               table.setLayoutData(EclipseUiUtils.fillAll());
-
-               table.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = -8083424284436715709L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               log.debug("Key event received: " + e.keyCode);
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (!selection.isEmpty())
-                                       selected = ((Path) selection.getFirstElement());
-                               if (e.keyCode == SWT.CR) {
-                                       if (!Files.isDirectory(selected))
-                                               return;
-                                       if (selected != null) {
-                                               currSelected = selected;
-                                               directoryDisplayViewer.setInput(currSelected, "*");
-                                       }
-                               } else if (e.keyCode == SWT.BS) {
-                                       currSelected = currSelected.getParent();
-                                       directoryDisplayViewer.setInput(currSelected, "*");
-                                       directoryDisplayViewer.getTable().setFocus();
-                               }
-                       }
-               });
-
-//             directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
-//                     @Override
-//                     public void doubleClick(DoubleClickEvent event) {
-//                             IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-//                             Path selected = null;
-//                             if (!selection.isEmpty()) {
-//                                     Object obj = selection.getFirstElement();
-//                                     if (obj instanceof Path)
-//                                             selected = (Path) obj;
-//                                     else if (obj instanceof ParentDir)
-//                                             selected = ((ParentDir) obj).getPath();
-//                             }
-//                             if (selected != null) {
-//                                     if (!Files.isDirectory(selected))
-//                                             return;
-//                                     currSelected = selected;
-//                                     directoryDisplayViewer.setInput(currSelected, "*");
-//                             }
-//                     }
-//             });
-
-               directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (!selection.isEmpty()) {
-                                       Object obj = selection.getFirstElement();
-                                       if (obj instanceof Path)
-                                               selected = (Path) obj;
-                                       else if (obj instanceof ParentDir)
-                                               selected = ((ParentDir) obj).getPath();
-                               }
-                               if (selected != null) {
-                                       if (!Files.isDirectory(selected))
-                                               return;
-                                       currSelected = selected;
-                                       directoryDisplayViewer.setInput(currSelected, "*");
-                               }
-                       }
-               });
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java
deleted file mode 100644 (file)
index 401e5cb..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.argeo.eclipse.ui.fs;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Tree;
-
-/** A simple Java 7 nio files browser with a tree */
-public class SimpleFsTreeBrowser extends Composite {
-       private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class);
-       private static final long serialVersionUID = -40347919096946585L;
-
-       private Path currSelected;
-       private FsTreeViewer treeViewer;
-       private FsTableViewer directoryDisplayViewer;
-
-       public SimpleFsTreeBrowser(Composite parent, int style) {
-               super(parent, style);
-               createContent(this);
-               // parent.layout(true, true);
-       }
-
-       private void createContent(Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
-               Composite child1 = new Composite(form, SWT.NONE);
-               populateTree(child1);
-               Composite child2 = new Composite(form, SWT.BORDER);
-               populateDisplay(child2);
-               form.setLayoutData(EclipseUiUtils.fillAll());
-               form.setWeights(new int[] { 1, 3 });
-       }
-
-       public void setInput(Path... paths) {
-               treeViewer.setPathsInput(paths);
-               treeViewer.getControl().getParent().layout(true, true);
-       }
-
-       private void populateTree(final Composite parent) {
-               // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
-               // layout.verticalSpacing = 5;
-               parent.setLayout(new GridLayout());
-
-               ISelectionChangedListener selList = new MySelectionChangedListener();
-
-               treeViewer = new FsTreeViewer(parent, SWT.MULTI);
-               Tree tree = treeViewer.configureDefaultSingleColumnTable(500);
-               GridData gd = EclipseUiUtils.fillAll();
-               // gd.horizontalIndent = 10;
-               tree.setLayoutData(gd);
-               treeViewer.addSelectionChangedListener(selList);
-       }
-
-       private class MySelectionChangedListener implements ISelectionChangedListener {
-               @Override
-               public void selectionChanged(SelectionChangedEvent event) {
-                       IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
-                       if (selection.isEmpty())
-                               return;
-                       else {
-                               Path newSelected = (Path) selection.getFirstElement();
-                               if (newSelected.equals(currSelected))
-                                       return;
-                               currSelected = newSelected;
-                               if (Files.isDirectory(currSelected))
-                                       directoryDisplayViewer.setInput(currSelected, "*");
-                       }
-               }
-       }
-
-       private void populateDisplay(final Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
-               List<ColumnDefinition> colDefs = new ArrayList<>();
-               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
-                               "Last modified", 100, 100));
-               Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
-               table.setLayoutData(EclipseUiUtils.fillAll());
-
-               table.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = -8083424284436715709L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               log.debug("Key event received: " + e.keyCode);
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (!selection.isEmpty())
-                                       selected = ((Path) selection.getFirstElement());
-                               if (e.keyCode == SWT.CR) {
-                                       if (!Files.isDirectory(selected))
-                                               return;
-                                       if (selected != null) {
-                                               currSelected = selected;
-                                               directoryDisplayViewer.setInput(currSelected, "*");
-                                       }
-                               } else if (e.keyCode == SWT.BS) {
-                                       currSelected = currSelected.getParent();
-                                       directoryDisplayViewer.setInput(currSelected, "*");
-                                       directoryDisplayViewer.getTable().setFocus();
-                               }
-                       }
-               });
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png
deleted file mode 100644 (file)
index ce2f2a8..0000000
Binary files a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png
deleted file mode 100644 (file)
index c31f37e..0000000
Binary files a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png and /dev/null differ
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java
deleted file mode 100644 (file)
index d7f83c3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace file system utilities. */
-package org.argeo.eclipse.ui.fs;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java
deleted file mode 100644 (file)
index 0d245db..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace utilities. */
-package org.argeo.eclipse.ui;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java
deleted file mode 100644 (file)
index 5713905..0000000
+++ /dev/null
@@ -1,402 +0,0 @@
-package org.argeo.eclipse.ui.parts;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.util.ViewerUtils;
-import org.eclipse.jface.layout.TableColumnLayout;
-import org.eclipse.jface.viewers.CheckboxTableViewer;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ColumnWeightData;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Link;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.Text;
-import org.osgi.service.useradmin.User;
-
-/**
- * Generic composite that display a filter and a table viewer to display users
- * (can also be groups)
- * 
- * Warning: this class does not extends <code>TableViewer</code>. Use the
- * getTableViewer method to access it.
- * 
- */
-public abstract class LdifUsersTable extends Composite {
-       private static final long serialVersionUID = -7385959046279360420L;
-
-       // Context
-       // private UserAdmin userAdmin;
-
-       // Configuration
-       private List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
-       private boolean hasFilter;
-       private boolean preventTableLayout = false;
-       private boolean hasSelectionColumn;
-       private int tableStyle;
-
-       // Local UI Objects
-       private TableViewer usersViewer;
-       private Text filterTxt;
-
-       /* EXPOSED METHODS */
-
-       /**
-        * @param parent
-        * @param style
-        */
-       public LdifUsersTable(Composite parent, int style) {
-               super(parent, SWT.NO_FOCUS);
-               this.tableStyle = style;
-       }
-
-       // TODO workaround the bug of the table layout in the Form
-       public LdifUsersTable(Composite parent, int style, boolean preventTableLayout) {
-               super(parent, SWT.NO_FOCUS);
-               this.tableStyle = style;
-               this.preventTableLayout = preventTableLayout;
-       }
-
-       /** This must be called before the call to populate method */
-       public void setColumnDefinitions(List<ColumnDefinition> columnDefinitions) {
-               this.columnDefs = columnDefinitions;
-       }
-
-       /**
-        * 
-        * @param addFilter
-        *            choose to add a field to filter results or not
-        * @param addSelection
-        *            choose to add a column to select some of the displayed results or
-        *            not
-        */
-       public void populate(boolean addFilter, boolean addSelection) {
-               // initialization
-               Composite parent = this;
-               hasFilter = addFilter;
-               hasSelectionColumn = addSelection;
-
-               // Main Layout
-               GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
-               layout.verticalSpacing = 5;
-               this.setLayout(layout);
-               if (hasFilter)
-                       createFilterPart(parent);
-
-               Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
-               tableComp.setLayoutData(EclipseUiUtils.fillAll());
-               usersViewer = createTableViewer(tableComp);
-               usersViewer.setContentProvider(new UsersContentProvider());
-       }
-
-       /**
-        * 
-        * @param showMore
-        *            display static filters on creation
-        * @param addSelection
-        *            choose to add a column to select some of the displayed results or
-        *            not
-        */
-       public void populateWithStaticFilters(boolean showMore, boolean addSelection) {
-               // initialization
-               Composite parent = this;
-               hasFilter = true;
-               hasSelectionColumn = addSelection;
-
-               // Main Layout
-               GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
-               layout.verticalSpacing = 5;
-               this.setLayout(layout);
-               createStaticFilterPart(parent, showMore);
-
-               Composite tableComp = new Composite(parent, SWT.NO_FOCUS);
-               tableComp.setLayoutData(EclipseUiUtils.fillAll());
-               usersViewer = createTableViewer(tableComp);
-               usersViewer.setContentProvider(new UsersContentProvider());
-       }
-
-       /** Enable access to the selected users or groups */
-       public List<User> getSelectedUsers() {
-               if (hasSelectionColumn) {
-                       Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements();
-
-                       List<User> result = new ArrayList<User>();
-                       for (Object obj : elements) {
-                               result.add((User) obj);
-                       }
-                       return result;
-               } else
-                       throw new EclipseUiException(
-                                       "Unvalid request: no selection column " + "has been created for the current table");
-       }
-
-       /** Returns the User table viewer, typically to add doubleclick listener */
-       public TableViewer getTableViewer() {
-               return usersViewer;
-       }
-
-       /**
-        * Force the refresh of the underlying table using the current filter string if
-        * relevant
-        */
-       public void refresh() {
-               String filter = hasFilter ? filterTxt.getText().trim() : null;
-               if ("".equals(filter))
-                       filter = null;
-               refreshFilteredList(filter);
-       }
-
-       /** Effective repository request: caller must implement this method */
-       abstract protected List<User> listFilteredElements(String filter);
-
-       // protected List<User> listFilteredElements(String filter) {
-       // List<User> users = new ArrayList<User>();
-       // try {
-       // Role[] roles = userAdmin.getRoles(filter);
-       // // Display all users and groups
-       // for (Role role : roles)
-       // users.add((User) role);
-       // } catch (InvalidSyntaxException e) {
-       // throw new EclipseUiException("Unable to get roles with filter: "
-       // + filter, e);
-       // }
-       // return users;
-       // }
-
-       /* GENERIC COMPOSITE METHODS */
-       @Override
-       public boolean setFocus() {
-               if (hasFilter)
-                       return filterTxt.setFocus();
-               else
-                       return usersViewer.getTable().setFocus();
-       }
-
-       @Override
-       public void dispose() {
-               super.dispose();
-       }
-
-       /* LOCAL CLASSES AND METHODS */
-       // Will be usefull to rather use a virtual table viewer
-       private void refreshFilteredList(String filter) {
-               List<User> users = listFilteredElements(filter);
-               usersViewer.setInput(users.toArray());
-       }
-
-       private class UsersContentProvider implements IStructuredContentProvider {
-               private static final long serialVersionUID = 1L;
-
-               public Object[] getElements(Object inputElement) {
-                       return (Object[]) inputElement;
-               }
-
-               public void dispose() {
-               }
-
-               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               }
-       }
-
-       /* MANAGE FILTER */
-       private void createFilterPart(Composite parent) {
-               // Text Area for the filter
-               filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void modifyText(ModifyEvent event) {
-                               refreshFilteredList(filterTxt.getText());
-                       }
-               });
-       }
-
-       private void createStaticFilterPart(Composite parent, boolean showMore) {
-               Composite filterComp = new Composite(parent, SWT.NO_FOCUS);
-               filterComp.setLayout(new GridLayout(2, false));
-               filterComp.setLayoutData(EclipseUiUtils.fillWidth());
-               // generic search
-               filterTxt = new Text(filterComp, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
-               // filterTxt.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
-               // GridData.HORIZONTAL_ALIGN_FILL));
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void modifyText(ModifyEvent event) {
-                               refreshFilteredList(filterTxt.getText());
-                       }
-               });
-
-               // add static filter abilities
-               Link moreLk = new Link(filterComp, SWT.NONE);
-               Composite staticFilterCmp = new Composite(filterComp, SWT.NO_FOCUS);
-               staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2));
-               populateStaticFilters(staticFilterCmp);
-
-               MoreLinkListener listener = new MoreLinkListener(moreLk, staticFilterCmp, showMore);
-               // initialise the layout
-               listener.refresh();
-               moreLk.addSelectionListener(listener);
-       }
-
-       /** Overwrite to add static filters */
-       protected void populateStaticFilters(Composite staticFilterCmp) {
-       }
-
-       // private void addMoreSL(final Link more) {
-       // more.addSelectionListener( }
-
-       private class MoreLinkListener extends SelectionAdapter {
-               private static final long serialVersionUID = -524987616510893463L;
-               private boolean isShown;
-               private final Composite staticFilterCmp;
-               private final Link moreLk;
-
-               public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) {
-                       this.moreLk = moreLk;
-                       this.staticFilterCmp = staticFilterCmp;
-                       this.isShown = isShown;
-               }
-
-               @Override
-               public void widgetSelected(SelectionEvent e) {
-                       isShown = !isShown;
-                       refresh();
-               }
-
-               public void refresh() {
-                       GridData gd = (GridData) staticFilterCmp.getLayoutData();
-                       if (isShown) {
-                               moreLk.setText("<a> Less... </a>");
-                               gd.heightHint = SWT.DEFAULT;
-                       } else {
-                               moreLk.setText("<a> More... </a>");
-                               gd.heightHint = 0;
-                       }
-                       forceLayout();
-               }
-       }
-
-       private void forceLayout() {
-               LdifUsersTable.this.getParent().layout(true, true);
-       }
-
-       private TableViewer createTableViewer(final Composite parent) {
-
-               int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL;
-               if (hasSelectionColumn)
-                       style = style | SWT.CHECK;
-               Table table = new Table(parent, style);
-               TableColumnLayout layout = new TableColumnLayout();
-
-               // TODO the table layout does not works with the scrolled form
-
-               if (preventTableLayout) {
-                       parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-                       table.setLayoutData(EclipseUiUtils.fillAll());
-               } else
-                       parent.setLayout(layout);
-
-               TableViewer viewer;
-               if (hasSelectionColumn)
-                       viewer = new CheckboxTableViewer(table);
-               else
-                       viewer = new TableViewer(table);
-               table.setLinesVisible(true);
-               table.setHeaderVisible(true);
-
-               TableViewerColumn column;
-               // int offset = 0;
-               if (hasSelectionColumn) {
-                       // offset = 1;
-                       column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25);
-                       column.setLabelProvider(new ColumnLabelProvider() {
-                               private static final long serialVersionUID = 1L;
-
-                               @Override
-                               public String getText(Object element) {
-                                       return null;
-                               }
-                       });
-                       layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false));
-
-                       SelectionAdapter selectionAdapter = new SelectionAdapter() {
-                               private static final long serialVersionUID = 1L;
-
-                               boolean allSelected = false;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       allSelected = !allSelected;
-                                       ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected);
-                               }
-                       };
-                       column.getColumn().addSelectionListener(selectionAdapter);
-               }
-
-               // NodeViewerComparator comparator = new NodeViewerComparator();
-               // TODO enable the sort by click on the header
-               // int i = offset;
-               for (ColumnDefinition colDef : columnDefs)
-                       createTableColumn(viewer, layout, colDef);
-
-               // column = ViewerUtils.createTableViewerColumn(viewer,
-               // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize());
-               // column.setLabelProvider(new CLProvider(colDef.getPropertyName()));
-               // column.getColumn().addSelectionListener(
-               // JcrUiUtils.getNodeSelectionAdapter(i,
-               // colDef.getPropertyType(), colDef.getPropertyName(),
-               // comparator, viewer));
-               // i++;
-               // }
-
-               // IMPORTANT: initialize comparator before setting it
-               // JcrColumnDefinition firstCol = colDefs.get(0);
-               // comparator.setColumn(firstCol.getPropertyType(),
-               // firstCol.getPropertyName());
-               // viewer.setComparator(comparator);
-
-               return viewer;
-       }
-
-       /** Default creation of a column for a user table */
-       private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout,
-                       ColumnDefinition columnDef) {
-
-               boolean resizable = true;
-               TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE);
-               TableColumn column = tvc.getColumn();
-
-               column.setText(columnDef.getLabel());
-               column.setWidth(columnDef.getMinWidth());
-               column.setResizable(resizable);
-
-               ColumnLabelProvider lp = columnDef.getLabelProvider();
-               // add a reference to the display to enable font management
-               // if (lp instanceof UserAdminAbstractLP)
-               // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable()
-               // .getDisplay());
-               tvc.setLabelProvider(lp);
-
-               layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable));
-
-               return tvc;
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java
deleted file mode 100644 (file)
index 9e93b11..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace composites. */
-package org.argeo.eclipse.ui.parts;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java
deleted file mode 100644 (file)
index 8f4df17..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.eclipse.ui.util;
-
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.TreeViewer;
-import org.eclipse.jface.viewers.TreeViewerColumn;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableColumn;
-import org.eclipse.swt.widgets.TreeColumn;
-
-/**
- * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers.
- */
-public class ViewerUtils {
-
-       /**
-        * Creates a basic column for the given table. For the time being, we do not
-        * support movable columns.
-        */
-       public static TableColumn createColumn(Table parent, String name, int style, int width) {
-               TableColumn result = new TableColumn(parent, style);
-               result.setText(name);
-               result.setWidth(width);
-               result.setResizable(true);
-               return result;
-       }
-
-       /**
-        * Creates a TableViewerColumn for the given viewer. For the time being, we do
-        * not support movable columns.
-        */
-       public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) {
-               TableViewerColumn tvc = new TableViewerColumn(parent, style);
-               TableColumn column = tvc.getColumn();
-               column.setText(name);
-               column.setWidth(width);
-               column.setResizable(true);
-               return tvc;
-       }
-
-       // public static TableViewerColumn createTableViewerColumn(TableViewer parent,
-       // Localized name, int style, int width) {
-       // return createTableViewerColumn(parent, name.lead(), style, width);
-       // }
-
-       /**
-        * Creates a TreeViewerColumn for the given viewer. For the time being, we do
-        * not support movable columns.
-        */
-       public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) {
-               TreeViewerColumn tvc = new TreeViewerColumn(parent, style);
-               TreeColumn column = tvc.getColumn();
-               column.setText(name);
-               column.setWidth(width);
-               column.setResizable(true);
-               return tvc;
-       }
-}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java
deleted file mode 100644 (file)
index 798d174..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace JCR helpers. */
-package org.argeo.eclipse.ui.util;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/.classpath b/jcr/org.argeo.cms.jcr/.classpath
deleted file mode 100644 (file)
index 4a00bec..0000000
+++ /dev/null
@@ -1,11 +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">
-               <attributes>
-                       <attribute name="module" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/jcr/org.argeo.cms.jcr/.project b/jcr/org.argeo.cms.jcr/.project
deleted file mode 100644 (file)
index 3e470f8..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.jcr</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs b/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644 (file)
index 7e2e119..0000000
+++ /dev/null
@@ -1,101 +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.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
-org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml
deleted file mode 100644 (file)
index f5fc8de..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.dataServletContext">
-   <implementation class="org.argeo.cms.jcr.internal.servlet.DataServletContext"/>
-   <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
-   </service>
-   <property name="osgi.http.whiteboard.context.name" type="String" value="dataServletContext"/>
-   <property name="osgi.http.whiteboard.context.path" type="String" value="/data"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml
deleted file mode 100644 (file)
index a283ef0..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.filesServlet">
-   <implementation class="org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet"/>
-   <service>
-      <provide interface="javax.servlet.Servlet"/>
-   </service>
-   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
-   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=filesServletContext)"/>
-   <property name="servlet.init.resource-config" type="String" value="/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"/>  
-   <property name="servlet.init.resource-path-prefix" type="String" value="/files"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" policy="static" target="(cn=ego)"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml
deleted file mode 100644 (file)
index 5fb56e3..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.filesServletContext">
-   <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
-   <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
-   </service>
-   <property name="osgi.http.whiteboard.context.name" type="String" value="filesServletContext"/>
-   <property name="osgi.http.whiteboard.context.path" type="String" value="/files"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml
deleted file mode 100644 (file)
index a94b151..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="JCR Deployment">
-   <implementation class="org.argeo.cms.jcr.internal.CmsJcrDeployment"/>
-   <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" policy="static"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml
deleted file mode 100644 (file)
index e26453b..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR FS Provider">
-   <implementation class="org.argeo.cms.jcr.internal.CmsJcrFsProvider"/>
-   <property name="service.pid" type="String" value="org.argeo.api.fsProvider"/>
-   <service>
-      <provide interface="java.nio.file.spi.FileSystemProvider"/>
-   </service>
-   <reference bind="setRepositoryFactory" cardinality="1..1" interface="javax.jcr.RepositoryFactory" name="RepositoryFactory" policy="static"/>
-   <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml
deleted file mode 100644 (file)
index b43b519..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="JCR Repository Factory">
-   <implementation class="org.argeo.cms.jcr.internal.JcrRepositoryFactory"/>
-   <service>
-      <provide interface="javax.jcr.RepositoryFactory"/>
-   </service>
-   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml
deleted file mode 100644 (file)
index a0885bb..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.jcrServletContext">
-   <implementation class="org.argeo.cms.jcr.internal.servlet.JcrServletContext"/>
-   <service>
-      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
-   </service>
-   <property name="osgi.http.whiteboard.context.name" type="String" value="jcrServletContext"/>
-   <property name="osgi.http.whiteboard.context.path" type="String" value="/jcr"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml
deleted file mode 100644 (file)
index db2bfaa..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Jackrabbit Repository Contexts Factory">
-   <implementation class="org.argeo.cms.jcr.internal.RepositoryContextsFactory"/>
-   <property name="service.pid" type="String" value="org.argeo.api.repos"/>
-   <service>
-      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
-   </service>
-   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/jcr/org.argeo.cms.jcr/bnd.bnd b/jcr/org.argeo.cms.jcr/bnd.bnd
deleted file mode 100644 (file)
index 065f0d1..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator
-
-Provide-Capability:\
-cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\
-cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\
-cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\
-osgi.service;objectClass="javax.jcr.Repository"
-
-Import-Package:\
-org.argeo.cms.servlet,\
-javax.jcr.security,\
-org.h2;resolution:=optional;version="[1,3)",\
-org.postgresql;version="[42,43)";resolution:=optional,\
-org.apache.commons.httpclient.cookie;resolution:=optional,\
-org.osgi.framework.namespace;version=0.0.0,\
-org.osgi.*;version=0.0.0,\
-org.osgi.service.http.whiteboard,\
-org.apache.jackrabbit.api.stats;version="[1,4)",\
-org.apache.jackrabbit.api;version="[1,4)",\
-org.apache.jackrabbit.commons;version="[1,4)",\
-org.apache.jackrabbit.spi;version="[1,4)",\
-org.apache.jackrabbit.spi2dav;version="[1,4)",\
-org.apache.jackrabbit.spi2davex;version="[1,4)",\
-org.apache.jackrabbit.webdav.jcr;version="[1,4)",\
-org.apache.jackrabbit.webdav.server;version="[1,4)",\
-org.apache.jackrabbit.webdav.simple;version="[1,4)",\
-org.apache.jackrabbit.*;version="[1,4)",\
-junit.*;resolution:=optional,\
-javax.servlet.*;version="[3,5)",\
-*
-
-Service-Component:\
-OSGI-INF/repositoryContextsFactory.xml,\
-OSGI-INF/jcrRepositoryFactory.xml,\
-OSGI-INF/jcrFsProvider.xml,\
-OSGI-INF/jcrDeployment.xml,\
-OSGI-INF/jcrServletContext.xml,\
-OSGI-INF/dataServletContext.xml,\
-OSGI-INF/filesServletContext.xml,\
-OSGI-INF/filesServlet.xml
diff --git a/jcr/org.argeo.cms.jcr/build.properties b/jcr/org.argeo.cms.jcr/build.properties
deleted file mode 100644 (file)
index 2b99dd7..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/
-source.. = src/
-additional.bundles = \
-org.apache.jackrabbit.data, \
-org.apache.jackrabbit.spi.commons,\
-
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java
deleted file mode 100644 (file)
index 40d38ee..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.argeo.cms.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.Jcr;
-
-/** Utilities around documents. */
-public class CmsFsUtils {
-       // TODO make it more robust and configurable
-       private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE;
-
-       public static Node getNode(Repository repository, Path path) {
-               String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString();
-               String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString();
-               try {
-                       Session newSession;
-                       try {
-                               newSession = repository.login(workspaceName);
-                       } catch (NoSuchWorkspaceException e) {
-                               // base workspace
-                               newSession = repository.login(baseWorkspaceName);
-                               jcrPath = path.toString();
-                       }
-                       return newSession.getNode(jcrPath);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get node from path " + path, e);
-               }
-       }
-
-       public static NodeIterator getLastUpdatedDocuments(Session session) {
-               try {
-                       String qStr = "//element(*, nt:file)";
-                       qStr += " order by @jcr:lastModified descending";
-                       QueryManager queryManager = session.getWorkspace().getQueryManager();
-                       @SuppressWarnings("deprecation")
-                       Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH);
-                       xpathQuery.setLimit(8);
-                       NodeIterator nit = xpathQuery.execute().getNodes();
-                       return nit;
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Unable to retrieve last updated documents", e);
-               }
-       }
-
-       public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) {
-               try {
-                       FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
-                       if (fileSystem == null)
-                               fileSystem = nodeFileSystemProvider.newFileSystem(uri, null);
-                       String path = uri.getPath();
-                       return fileSystem.getPath(path);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Unable to initialise file system for " + uri, e);
-               }
-       }
-
-       public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) {
-               String workspaceName = Jcr.getWorkspaceName(node);
-               String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node)
-                               : '/' + workspaceName + Jcr.getPath(node);
-               URI uri;
-               try {
-                       uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e);
-               }
-               return getPath(nodeFileSystemProvider, uri);
-       }
-
-       /** Singleton. */
-       private CmsFsUtils() {
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java
deleted file mode 100644 (file)
index c289857..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import java.util.Properties;
-
-import org.apache.jackrabbit.core.config.BeanConfig;
-import org.apache.jackrabbit.core.config.ConfigurationException;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig;
-import org.apache.jackrabbit.core.util.db.ConnectionFactory;
-import org.w3c.dom.Element;
-
-/**
- * A {@link RepositoryConfigurationParser} providing more flexibility with
- * classloaders.
- */
-@SuppressWarnings("restriction")
-class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser {
-       private ClassLoader classLoader = null;
-
-       public CustomRepositoryConfigurationParser(Properties variables) {
-               super(variables);
-       }
-
-       public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) {
-               super(variables, connectionFactory);
-       }
-
-       @Override
-       protected RepositoryConfigurationParser createSubParser(Properties variables) {
-               Properties props = new Properties(getVariables());
-               props.putAll(variables);
-               CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props,
-                               connectionFactory);
-               subParser.setClassLoader(classLoader);
-               return subParser;
-       }
-
-       @Override
-       public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException {
-               WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent);
-               workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader);
-               return workspaceSecurityConfig;
-       }
-
-       @Override
-       protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
-               BeanConfig beanConfig = super.parseBeanConfig(parent, name);
-               if (beanConfig.getClassName().startsWith("org.argeo")) {
-                       beanConfig.setClassLoader(classLoader);
-               }
-               return beanConfig;
-       }
-
-       public void setClassLoader(ClassLoader classLoader) {
-               this.classLoader = classLoader;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java
deleted file mode 100644 (file)
index 40c83f6..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-/** Pre-defined Jackrabbit repository configurations. */
-enum JackrabbitType {
-       /** Local file system */
-       localfs,
-       /** Embedded Java H2 database */
-       h2,
-       /** Embedded Java H2 database in PostgreSQL compatibility mode */
-       h2_postgresql,
-       /** PostgreSQL */
-       postgresql,
-       /** PostgreSQL with datastore */
-       postgresql_ds,
-       /** PostgreSQL with cluster */
-       postgresql_cluster,
-       /** PostgreSQL with cluster and datastore */
-       postgresql_cluster_ds,
-       /** Memory */
-       memory;
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java
deleted file mode 100644 (file)
index 0536fb6..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.JcrException;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-
-/** JCR specific init utilities. */
-public class JcrInitUtils {
-       private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class);
-       private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext();
-
-       public static void addToDeployment(CmsDeployment nodeDeployment) {
-               // node repository
-//             Dictionary<String, Object> provided = null;
-               Dictionary<String, Object> provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID,
-                               CmsConstants.NODE);
-               Dictionary<String, Object> nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided);
-               // node repository is mandatory
-               nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig);
-
-               // additional repositories
-//             dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) {
-//                     if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName()))
-//                             continue dataModels;
-//                     Dictionary<String, Object> config = JcrInitUtils.getRepositoryConfig(dataModel.getName(),
-//                                     getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName()));
-//                     if (config.size() != 0)
-//                             putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config);
-//             }
-
-       }
-
-       /** Override the provided config with the framework properties */
-       public static Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
-               Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
-               for (RepoConf repoConf : RepoConf.values()) {
-                       Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
-                       if (value != null) {
-                               props.put(repoConf.name(), value);
-                               if (log.isDebugEnabled())
-                                       log.debug("Set node repo configuration " + repoConf.name() + " to " + value);
-                       }
-               }
-               props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY);
-               return props;
-       }
-
-       public static Dictionary<String, Object> getRepositoryConfig(String dataModelName,
-                       Dictionary<String, Object> provided) {
-               if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY))
-                       throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved.");
-               Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
-               for (RepoConf repoConf : RepoConf.values()) {
-                       Object value = getFrameworkProp(
-                                       CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name());
-                       if (value != null) {
-                               props.put(repoConf.name(), value);
-                               if (log.isDebugEnabled())
-                                       log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value);
-                       }
-               }
-               if (props.size() != 0)
-                       props.put(CmsConstants.CN, dataModelName);
-               return props;
-       }
-
-       private static void registerRemoteInit(String uri) {
-               try {
-                       Repository repository = createRemoteRepository(new URI(uri));
-                       Hashtable<String, Object> properties = new Hashtable<>();
-                       properties.put(CmsConstants.CN, CmsConstants.NODE_INIT);
-                       properties.put(LdapAttrs.labeledURI.name(), uri);
-                       properties.put(Constants.SERVICE_RANKING, -1000);
-                       bundleContext.registerService(Repository.class, repository, properties);
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException(e);
-               }
-       }
-
-       private static Repository createRemoteRepository(URI uri) throws RepositoryException {
-               RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
-               Map<String, String> params = new HashMap<String, String>();
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
-               // TODO make it configurable
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
-               return repositoryFactory.getRepository(params);
-       }
-
-       private static String getFrameworkProp(String key, String def) {
-               String value;
-               if (bundleContext != null)
-                       value = bundleContext.getProperty(key);
-               else
-                       value = System.getProperty(key);
-               if (value == null)
-                       return def;
-               return value;
-       }
-
-       private static String getFrameworkProp(String key) {
-               return getFrameworkProp(key, null);
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java
deleted file mode 100644 (file)
index dba005c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-import org.apache.jackrabbit.core.data.DataIdentifier;
-import org.apache.jackrabbit.core.data.DataRecord;
-import org.apache.jackrabbit.core.data.DataStoreException;
-import org.apache.jackrabbit.core.data.FileDataStore;
-
-/**
- * <b>experimental</b> Duplicate added entries in another directory (typically a
- * remote mount).
- */
-@SuppressWarnings("restriction")
-public class LocalFsDataStore extends FileDataStore {
-       String redundantPath;
-       FileDataStore redundantStore;
-
-       @Override
-       public void init(String homeDir) {
-               // init primary first
-               super.init(homeDir);
-
-               if (redundantPath != null) {
-                       // redundant directory must be created first
-                       // TODO implement some polling?
-                       if (Files.exists(Paths.get(redundantPath))) {
-                               redundantStore = new FileDataStore();
-                               redundantStore.setPath(redundantPath);
-                               redundantStore.init(homeDir);
-                       }
-               }
-       }
-
-       @Override
-       public DataRecord addRecord(InputStream input) throws DataStoreException {
-               DataRecord dataRecord = super.addRecord(input);
-               syncRedundantRecord(dataRecord);
-               return dataRecord;
-       }
-
-       @Override
-       public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
-               DataRecord dataRecord = super.getRecord(identifier);
-               syncRedundantRecord(dataRecord);
-               return dataRecord;
-       }
-
-       protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException {
-               if (redundantStore == null)
-                       return;
-               if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) {
-                       try (InputStream redundant = dataRecord.getStream()) {
-                               redundantStore.addRecord(redundant);
-                       } catch (IOException e) {
-                               throw new DataStoreException("Cannot add redundant record.", e);
-                       }
-               }
-       }
-
-       public void setRedundantPath(String redundantPath) {
-               this.redundantPath = redundantPath;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java
deleted file mode 100644 (file)
index a45656c..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.osgi.metatype.EnumAD;
-import org.argeo.osgi.metatype.EnumOCD;
-
-/** JCR repository configuration */
-public enum RepoConf implements EnumAD {
-       /** Repository type */
-       type("h2"),
-       /** Default workspace */
-       defaultWorkspace(CmsConstants.SYS_WORKSPACE),
-       /** Database URL */
-       dburl(null),
-       /** Database user */
-       dbuser(null),
-       /** Database password */
-       dbpassword(null),
-
-       /** The identifier (can be an URL locating the repo) */
-       labeledUri(null),
-       //
-       // JACKRABBIT SPECIFIC
-       //
-       /** Maximum database pool size */
-       maxPoolSize(10),
-       /** Maximum cache size in MB */
-       maxCacheMB(null),
-       /** Bundle cache size in MB */
-       bundleCacheMB(8),
-       /** Extractor pool size */
-       extractorPoolSize(0),
-       /** Search cache size */
-       searchCacheSize(1000),
-       /** Max volatile index size */
-       maxVolatileIndexSize(1048576),
-       /** Cluster id (if appropriate configuration) */
-       clusterId("default"),
-       /** Indexes base path */
-       indexesBase(null);
-
-       /** The default value. */
-       private Object def;
-       private String oid;
-
-       RepoConf(String oid, Object def) {
-               this.oid = oid;
-               this.def = def;
-       }
-
-       RepoConf(Object def) {
-               this.def = def;
-       }
-
-       public Object getDefault() {
-               return def;
-       }
-
-       @Override
-       public String getID() {
-               if (oid != null)
-                       return oid;
-               return EnumAD.super.getID();
-       }
-
-       public static class OCD extends EnumOCD<RepoConf> {
-               public OCD(String locale) {
-                       super(RepoConf.class, locale);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java
deleted file mode 100644 (file)
index 3db9716..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Properties;
-import java.util.UUID;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.cache.CacheManager;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.config.RepositoryConfigurationParser;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.internal.CmsPaths;
-import org.xml.sax.InputSource;
-
-/** Can interpret properties in order to create an actual JCR repository. */
-public class RepositoryBuilder {
-       private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class);
-
-       public RepositoryContext createRepositoryContext(Dictionary<String, ?> properties)
-                       throws RepositoryException, IOException {
-               RepositoryConfig repositoryConfig = createRepositoryConfig(properties);
-               RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig);
-               RepositoryImpl repository = repositoryContext.getRepository();
-
-               // cache
-               Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB);
-               if (maxCacheMbStr != null) {
-                       Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString());
-                       CacheManager cacheManager = repository.getCacheManager();
-                       cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l);
-                       cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l);
-               }
-
-               return repositoryContext;
-       }
-
-       RepositoryConfig createRepositoryConfig(Dictionary<String, ?> properties) throws RepositoryException, IOException {
-               JackrabbitType type = JackrabbitType.valueOf(prop(properties, RepoConf.type).toString());
-               ClassLoader cl = getClass().getClassLoader();
-               final String base = "/org/argeo/cms/internal/jcr";
-               try (InputStream in = cl.getResourceAsStream(base + "/repository-" + type.name() + ".xml")) {
-                       if (in == null)
-                               throw new IllegalArgumentException("Repository configuration not found");
-                       InputSource config = new InputSource(in);
-                       Properties jackrabbitVars = getConfigurationProperties(type, properties);
-                       // RepositoryConfig repositoryConfig = RepositoryConfig.create(config,
-                       // jackrabbitVars);
-
-                       // custom configuration parser
-                       CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars);
-                       parser.setClassLoader(cl);
-                       RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config);
-                       repositoryConfig.init();
-
-                       // set the proper classloaders
-                       repositoryConfig.getSecurityConfig().getSecurityManagerConfig().setClassLoader(cl);
-                       repositoryConfig.getSecurityConfig().getAccessManagerConfig().setClassLoader(cl);
-//                     for (WorkspaceConfig workspaceConfig : repositoryConfig.getWorkspaceConfigs()) {
-//                             workspaceConfig.getSecurityConfig().getAccessControlProviderConfig().setClassLoader(cl);
-//                     }
-                       return repositoryConfig;
-               }
-       }
-
-       private Properties getConfigurationProperties(JackrabbitType type, Dictionary<String, ?> properties) {
-               Properties props = new Properties();
-               for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
-                       String key = keys.nextElement();
-                       props.put(key, properties.get(key));
-               }
-
-               // cluster id
-               // cf. https://wiki.apache.org/jackrabbit/Clustering
-               // TODO deal with multiple repos
-               String clusterId = System.getProperty("org.apache.jackrabbit.core.cluster.node_id");
-               String clusterIdProp = props.getProperty(RepoConf.clusterId.name());
-               if (clusterId != null) {
-                       if (clusterIdProp != null)
-                               throw new IllegalArgumentException("Cluster id defined as System properties and in deploy config");
-                       props.put(RepoConf.clusterId.name(), clusterId);
-               } else {
-                       clusterId = clusterIdProp;
-               }
-
-               // home
-               String homeUri = props.getProperty(RepoConf.labeledUri.name());
-               Path homePath;
-               if (homeUri == null) {
-                       String cn = props.getProperty(CmsConstants.CN);
-                       assert cn != null;
-                       if (clusterId != null) {
-                               homePath = CmsPaths.getRepoDirPath(cn + '/' + clusterId);
-                       } else {
-                               homePath = CmsPaths.getRepoDirPath(cn);
-                       }
-               } else {
-                       try {
-                               URI uri = new URI(homeUri);
-                               String host = uri.getHost();
-                               if (host == null || host.trim().equals("")) {
-                                       homePath = Paths.get(uri).toAbsolutePath();
-                               } else {
-                                       // TODO remote at this stage?
-                                       throw new IllegalArgumentException("Cannot manage repository path for host " + host);
-                               }
-                       } catch (URISyntaxException e) {
-                               throw new IllegalArgumentException("Invalid repository home URI", e);
-                       }
-               }
-               // TODO use Jackrabbit API (?)
-               Path rootUuidPath = homePath.resolve("repository/meta/rootUUID");
-               try {
-                       if (!Files.exists(rootUuidPath)) {
-                               Files.createDirectories(rootUuidPath.getParent());
-                               Files.write(rootUuidPath, UUID.randomUUID().toString().getBytes());
-                       }
-                       // File homeDir = homePath.toFile();
-                       // homeDir.mkdirs();
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot set up repository  home " + homePath, e);
-               }
-               // home cannot be overridden
-               props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homePath.toString());
-
-               setProp(props, RepoConf.indexesBase, CmsPaths.getRepoIndexesBase().toString());
-               // common
-               setProp(props, RepoConf.defaultWorkspace);
-               setProp(props, RepoConf.maxPoolSize);
-               // Jackrabbit defaults
-               setProp(props, RepoConf.bundleCacheMB);
-               // See http://wiki.apache.org/jackrabbit/Search
-               setProp(props, RepoConf.extractorPoolSize);
-               setProp(props, RepoConf.searchCacheSize);
-               setProp(props, RepoConf.maxVolatileIndexSize);
-
-               // specific
-               String dburl;
-               switch (type) {
-               case h2:
-                       dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository";
-                       setProp(props, RepoConf.dburl, dburl);
-                       setProp(props, RepoConf.dbuser, "sa");
-                       setProp(props, RepoConf.dbpassword, "");
-                       break;
-               case h2_postgresql:
-                       dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE";
-                       setProp(props, RepoConf.dburl, dburl);
-                       setProp(props, RepoConf.dbuser, "sa");
-                       setProp(props, RepoConf.dbpassword, "");
-                       break;
-               case postgresql:
-               case postgresql_ds:
-               case postgresql_cluster:
-               case postgresql_cluster_ds:
-                       dburl = "jdbc:postgresql://localhost/demo";
-                       setProp(props, RepoConf.dburl, dburl);
-                       setProp(props, RepoConf.dbuser, "argeo");
-                       setProp(props, RepoConf.dbpassword, "argeo");
-                       break;
-               case memory:
-                       break;
-               case localfs:
-                       break;
-               default:
-                       throw new IllegalArgumentException("Unsupported node type " + type);
-               }
-               return props;
-       }
-
-       private void setProp(Properties props, RepoConf key, String def) {
-               Object value = props.get(key.name());
-               if (value == null)
-                       value = def;
-               if (value == null)
-                       value = key.getDefault();
-               if (value != null)
-                       props.put(key.name(), value.toString());
-       }
-
-       private void setProp(Properties props, RepoConf key) {
-               setProp(props, key, null);
-       }
-
-       private String prop(Dictionary<String, ?> properties, RepoConf key) {
-               Object value = properties.get(key.name());
-               if (value == null)
-                       return key.getDefault() != null ? key.getDefault().toString() : null;
-               else
-                       return value.toString();
-       }
-
-       private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException {
-               ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader());
-               try {
-                       long begin = System.currentTimeMillis();
-                       //
-                       // Actual repository creation
-                       //
-                       RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig);
-
-                       double duration = ((double) (System.currentTimeMillis() - begin)) / 1000;
-                       if (log.isDebugEnabled())
-                               log.debug(
-                                               "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir());
-
-                       return repositoryContext;
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextCl);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml
deleted file mode 100644 (file)
index ace0fa5..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.h2.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="h2" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="default" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!--                   <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
-                       <param name="supportHighlighting" value="true" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!--           <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
-               <param name="supportHighlighting" value="true" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml
deleted file mode 100644 (file)
index 4303676..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.h2.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="default" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!--                   <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
-                       <param name="supportHighlighting" value="true" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-<!--           <param name="tikaConfigPath" value="${indexesBase}/${cn}/tika-config.xml" /> -->
-               <param name="supportHighlighting" value="true" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml
deleted file mode 100644 (file)
index b889079..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-               <param name="path" value="${rep.home}/repository" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-                       <param name="path" value="${wsp.home}" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-                       <param name="path" value="${rep.home}/version" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml
deleted file mode 100644 (file)
index 3630a14..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="directoryManagerClass"
-                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="directoryManagerClass"
-                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml
deleted file mode 100644 (file)
index de2f245..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml
deleted file mode 100644 (file)
index 488ad6b..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-
-       <!-- Clustering -->
-       <Cluster id="${clusterId}">
-               <Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="journal_" />
-               </Journal>
-       </Cluster>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml
deleted file mode 100644 (file)
index b430674..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem
-               class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore
-               class="org.argeo.cms.internal.jcr.LocalFsDataStore">
-               <param name="path" value="${rep.home}/../datastore" />
-               <param name="redundantPath" value="${rep.home}/../datastorer" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem
-                       class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex
-                       class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path"
-                               value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize"
-                               value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem
-                       class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex
-               class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize"
-                       value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager
-                       class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager
-                       class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-
-       <!-- Clustering -->
-       <Cluster id="${clusterId}" syncDelay="100">
-               <Journal
-                       class="org.apache.jackrabbit.core.journal.DatabaseJournal">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="journal_" />
-               </Journal>
-       </Cluster>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml
deleted file mode 100644 (file)
index 5229d16..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${indexesBase}/${cn}/${wsp.name}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${indexesBase}/${cn}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java
deleted file mode 100644 (file)
index 4a28dca..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-package org.argeo.cms.jcr;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.AuthPermission;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-
-/** Utilities related to Argeo model in JCR */
-public class CmsJcrUtils {
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link CmsConstants#CN} in order to simplify it and protect against future
-        * API changes.
-        */
-       public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) {
-               try {
-                       Map<String, String> parameters = new HashMap<String, String>();
-                       parameters.put(CmsConstants.CN, alias);
-                       return repositoryFactory.getRepository(parameters);
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias,
-                                       e);
-               }
-       }
-
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
-        * future API changes.
-        */
-       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) {
-               return getRepositoryByUri(repositoryFactory, uri, null);
-       }
-
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
-        * future API changes.
-        */
-       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) {
-               try {
-                       Map<String, String> parameters = new HashMap<String, String>();
-                       parameters.put(CmsConstants.LABELED_URI, uri);
-                       if (alias != null)
-                               parameters.put(CmsConstants.CN, alias);
-                       return repositoryFactory.getRepository(parameters);
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e);
-               }
-       }
-
-       /**
-        * Returns the home node of the user or null if none was found.
-        * 
-        * @param session  the session to use in order to perform the search, this can
-        *                 be a session with a different user ID than the one searched,
-        *                 typically when a system or admin session is used.
-        * @param username the username of the user
-        */
-       public static Node getUserHome(Session session, String username) {
-//             try {
-//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-//                     Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
-//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
-//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
-//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-//                     Query query = qomf.createQuery(sel, constraint, null, null);
-//                     return querySingleNode(query);
-//             } catch (RepositoryException e) {
-//                     throw new RuntimeException("Cannot find home for user " + username, e);
-//             }
-
-               try {
-                       checkUserWorkspace(session, username);
-                       String homePath = getHomePath(username);
-                       if (session.itemExists(homePath))
-                               return session.getNode(homePath);
-                       // legacy
-                       homePath = "/home/" + username;
-                       if (session.itemExists(homePath))
-                               return session.getNode(homePath);
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Cannot find home for user " + username, e);
-               }
-       }
-
-       private static String getHomePath(String username) {
-               LdapName dn;
-               try {
-                       dn = new LdapName(username);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Invalid name " + username, e);
-               }
-               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
-               return '/' + userId;
-       }
-
-       private static void checkUserWorkspace(Session session, String username) {
-               String workspaceName = session.getWorkspace().getName();
-               if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName))
-                       throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
-       }
-
-       /**
-        * Returns the home node of the user or null if none was found.
-        * 
-        * @param session   the session to use in order to perform the search, this can
-        *                  be a session with a different user ID than the one searched,
-        *                  typically when a system or admin session is used.
-        * @param groupname the name of the group
-        */
-       public static Node getGroupHome(Session session, String groupname) {
-//             try {
-//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
-//                     Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
-//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
-//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
-//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
-//                     Query query = qomf.createQuery(sel, constraint, null, null);
-//                     return querySingleNode(query);
-//             } catch (RepositoryException e) {
-//                     throw new RuntimeException("Cannot find home for group " + cn, e);
-//             }
-
-               try {
-                       checkGroupWorkspace(session, groupname);
-                       String homePath = getGroupPath(groupname);
-                       if (session.itemExists(homePath))
-                               return session.getNode(homePath);
-                       // legacy
-                       homePath = "/groups/" + groupname;
-                       if (session.itemExists(homePath))
-                               return session.getNode(homePath);
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Cannot find home for group " + groupname, e);
-               }
-
-       }
-
-       private static String getGroupPath(String groupname) {
-               String cn;
-               try {
-                       LdapName dn = new LdapName(groupname);
-                       cn = dn.getRdn(dn.size() - 1).getValue().toString();
-               } catch (InvalidNameException e) {
-                       cn = groupname;
-               }
-               return '/' + cn;
-       }
-
-       private static void checkGroupWorkspace(Session session, String groupname) {
-               String workspaceName = session.getWorkspace().getName();
-               if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName))
-                       throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
-       }
-
-       /**
-        * Queries one single node.
-        * 
-        * @return one single node or null if none was found
-        * @throws ArgeoJcrException if more than one node was found
-        */
-//     private static Node querySingleNode(Query query) {
-//             NodeIterator nodeIterator;
-//             try {
-//                     QueryResult queryResult = query.execute();
-//                     nodeIterator = queryResult.getNodes();
-//             } catch (RepositoryException e) {
-//                     throw new RuntimeException("Cannot execute query " + query, e);
-//             }
-//             Node node;
-//             if (nodeIterator.hasNext())
-//                     node = nodeIterator.nextNode();
-//             else
-//                     return null;
-//
-//             if (nodeIterator.hasNext())
-//                     throw new RuntimeException("Query returned more than one node.");
-//             return node;
-//     }
-
-       /** Returns the home node of the session user or null if none was found. */
-       public static Node getUserHome(Session session) {
-               String userID = session.getUserID();
-               return getUserHome(session, userID);
-       }
-
-       /** Whether this node is the home of the user of the underlying session. */
-       public static boolean isUserHome(Node node) {
-               try {
-                       String userID = node.getSession().getUserID();
-                       return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       /**
-        * Translate the path to this node into a path containing the name of the
-        * repository and the name of the workspace.
-        */
-       public static String getDataPath(String cn, Node node) {
-               assert node != null;
-               StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
-               try {
-                       return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName())
-                                       .append(node.getPath()).toString();
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e);
-               }
-       }
-
-       /**
-        * Translate the path to this node into a path containing the name of the
-        * repository and the name of the workspace.
-        */
-       public static String getDataPath(Node node) {
-               return getDataPath(CmsConstants.NODE, node);
-       }
-
-       /**
-        * Open a JCR session with full read/write rights on the data, as
-        * {@link CmsConstants#ROLE_USER_ADMIN}, using the
-        * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security hardened
-        * deployement, use {@link AuthPermission} on this login context.
-        */
-       public static Session openDataAdminSession(Repository repository, String workspaceName) {
-               LoginContext loginContext;
-               try {
-                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
-                       loginContext.login();
-               } catch (LoginException e1) {
-                       throw new RuntimeException("Could not login as data admin", e1);
-               } finally {
-               }
-
-               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
-               try {
-                       Thread.currentThread().setContextClassLoader(CmsJcrUtils.class.getClassLoader());
-                       return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-
-                               @Override
-                               public Session run() {
-                                       try {
-                                               return repository.login(workspaceName);
-                                       } catch (NoSuchWorkspaceException e) {
-                                               throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
-                                       } catch (RepositoryException e) {
-                                               throw new RuntimeException("Cannot open data admin session", e);
-                                       }
-                               }
-
-                       });
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentCl);
-               }
-       }
-
-       /** Singleton. */
-       private CmsJcrUtils() {
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
deleted file mode 100644 (file)
index 04c5d2d..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.argeo.cms.jcr.acr;
-
-import java.util.Calendar;
-import java.util.Iterator;
-import java.util.Optional;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.AbstractContent;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-
-public class JcrContent extends AbstractContent {
-       private Node jcrNode;
-
-       private JcrContentProvider provider;
-       private ProvidedSession session;
-
-       protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) {
-               this.session = session;
-               this.provider = provider;
-               this.jcrNode = node;
-       }
-
-       @Override
-       public QName getName() {
-               return session.parsePrefixedName(Jcr.getName(jcrNode));
-       }
-
-       @Override
-       public <A> Optional<A> get(QName key, Class<A> clss) {
-               if (isDefaultAttrTypeRequested(clss)) {
-                       return Optional.of((A) get(jcrNode, key.toString()));
-               }
-               return Optional.of((A) Jcr.get(jcrNode, key.toString()));
-       }
-
-       @Override
-       public Iterator<Content> iterator() {
-               try {
-                       return new JcrContentIterator(jcrNode.getNodes());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot list children of " + jcrNode, e);
-               }
-       }
-
-       @Override
-       protected Iterable<QName> keys() {
-               return new Iterable<QName>() {
-
-                       @Override
-                       public Iterator<QName> iterator() {
-                               try {
-                                       PropertyIterator propertyIterator = jcrNode.getProperties();
-                                       return new JcrKeyIterator(provider, propertyIterator);
-                               } catch (RepositoryException e) {
-                                       throw new JcrException("Cannot retrive properties from " + jcrNode, e);
-                               }
-                       }
-               };
-       }
-
-       public Node getJcrNode() {
-               return jcrNode;
-       }
-
-       /** Cast to a standard Java object. */
-       static Object get(Node node, String property) {
-               try {
-                       Value value = node.getProperty(property).getValue();
-                       switch (value.getType()) {
-                       case PropertyType.STRING:
-                               return value.getString();
-                       case PropertyType.DOUBLE:
-                               return (Double) value.getDouble();
-                       case PropertyType.LONG:
-                               return (Long) value.getLong();
-                       case PropertyType.BOOLEAN:
-                               return (Boolean) value.getBoolean();
-                       case PropertyType.DATE:
-                               Calendar calendar = value.getDate();
-                               return calendar.toInstant();
-                       case PropertyType.BINARY:
-                               throw new IllegalArgumentException("Binary is not supported as an attribute");
-                       default:
-                               return value.getString();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot cast value from " + property + " of node " + node, e);
-               }
-       }
-
-       class JcrContentIterator implements Iterator<Content> {
-               private final NodeIterator nodeIterator;
-               // we keep track in order to be able to delete it
-               private JcrContent current = null;
-
-               protected JcrContentIterator(NodeIterator nodeIterator) {
-                       this.nodeIterator = nodeIterator;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return nodeIterator.hasNext();
-               }
-
-               @Override
-               public Content next() {
-                       current = new JcrContent(session, provider, nodeIterator.nextNode());
-                       return current;
-               }
-
-               @Override
-               public void remove() {
-                       if (current != null) {
-                               Jcr.remove(current.getJcrNode());
-                       }
-               }
-
-       }
-
-       @Override
-       public Content getParent() {
-               return new JcrContent(session, provider, Jcr.getParent(getJcrNode()));
-       }
-
-       @Override
-       public Content add(QName name, QName... classes) {
-               if (classes.length > 0) {
-                       QName primaryType = classes[0];
-                       Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
-                       for (int i = 1; i < classes.length; i++) {
-                               try {
-                                       child.addMixin(classes[i].toString());
-                               } catch (RepositoryException e) {
-                                       throw new JcrException("Cannot add child to " + getJcrNode(), e);
-                               }
-                       }
-
-               } else {
-                       Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED);
-               }
-               return null;
-       }
-
-       @Override
-       public void remove() {
-               Jcr.remove(getJcrNode());
-       }
-
-       @Override
-       protected void removeAttr(QName key) {
-               Property property = Jcr.getProperty(getJcrNode(), key.toString());
-               if (property != null) {
-                       try {
-                               property.remove();
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
-                       }
-               }
-
-       }
-
-       class JcrKeyIterator implements Iterator<QName> {
-               private final JcrContentProvider contentSession;
-               private final PropertyIterator propertyIterator;
-
-               protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
-                       this.contentSession = contentSession;
-                       this.propertyIterator = propertyIterator;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return propertyIterator.hasNext();
-               }
-
-               @Override
-               public QName next() {
-                       Property property = null;
-                       try {
-                               property = propertyIterator.nextProperty();
-                               // TODO map standard property names
-                               return session.parsePrefixedName(property.getName());
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot retrieve property " + property, null);
-                       }
-               }
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java
deleted file mode 100644 (file)
index ef8e375..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.cms.jcr.acr;
-
-import java.util.Arrays;
-import java.util.Iterator;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.xml.namespace.NamespaceContext;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.spi.ContentProvider;
-import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrContentProvider implements ContentProvider, NamespaceContext {
-       private Repository jcrRepository;
-       private Session adminSession;
-
-       public void init() {
-               adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
-       }
-
-       public void destroy() {
-               JcrUtils.logoutQuietly(adminSession);
-       }
-
-       public void setJcrRepository(Repository jcrRepository) {
-               this.jcrRepository = jcrRepository;
-       }
-
-       @Override
-       public Content get(ProvidedSession session, String mountPath, String relativePath) {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-       @Override
-       public String getNamespaceURI(String prefix) {
-               try {
-                       return adminSession.getNamespaceURI(prefix);
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       @Override
-       public String getPrefix(String namespaceURI) {
-               try {
-                       return adminSession.getNamespacePrefix(namespaceURI);
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       @Override
-       public Iterator<String> getPrefixes(String namespaceURI) {
-               try {
-                       return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd
deleted file mode 100644 (file)
index c9e6ee7..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<argeo = 'http://www.argeo.org/ns/argeo'>
-
-// GENERIC TYPES
-[argeo:remoteRepository] > nt:unstructured
-- argeo:uri (STRING)
-- argeo:userID (STRING)
-+ argeo:password (argeo:encrypted)
-
-// TABULAR CONTENT
-[argeo:table] > nt:file
-+ * (argeo:column) *
-
-[argeo:column] > mix:title
-- jcr:requiredType (STRING) = 'STRING'
-
-[argeo:csv] > nt:resource
-
-// CRYPTO
-[argeo:encrypted]
-mixin
-// initialization vector used by some algorithms
-- argeo:iv (BINARY)
-
-[argeo:pbeKeySpec]
-mixin
-- argeo:secretKeyFactory (STRING)
-- argeo:salt (BINARY)
-- argeo:iterationCount (LONG)
-- argeo:keyLength (LONG)
-- argeo:secretKeyEncryption (STRING)
-
-[argeo:pbeSpec] > argeo:pbeKeySpec
-mixin
-- argeo:cipher (STRING)
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd
deleted file mode 100644 (file)
index 80849be..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-// DN (see https://tools.ietf.org/html/rfc4514)
-<cn = 'http://www.argeo.org/ns/rfc4514/cn'>
-<l = 'http://www.argeo.org/ns/rfc4514/l'>
-<st = 'http://www.argeo.org/ns/rfc4514/st'>
-<o = 'http://www.argeo.org/ns/rfc4514/o'>
-<ou = 'http://www.argeo.org/ns/rfc4514/ou'>
-<c = 'http://www.argeo.org/ns/rfc4514/c'>
-<street = 'http://www.argeo.org/ns/rfc4514/street'>
-<dc = 'http://www.argeo.org/ns/rfc4514/dc'>
-<uid = 'http://www.argeo.org/ns/rfc4514/uid'>
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java
deleted file mode 100644 (file)
index 16f5979..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
-import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.callback.CallbackHandler;
-import javax.servlet.Servlet;
-
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsDeployment;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.internal.jcr.JcrInitUtils;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet;
-import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet;
-import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.cms.security.Keyring;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.LangUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
-import org.osgi.service.cm.ManagedService;
-import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Implementation of a CMS deployment. */
-public class CmsJcrDeployment {
-       private final CmsLog log = CmsLog.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private DataModels dataModels;
-       private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG;
-
-       private boolean argeoDataModelExtensionsAvailable = false;
-
-       // Readiness
-       private boolean nodeAvailable = false;
-
-       CmsDeployment cmsDeployment;
-
-       public void start() {
-               dataModels = new DataModels(bc);
-
-               ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
-               repoContextSt.open();
-               //KernelUtils.asyncOpen(repoContextSt);
-
-//             nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
-
-               JcrInitUtils.addToDeployment(cmsDeployment);
-
-       }
-
-       public void stop() {
-//             if (nodeHttp != null)
-//                     nodeHttp.destroy();
-
-               try {
-                       for (ServiceReference<JackrabbitLocalRepository> sr : bc
-                                       .getServiceReferences(JackrabbitLocalRepository.class, null)) {
-                               bc.getService(sr).destroy();
-                       }
-               } catch (InvalidSyntaxException e1) {
-                       log.error("Cannot clean repositories", e1);
-               }
-
-       }
-
-       public void setCmsDeployment(CmsDeployment cmsDeployment) {
-               this.cmsDeployment = cmsDeployment;
-       }
-
-       /**
-        * Checks whether the deployment is available according to expectations, and
-        * mark it as available.
-        */
-//     private synchronized void checkReadiness() {
-//             if (isAvailable())
-//                     return;
-//             if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
-//                     String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
-//                     String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
-//                     availableSince = System.currentTimeMillis();
-//                     long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-//                     String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-//                     log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
-//                     if (log.isDebugEnabled()) {
-//                             log.debug("## state: " + state);
-//                             if (data != null)
-//                                     log.debug("## data: " + data);
-//                     }
-//                     long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
-//                     long initDuration = System.currentTimeMillis() - begin;
-//                     if (log.isTraceEnabled())
-//                             log.trace("Kernel initialization took " + initDuration + "ms");
-//                     tributeToFreeSoftware(initDuration);
-//             }
-//     }
-
-       private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
-//             if (availableSince != null) {
-//                     throw new IllegalStateException("Deployment is already available");
-//             }
-
-               // home
-               prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
-
-               // init from backup
-//             if (deployConfig.isFirstInit()) {
-//                     Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
-//                     if (Files.exists(restorePath)) {
-//                             if (log.isDebugEnabled())
-//                                     log.debug("Found backup " + restorePath + ", restoring it...");
-//                             LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
-//                             KernelUtils.doAsDataAdmin(logicalRestore);
-//                             log.info("Restored backup from " + restorePath);
-//                     }
-//             }
-
-               // init from repository
-               Collection<ServiceReference<Repository>> initRepositorySr;
-               try {
-                       initRepositorySr = bc.getServiceReferences(Repository.class,
-                                       "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
-               } catch (InvalidSyntaxException e1) {
-                       throw new IllegalArgumentException(e1);
-               }
-               Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
-               while (it.hasNext()) {
-                       ServiceReference<Repository> sr = it.next();
-                       Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
-                       Repository initRepository = bc.getService(sr);
-                       if (log.isDebugEnabled())
-                               log.debug("Found init repository " + labeledUri + ", copying it...");
-                       initFromRepository(deployedNodeRepository, initRepository);
-                       log.info("Node repository initialised from " + labeledUri);
-               }
-       }
-
-       /** Init from a (typically remote) repository. */
-       private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
-               Session initSession = null;
-               try {
-                       initSession = initRepository.login();
-                       workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
-                               if ("security".equals(workspaceName))
-                                       continue workspaces;
-                               if (log.isDebugEnabled())
-                                       log.debug("Copying workspace " + workspaceName + " from init repository...");
-                               long begin = System.currentTimeMillis();
-                               Session targetSession = null;
-                               Session sourceSession = null;
-                               try {
-                                       try {
-                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
-                                       } catch (IllegalArgumentException e) {// no such workspace
-                                               Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
-                                               try {
-                                                       adminSession.getWorkspace().createWorkspace(workspaceName);
-                                               } finally {
-                                                       Jcr.logout(adminSession);
-                                               }
-                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
-                                       }
-                                       sourceSession = initRepository.login(workspaceName);
-//                                     JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
-                                       // TODO deal with referenceable nodes
-                                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
-                                       targetSession.save();
-                                       long duration = System.currentTimeMillis() - begin;
-                                       if (log.isDebugEnabled())
-                                               log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
-                                                               + " s");
-                               } catch (Exception e) {
-                                       log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
-                               } finally {
-                                       Jcr.logout(sourceSession);
-                                       Jcr.logout(targetSession);
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } finally {
-                       Jcr.logout(initSession);
-               }
-       }
-
-       private void prepareHomeRepository(RepositoryImpl deployedRepository) {
-               Session adminSession = KernelUtils.openAdminSession(deployedRepository);
-               try {
-                       argeoDataModelExtensionsAvailable = Arrays
-                                       .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
-                                       .contains(ArgeoNames.ARGEO_NAMESPACE);
-               } catch (RepositoryException e) {
-                       log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
-                       argeoDataModelExtensionsAvailable = false;
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-
-               // Publish home with the highest service ranking
-               Hashtable<String, Object> regProps = new Hashtable<>();
-               regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
-               regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               Repository egoRepository = new EgoRepository(deployedRepository, false);
-               bc.registerService(Repository.class, egoRepository, regProps);
-               registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
-
-               // Keyring only if Argeo extensions are available
-               if (argeoDataModelExtensionsAvailable) {
-                       new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
-
-                               @Override
-                               public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
-                                       NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
-                                       CallbackHandler callbackHandler = bc.getService(reference);
-                                       nodeKeyring.setDefaultCallbackHandler(callbackHandler);
-                                       bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
-                                                       nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
-                                       return callbackHandler;
-                               }
-
-                       }.open();
-               }
-       }
-
-       /** Session is logged out. */
-       private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
-               Session adminSession = KernelUtils.openAdminSession(repository);
-               try {
-                       Set<String> processed = new HashSet<String>();
-                       bundles: for (Bundle bundle : bc.getBundles()) {
-                               BundleWiring wiring = bundle.adapt(BundleWiring.class);
-                               if (wiring == null)
-                                       continue bundles;
-                               if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
-                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
-                               else {
-                                       List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-                                       for (BundleCapability capability : capabilities) {
-                                               String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
-                                               if (dataModelName.equals(cn))// process only own data model
-                                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
-                                       }
-                               }
-                       }
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-       private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
-                       boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
-               // recursively process requirements first
-               List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
-               for (BundleWire wire : requiredWires) {
-                       processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
-               }
-
-               List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-               capabilities: for (BundleCapability capability : capabilities) {
-                       if (!importListedAbstractModels
-                                       && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
-                               continue capabilities;
-                       }
-                       boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
-                       if (publish)
-                               publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
-               }
-       }
-
-       private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
-                       Set<String> processed) {
-               Map<String, Object> attrs = capability.getAttributes();
-               String name = (String) attrs.get(DataModelNamespace.NAME);
-               if (processed.contains(name)) {
-                       if (log.isTraceEnabled())
-                               log.trace("Data model " + name + " has already been processed");
-                       return false;
-               }
-
-               // CND
-               String path = (String) attrs.get(DataModelNamespace.CND);
-               if (path != null) {
-                       File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
-                       if (!dataModel.exists()) {
-                               URL url = capability.getRevision().getBundle().getResource(path);
-                               if (url == null)
-                                       throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
-                               try (Reader reader = new InputStreamReader(url.openStream())) {
-                                       CndImporter.registerNodeTypes(reader, adminSession, true);
-                                       processed.add(name);
-                                       dataModel.getParentFile().mkdirs();
-                                       dataModel.createNewFile();
-                                       if (log.isDebugEnabled())
-                                               log.debug("Registered CND " + url);
-                               } catch (Exception e) {
-                                       log.error("Cannot import CND " + url, e);
-                               }
-                       }
-               }
-
-               if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
-                       return false;
-               // Non abstract
-               boolean isStandalone = isStandalone(name);
-               boolean publishLocalRepo;
-               if (isStandalone && name.equals(cn))// includes the node itself
-                       publishLocalRepo = true;
-               else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
-                       publishLocalRepo = true;
-               else
-                       publishLocalRepo = false;
-
-               return publishLocalRepo;
-       }
-
-       boolean isStandalone(String dataModelName) {
-               return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
-       }
-
-       private void publishLocalRepo(String dataModelName, Repository repository) {
-               Hashtable<String, Object> properties = new Hashtable<>();
-               properties.put(CmsConstants.CN, dataModelName);
-               LocalRepository localRepository;
-               String[] classes;
-               if (repository instanceof RepositoryImpl) {
-                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
-                                       JackrabbitLocalRepository.class.getName() };
-               } else {
-                       localRepository = new LocalRepository(repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
-               }
-               bc.registerService(classes, localRepository, properties);
-
-               // TODO make it configurable
-               registerRepositoryServlets(dataModelName, localRepository);
-               if (log.isTraceEnabled())
-                       log.trace("Published data model " + dataModelName);
-       }
-
-//     @Override
-//     public synchronized Long getAvailableSince() {
-//             return availableSince;
-//     }
-//
-//     public synchronized boolean isAvailable() {
-//             return availableSince != null;
-//     }
-
-       protected void registerRepositoryServlets(String alias, Repository repository) {
-               registerRemotingServlet(alias, repository);
-               registerWebdavServlet(alias, repository);
-       }
-
-       protected void registerWebdavServlet(String alias, Repository repository) {
-               CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
-               Hashtable<String, String> ip = new Hashtable<>();
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                               "/" + alias);
-
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
-                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
-               bc.registerService(Servlet.class, webdavServlet, ip);
-       }
-
-       protected void registerRemotingServlet(String alias, Repository repository) {
-               CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
-               Hashtable<String, String> ip = new Hashtable<>();
-               ip.put(CmsConstants.CN, alias);
-               // Properties ip = new Properties();
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                               "/" + alias);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
-                               "Negotiate");
-
-               // Looks like a bug in Jackrabbit remoting init
-               Path tmpDir;
-               try {
-                       tmpDir = Files.createTempDirectory("remoting_" + alias);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
-               }
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
-                               "remoting_" + alias);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
-                               JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
-
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
-                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
-               bc.registerService(Servlet.class, remotingServlet, ip);
-       }
-
-       private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
-
-               public RepositoryContextStc() {
-                       super(bc, RepositoryContext.class, null);
-               }
-
-               @Override
-               public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
-                       RepositoryContext repoContext = bc.getService(reference);
-                       String cn = (String) reference.getProperty(CmsConstants.CN);
-                       if (cn != null) {
-                               List<String> publishAsLocalRepo = new ArrayList<>();
-                               if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
-//                                     JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
-                                       prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
-                                       // TODO separate home repository
-                                       prepareHomeRepository(repoContext.getRepository());
-                                       registerRepositoryServlets(cn, repoContext.getRepository());
-                                       nodeAvailable = true;
-//                                     checkReadiness();
-                               } else {
-                                       prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
-                               }
-                               // Publish all at once, so that bundles with multiple CNDs are consistent
-                               for (String dataModelName : publishAsLocalRepo)
-                                       publishLocalRepo(dataModelName, repoContext.getRepository());
-                       }
-                       return repoContext;
-               }
-
-               @Override
-               public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
-               }
-
-               @Override
-               public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
-               }
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java
deleted file mode 100644 (file)
index 0099b3b..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Path;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-import org.argeo.jcr.fs.JcrFsException;
-
-/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */
-public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider {
-       private Map<String, CmsFileSystem> fileSystems = new HashMap<>();
-
-       private RepositoryFactory repositoryFactory;
-       private Repository repository;
-
-       @Override
-       public String getScheme() {
-               return CmsConstants.SCHEME_NODE;
-       }
-
-       @Override
-       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-//             BundleContext bc = FrameworkUtil.getBundle(CmsJcrFsProvider.class).getBundleContext();
-               String username = CurrentUser.getUsername();
-               if (username == null) {
-                       // TODO deal with anonymous
-                       return null;
-               }
-               if (fileSystems.containsKey(username))
-                       throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username);
-
-               try {
-                       String host = uri.getHost();
-                       if (host != null && !host.trim().equals("")) {
-                               URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null);
-//                             RepositoryFactory repositoryFactory = bc.getService(bc.getServiceReference(RepositoryFactory.class));
-                               Repository repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, repoUri.toString());
-                               CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
-                               fileSystems.put(username, fileSystem);
-                               return fileSystem;
-                       } else {
-//                             Repository repository = bc.getService(
-//                                             bc.getServiceReferences(Repository.class, "(cn=" + CmsConstants.EGO_REPOSITORY + ")")
-//                                                             .iterator().next());
-
-                               // Session session = repository.login();
-                               CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
-                               fileSystems.put(username, fileSystem);
-                               return fileSystem;
-                       }
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Cannot open file system " + uri + " for user " + username, e);
-               }
-       }
-
-       @Override
-       public FileSystem getFileSystem(URI uri) {
-               return currentUserFileSystem();
-       }
-
-       @Override
-       public Path getPath(URI uri) {
-               JcrFileSystem fileSystem = currentUserFileSystem();
-               String path = uri.getPath();
-               if (fileSystem == null)
-                       try {
-                               fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
-                       } catch (IOException e) {
-                               throw new JcrFsException("Could not autocreate file system", e);
-                       }
-               return fileSystem.getPath(path);
-       }
-
-       protected JcrFileSystem currentUserFileSystem() {
-               String username = CurrentUser.getUsername();
-               return fileSystems.get(username);
-       }
-
-       public Node getUserHome(Repository repository) {
-               try {
-                       Session session = repository.login(CmsConstants.HOME_WORKSPACE);
-                       return CmsJcrUtils.getUserHome(session);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get user home", e);
-               }
-       }
-
-       public void setRepositoryFactory(RepositoryFactory repositoryFactory) {
-               this.repositoryFactory = repositoryFactory;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       static class CmsFileSystem extends JcrFileSystem {
-               public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
-                       super(provider, repository);
-               }
-
-               public boolean skipNode(Node node) throws RepositoryException {
-//                     if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME)
-//                                     || node.isNodeType(NodeTypes.NODE_GROUP_HOME))
-                       if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
-                               return false;
-                       // FIXME Better identifies home
-                       if (node.hasProperty(Property.JCR_ID))
-                               return false;
-                       return true;
-               }
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java
deleted file mode 100644 (file)
index e7f5a55..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.nio.file.Path;
-
-/** Centralises access to the default node deployment directories. */
-public class CmsPaths {
-       public static Path getRepoDirPath(String cn) {
-               return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn);
-       }
-
-       public static Path getRepoIndexesBase() {
-               return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES);
-       }
-
-       /** Singleton. */
-       private CmsPaths() {
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java
deleted file mode 100644 (file)
index 69b98dc..0000000
+++ /dev/null
@@ -1,342 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.util.GregorianCalendar;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.version.VersionManager;
-
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-
-/** Ensure consistency of files, folder and last modified nodes. */
-class CmsWorkspaceIndexer implements EventListener {
-       private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class);
-
-//     private final static String MIX_ETAG = "mix:etag";
-       private final static String JCR_ETAG = "jcr:etag";
-//     private final static String JCR_LAST_MODIFIED = "jcr:lastModified";
-//     private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-//     private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
-       private final static String JCR_DATA = "jcr:data";
-       private final static String JCR_CONTENT = "jcr:data";
-
-       private String cn;
-       private String workspaceName;
-       private RepositoryImpl repositoryImpl;
-       private Session session;
-       private VersionManager versionManager;
-
-       private LinkedBlockingDeque<Event> toProcess = new LinkedBlockingDeque<>();
-       private IndexingThread indexingThread;
-       private AtomicBoolean stopping = new AtomicBoolean(false);
-
-       public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName)
-                       throws RepositoryException {
-               this.cn = cn;
-               this.workspaceName = workspaceName;
-               this.repositoryImpl = repositoryImpl;
-       }
-
-       public void init() {
-               session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
-               try {
-                       String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED };
-                       session.getWorkspace().getObservationManager().addEventListener(this,
-                                       Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true);
-                       versionManager = session.getWorkspace().getVersionManager();
-
-                       indexingThread = new IndexingThread();
-                       indexingThread.start();
-               } catch (RepositoryException e1) {
-                       throw new IllegalStateException(e1);
-               }
-       }
-
-       public void destroy() {
-               stopping.set(true);
-               indexingThread.interrupt();
-               // TODO make it configurable
-               try {
-                       indexingThread.join(10 * 60 * 1000);
-               } catch (InterruptedException e1) {
-                       log.warn("Indexing thread interrupted. Will log out session.");
-               }
-
-               try {
-                       session.getWorkspace().getObservationManager().removeEventListener(this);
-               } catch (RepositoryException e) {
-                       if (log.isTraceEnabled())
-                               log.warn("Cannot unregistered JCR event listener", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       private synchronized void processEvents(EventIterator events) {
-               long begin = System.currentTimeMillis();
-               long count = 0;
-               while (events.hasNext()) {
-                       Event event = events.nextEvent();
-                       try {
-                               toProcess.put(event);
-                       } catch (InterruptedException e) {
-                               e.printStackTrace();
-                       }
-//                     processEvent(event);
-                       count++;
-               }
-               long duration = System.currentTimeMillis() - begin;
-               if (log.isTraceEnabled())
-                       log.trace("Processed " + count + " events in " + duration + " ms");
-               notifyAll();
-       }
-
-       protected synchronized void processEvent(Event event) {
-               try {
-                       String eventPath = event.getPath();
-                       if (event.getType() == Event.NODE_ADDED) {
-                               if (!versionManager.isCheckedOut(eventPath))
-                                       return;// ignore checked-in nodes
-                               if (log.isTraceEnabled())
-                                       log.trace("NODE_ADDED " + eventPath);
-//                             session.refresh(true);
-                               session.refresh(false);
-                               Node node = session.getNode(eventPath);
-                               Node parentNode = node.getParent();
-                               if (parentNode.isNodeType(NodeType.NT_FILE)) {
-                                       if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
-                                               if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
-                                                       node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                                               Property property = node.getProperty(Property.JCR_DATA);
-                                               String etag = toEtag(property.getValue());
-                                               session.save();
-                                               node.setProperty(JCR_ETAG, etag);
-                                               if (log.isTraceEnabled())
-                                                       log.trace("ETag and last modified added to new " + node);
-                                       } else if (node.isNodeType(NodeType.NT_RESOURCE)) {
-//                                             if (!node.isNodeType(MIX_ETAG))
-//                                                     node.addMixin(MIX_ETAG);
-//                                             session.save();
-//                                             Property property = node.getProperty(Property.JCR_DATA);
-//                                             String etag = toEtag(property.getValue());
-//                                             node.setProperty(JCR_ETAG, etag);
-//                                             session.save();
-                                       }
-//                                     setLastModifiedRecursive(parentNode, event);
-//                                     session.save();
-//                                     if (log.isTraceEnabled())
-//                                             log.trace("ETag and last modified added to new " + node);
-                               }
-
-//                             if (node.isNodeType(NodeType.NT_FOLDER)) {
-//                                     setLastModifiedRecursive(node, event);
-//                                     session.save();
-//                                     if (log.isTraceEnabled())
-//                                             log.trace("Last modified added to new " + node);
-//                             }
-                       } else if (event.getType() == Event.PROPERTY_CHANGED) {
-                               String propertyName = extractItemName(eventPath);
-                               // skip if last modified properties are explicitly set
-                               if (!propertyName.equals(JCR_DATA))
-                                       return;
-//                             if (propertyName.equals(JCR_LAST_MODIFIED))
-//                                     return;
-//                             if (propertyName.equals(JCR_LAST_MODIFIED_BY))
-//                                     return;
-//                             if (propertyName.equals(JCR_MIXIN_TYPES))
-//                                     return;
-//                             if (propertyName.equals(JCR_ETAG))
-//                                     return;
-
-                               if (log.isTraceEnabled())
-                                       log.trace("PROPERTY_CHANGED " + eventPath);
-
-                               if (!session.propertyExists(eventPath))
-                                       return;
-                               session.refresh(false);
-                               Property property = session.getProperty(eventPath);
-                               Node node = property.getParent();
-                               if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA)
-                                               && node.isNodeType(NodeType.NT_UNSTRUCTURED)) {
-                                       String etag = toEtag(property.getValue());
-                                       node.setProperty(JCR_ETAG, etag);
-                                       Node parentNode = node.getParent();
-                                       if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-                                               setLastModified(parentNode, event);
-                                       }
-                                       if (log.isTraceEnabled())
-                                               log.trace("ETag and last modified updated for " + node);
-                               }
-//                             setLastModified(node, event);
-//                             session.save();
-//                             if (log.isTraceEnabled())
-//                                     log.trace("ETag and last modified updated for " + node);
-                       } else if (event.getType() == Event.NODE_REMOVED) {
-                               String removeNodePath = eventPath;
-                               String nodeName = extractItemName(eventPath);
-                               if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow
-                                       return;
-                               if (log.isTraceEnabled())
-                                       log.trace("NODE_REMOVED " + eventPath);
-//                             String parentPath = JcrUtils.parentPath(removeNodePath);
-//                             session.refresh(true);
-//                             setLastModified(parentPath, event);
-//                             session.save();
-                               if (log.isTraceEnabled())
-                                       log.trace("Last modified updated for parents of removed " + removeNodePath);
-                       }
-               } catch (Exception e) {
-                       if (log.isTraceEnabled())
-                               log.warn("Cannot process event " + event, e);
-               } finally {
-//                     try {
-//                             session.refresh(true);
-//                             if (session.hasPendingChanges())
-//                                     session.save();
-////                           session.refresh(false);
-//                     } catch (RepositoryException e) {
-//                             if (log.isTraceEnabled())
-//                                     log.warn("Cannot refresh JCR session", e);
-//                     }
-               }
-
-       }
-
-       private String extractItemName(String path) {
-               if (path == null || path.length() <= 1)
-                       return null;
-               int lastIndex = path.lastIndexOf('/');
-               if (lastIndex >= 0) {
-                       return path.substring(lastIndex + 1);
-               } else {
-                       return path;
-               }
-       }
-
-       @Override
-       public void onEvent(EventIterator events) {
-               processEvents(events);
-//             Runnable toRun = new Runnable() {
-//
-//                     @Override
-//                     public void run() {
-//                             processEvents(events);
-//                     }
-//             };
-//             Future<?> future = Activator.getInternalExecutorService().submit(toRun);
-//             try {
-//                     // make the call synchronous
-//                     future.get(60, TimeUnit.SECONDS);
-//             } catch (TimeoutException | ExecutionException | InterruptedException e) {
-//                     // silent
-//             }
-       }
-
-       static String toEtag(Value v) {
-               if (v instanceof JackrabbitValue) {
-                       JackrabbitValue value = (JackrabbitValue) v;
-                       return '\"' + value.getContentIdentity() + '\"';
-               } else {
-                       return null;
-               }
-
-       }
-
-       protected synchronized void setLastModified(Node node, Event event) throws RepositoryException {
-               GregorianCalendar calendar = new GregorianCalendar();
-               calendar.setTimeInMillis(event.getDate());
-               node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
-               node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
-               if (log.isTraceEnabled())
-                       log.trace("Last modified set on " + node);
-       }
-
-       /** Recursively set the last updated time on parents. */
-       protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException {
-               if (versionManager.isCheckedOut(node.getPath())) {
-                       if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-                               setLastModified(node, event);
-                       }
-                       if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                               if (log.isTraceEnabled())
-                                       log.trace("Last modified mix-in added to " + node);
-                       }
-
-               }
-
-               // end condition
-               if (node.getDepth() == 0) {
-//                     try {
-//                             node.getSession().save();
-//                     } catch (RepositoryException e) {
-//                             log.warn("Cannot index workspace", e);
-//                     }
-                       return;
-               } else {
-                       Node parent = node.getParent();
-                       setLastModifiedRecursive(parent, event);
-               }
-       }
-
-       /**
-        * Recursively set the last updated time on parents. Useful to use paths when
-        * dealing with deletions.
-        */
-       protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException {
-               // root node will always exist, so end condition is delegated to the other
-               // recursive setLastModified method
-               if (session.nodeExists(path)) {
-                       setLastModifiedRecursive(session.getNode(path), event);
-               } else {
-                       setLastModifiedRecursive(JcrUtils.parentPath(path), event);
-               }
-       }
-
-       @Override
-       public String toString() {
-               return "Indexer for workspace " + workspaceName + " of repository " + cn;
-       }
-
-       class IndexingThread extends Thread {
-
-               public IndexingThread() {
-                       super(CmsWorkspaceIndexer.this.toString());
-                       // TODO Auto-generated constructor stub
-               }
-
-               @Override
-               public void run() {
-                       life: while (session != null && session.isLive()) {
-                               try {
-                                       Event nextEvent = toProcess.take();
-                                       processEvent(nextEvent);
-                               } catch (InterruptedException e) {
-                                       // silent
-                                       interrupted();
-                               }
-
-                               if (stopping.get() && toProcess.isEmpty()) {
-                                       break life;
-                               }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down.");
-               }
-
-       }
-
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java
deleted file mode 100644 (file)
index f2196bd..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleListener;
-import org.osgi.framework.wiring.BundleCapability;
-import org.osgi.framework.wiring.BundleWire;
-import org.osgi.framework.wiring.BundleWiring;
-
-class DataModels implements BundleListener {
-       private final static CmsLog log = CmsLog.getLog(DataModels.class);
-
-       private Map<String, DataModel> dataModels = new TreeMap<>();
-
-       public DataModels(BundleContext bc) {
-               for (Bundle bundle : bc.getBundles())
-                       processBundle(bundle, null);
-               bc.addBundleListener(this);
-       }
-
-       public List<DataModel> getNonAbstractDataModels() {
-               List<DataModel> res = new ArrayList<>();
-               for (String name : dataModels.keySet()) {
-                       DataModel dataModel = dataModels.get(name);
-                       if (!dataModel.isAbstract())
-                               res.add(dataModel);
-               }
-               // TODO reorder?
-               return res;
-       }
-
-       @Override
-       public void bundleChanged(BundleEvent event) {
-               if (event.getType() == Bundle.RESOLVED) {
-                       processBundle(event.getBundle(), null);
-               } else if (event.getType() == Bundle.UNINSTALLED) {
-                       BundleWiring wiring = event.getBundle().adapt(BundleWiring.class);
-                       List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-                       if (providedDataModels.size() == 0)
-                               return;
-                       for (BundleCapability bundleCapability : providedDataModels) {
-                               dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME));
-                       }
-               }
-
-       }
-
-       protected void processBundle(Bundle bundle, List<Bundle> scannedBundles) {
-               if (scannedBundles != null && scannedBundles.contains(bundle))
-                       throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle);
-               BundleWiring wiring = bundle.adapt(BundleWiring.class);
-               if (wiring == null) {
-                       int bundleState = bundle.getState();
-                       if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles
-                               log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " ("
-                                               + bundle.getLocation() + ") cannot be adapted to a wiring");
-                       } else {
-                               if (log.isTraceEnabled())
-                                       log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved.");
-                       }
-                       return;
-               }
-               List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-               if (providedDataModels.size() == 0)
-                       return;
-               List<BundleWire> requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
-               // process requirements first
-               for (BundleWire bundleWire : requiredDataModels) {
-                       List<Bundle> nextScannedBundles = new ArrayList<>();
-                       if (scannedBundles != null)
-                               nextScannedBundles.addAll(scannedBundles);
-                       nextScannedBundles.add(bundle);
-                       Bundle providerBundle = bundleWire.getProvider().getBundle();
-                       processBundle(providerBundle, nextScannedBundles);
-               }
-               for (BundleCapability bundleCapability : providedDataModels) {
-                       String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME);
-                       assert name != null;
-                       if (!dataModels.containsKey(name)) {
-                               DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels);
-                               dataModels.put(dataModel.getName(), dataModel);
-                       }
-               }
-       }
-
-       /** Return a negative depth if dataModel is required by ref, 0 otherwise. */
-       static int required(DataModel ref, DataModel dataModel, int depth) {
-               for (DataModel dm : ref.getRequired()) {
-                       if (dm.equals(dataModel))// found here
-                               return depth - 1;
-                       int d = required(dm, dataModel, depth - 1);
-                       if (d != 0)// found deeper
-                               return d;
-               }
-               return 0;// not found
-       }
-
-       class DataModel {
-               private final String name;
-               private final boolean abstrct;
-               // private final boolean standalone;
-               private final String cnd;
-               private final List<DataModel> required;
-
-               private DataModel(String name, BundleCapability bundleCapability, List<BundleWire> requiredDataModels) {
-                       assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace());
-                       this.name = name;
-                       Map<String, Object> attrs = bundleCapability.getAttributes();
-                       abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT));
-                       // standalone = KernelUtils.asBoolean((String)
-                       // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE));
-                       cnd = (String) attrs.get(DataModelNamespace.CND);
-                       List<DataModel> req = new ArrayList<>();
-                       for (BundleWire wire : requiredDataModels) {
-                               String requiredDataModelName = (String) wire.getCapability().getAttributes()
-                                               .get(DataModelNamespace.NAME);
-                               assert requiredDataModelName != null;
-                               DataModel requiredDataModel = dataModels.get(requiredDataModelName);
-                               if (requiredDataModel == null)
-                                       throw new IllegalStateException("No required data model " + requiredDataModelName);
-                               req.add(requiredDataModel);
-                       }
-                       required = Collections.unmodifiableList(req);
-               }
-
-               public String getName() {
-                       return name;
-               }
-
-               public boolean isAbstract() {
-                       return abstrct;
-               }
-
-               // public boolean isStandalone() {
-               // return !isAbstract();
-               // }
-
-               public String getCnd() {
-                       return cnd;
-               }
-
-               public List<DataModel> getRequired() {
-                       return required;
-               }
-
-               // @Override
-               // public int compareTo(DataModel o) {
-               // if (equals(o))
-               // return 0;
-               // int res = required(this, o, 0);
-               // if (res != 0)
-               // return res;
-               // // the other way round
-               // res = required(o, this, 0);
-               // if (res != 0)
-               // return -res;
-               // return 0;
-               // }
-
-               @Override
-               public int hashCode() {
-                       return name.hashCode();
-               }
-
-               @Override
-               public boolean equals(Object obj) {
-                       if (obj instanceof DataModel)
-                               return ((DataModel) obj).name.equals(name);
-                       return false;
-               }
-
-               @Override
-               public String toString() {
-                       return "Data model " + name;
-               }
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java
deleted file mode 100644 (file)
index 2980250..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.security.PrivilegedAction;
-import java.text.SimpleDateFormat;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.security.Privilege;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrRepositoryWrapper;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * Make sure each user has a home directory available.
- */
-class EgoRepository extends JcrRepositoryWrapper implements KernelConstants {
-
-       /** The home base path. */
-//     private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH;
-//     private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH;
-//     private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH;
-
-       private Set<String> checkedUsers = new HashSet<String>();
-
-       private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM");
-
-       private String defaultHomeWorkspace = CmsConstants.HOME_WORKSPACE;
-       private String defaultGroupsWorkspace = CmsConstants.SRV_WORKSPACE;
-//     private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE;
-       private final boolean remote;
-
-       public EgoRepository(Repository repository, boolean remote) {
-               super(repository);
-               this.remote = remote;
-               putDescriptor(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
-               if (!remote) {
-                       LoginContext lc;
-                       try {
-                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
-                               lc.login();
-                       } catch (javax.security.auth.login.LoginException e1) {
-                               throw new IllegalStateException("Cannot login as system", e1);
-                       }
-                       Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
-
-                               @Override
-                               public Void run() {
-                                       loginOrCreateWorkspace(defaultHomeWorkspace);
-                                       loginOrCreateWorkspace(defaultGroupsWorkspace);
-                                       return null;
-                               }
-
-                       });
-               }
-       }
-
-       private void loginOrCreateWorkspace(String workspace) {
-               Session adminSession = null;
-               try {
-                       adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace);
-//                     JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ);
-
-//                     initJcr(adminSession);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot init JCR home", e);
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-//     @Override
-//     public Session login(Credentials credentials, String workspaceName)
-//                     throws LoginException, NoSuchWorkspaceException, RepositoryException {
-//             if (workspaceName == null) {
-//                     return super.login(credentials, getUserHomeWorkspace());
-//             } else {
-//                     return super.login(credentials, workspaceName);
-//             }
-//     }
-
-       protected String getUserHomeWorkspace() {
-               // TODO base on JAAS Subject metadata
-               return defaultHomeWorkspace;
-       }
-
-       protected String getGroupsWorkspace() {
-               // TODO base on JAAS Subject metadata
-               return defaultGroupsWorkspace;
-       }
-
-//     protected String getGuestsWorkspace() {
-//             // TODO base on JAAS Subject metadata
-//             return defaultGuestsWorkspace;
-//     }
-
-       @Override
-       protected void processNewSession(Session session, String workspaceName) {
-               String username = session.getUserID();
-               if (username == null || username.toString().equals(""))
-                       return;
-               if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS))
-                       return;
-
-               String userHomeWorkspace = getUserHomeWorkspace();
-               if (workspaceName == null || !workspaceName.equals(userHomeWorkspace))
-                       return;
-
-               if (checkedUsers.contains(username))
-                       return;
-               Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName);
-               try {
-                       syncJcr(adminSession, username);
-                       checkedUsers.add(username);
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-       /*
-        * JCR
-        */
-       /** Session is logged out. */
-       private void initJcr(Session adminSession) {
-               try {
-//                     JcrUtils.mkdirs(adminSession, homeBasePath);
-//                     JcrUtils.mkdirs(adminSession, groupsBasePath);
-                       adminSession.save();
-
-//                     JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
-//                     JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ);
-                       adminSession.save();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot initialize home repository", e);
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-       protected synchronized void syncJcr(Session adminSession, String username) {
-               // only in the default workspace
-//             if (workspaceName != null)
-//                     return;
-               // skip system users
-               if (username.endsWith(CmsConstants.ROLES_BASEDN))
-                       return;
-
-               try {
-                       Node userHome = CmsJcrUtils.getUserHome(adminSession, username);
-                       if (userHome == null) {
-//                             String homePath = generateUserPath(username);
-                               String userId = extractUserId(username);
-//                             if (adminSession.itemExists(homePath))// duplicate user id
-//                                     userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath));
-//                             else
-//                                     userHome = JcrUtils.mkdirs(adminSession, homePath);
-                               userHome = adminSession.getRootNode().addNode(userId);
-//                             userHome.addMixin(NodeTypes.NODE_USER_HOME);
-                               userHome.addMixin(NodeType.MIX_CREATED);
-                               userHome.addMixin(NodeType.MIX_TITLE);
-                               userHome.setProperty(Property.JCR_ID, username);
-                               // TODO use display name
-                               userHome.setProperty(Property.JCR_TITLE, userId);
-//                             userHome.setProperty(NodeNames.LDAP_UID, username);
-                               adminSession.save();
-
-                               JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username);
-                               JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL);
-//                             JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER,
-//                                             Privilege.JCR_READ);
-                       }
-                       if (adminSession.hasPendingChanges())
-                               adminSession.save();
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(adminSession);
-                       throw new JcrException("Cannot sync node security model for " + username, e);
-               }
-       }
-
-       /** Generate path for a new user home */
-       private String generateUserPath(String username) {
-               LdapName dn;
-               try {
-                       dn = new LdapName(username);
-               } catch (InvalidNameException e) {
-                       throw new CmsException("Invalid name " + username, e);
-               }
-               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
-               return '/' + userId;
-//             int atIndex = userId.indexOf('@');
-//             if (atIndex < 0) {
-//                     return homeBasePath+'/' + userId;
-//             } else {
-//                     return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
-//             }
-       }
-
-       private String extractUserId(String username) {
-               LdapName dn;
-               try {
-                       dn = new LdapName(username);
-               } catch (InvalidNameException e) {
-                       throw new CmsException("Invalid name " + username, e);
-               }
-               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
-               return userId;
-//             int atIndex = userId.indexOf('@');
-//             if (atIndex < 0) {
-//                     return homeBasePath+'/' + userId;
-//             } else {
-//                     return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId;
-//             }
-       }
-
-       public void createWorkgroup(LdapName dn) {
-               String groupsWorkspace = getGroupsWorkspace();
-               Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace);
-               String cn = dn.getRdn(dn.size() - 1).getValue().toString();
-               Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn);
-               if (newWorkgroup != null) {
-                       JcrUtils.logoutQuietly(adminSession);
-                       throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn);
-               }
-               try {
-                       // TODO enhance transformation of cn to a valid node name
-                       // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_");
-                       String relPath = JcrUtils.replaceInvalidChars(cn);
-                       newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED);
-//                     newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED);
-//                     newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME);
-                       newWorkgroup.addMixin(NodeType.MIX_CREATED);
-                       newWorkgroup.addMixin(NodeType.MIX_TITLE);
-                       newWorkgroup.setProperty(Property.JCR_ID, dn.toString());
-                       newWorkgroup.setProperty(Property.JCR_TITLE, cn);
-//                     newWorkgroup.setProperty(NodeNames.LDAP_CN, cn);
-                       adminSession.save();
-                       JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL);
-                       adminSession.save();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot create workgroup", e);
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-
-       }
-
-       public boolean isRemote() {
-               return remote;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java
deleted file mode 100644 (file)
index bad9fdf..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-
-class JackrabbitLocalRepository extends LocalRepository {
-       private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class);
-       final String SECURITY_WORKSPACE = "security";
-
-       private Map<String, CmsWorkspaceIndexer> workspaceMonitors = new TreeMap<>();
-
-       public JackrabbitLocalRepository(RepositoryImpl repository, String cn) {
-               super(repository, cn);
-//             Session session = KernelUtils.openAdminSession(repository);
-//             try {
-//                     if (NodeConstants.NODE.equals(cn))
-//                             for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
-//                                     addMonitor(workspaceName);
-//                             }
-//             } catch (RepositoryException e) {
-//                     throw new IllegalStateException(e);
-//             } finally {
-//                     JcrUtils.logoutQuietly(session);
-//             }
-       }
-
-       protected RepositoryImpl getJackrabbitrepository(String workspaceName) {
-               return (RepositoryImpl) getRepository(workspaceName);
-       }
-
-       @Override
-       protected synchronized void processNewSession(Session session, String workspaceName) {
-//             String realWorkspaceName = session.getWorkspace().getName();
-//             addMonitor(realWorkspaceName);
-       }
-
-       private void addMonitor(String realWorkspaceName) {
-               if (realWorkspaceName.equals(SECURITY_WORKSPACE))
-                       return;
-               if (!CmsConstants.NODE_REPOSITORY.equals(getCn()))
-                       return;
-
-               if (!workspaceMonitors.containsKey(realWorkspaceName)) {
-                       try {
-                               CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer(
-                                               getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName);
-                               workspaceMonitors.put(realWorkspaceName, workspaceMonitor);
-                               workspaceMonitor.init();
-                               if (log.isDebugEnabled())
-                                       log.debug("Registered " + workspaceMonitor);
-                       } catch (RepositoryException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       }
-               }
-       }
-
-       public void destroy() {
-               for (String workspaceName : workspaceMonitors.keySet()) {
-                       workspaceMonitors.get(workspaceName).destroy();
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java
deleted file mode 100644 (file)
index 17625f5..0000000
+++ /dev/null
@@ -1,397 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.io.ByteArrayInputStream;
-import java.io.CharArrayReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Provider;
-import java.security.SecureRandom;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.AbstractKeyring;
-import org.argeo.cms.security.PBEKeySpecCallback;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** JCR based implementation of a keyring */
-public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
-       private final static CmsLog log = CmsLog.getLog(JcrKeyring.class);
-       /**
-        * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case
-        */
-       public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l;
-       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
-
-       private Integer iterationCountFactor = 200;
-       private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
-       private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
-       private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
-       private String cipherName = DEFAULT_CIPHER_NAME;
-
-       private final Repository repository;
-       // TODO remove thread local session ; open a session each time
-       private ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>() {
-
-               @Override
-               protected Session initialValue() {
-                       return login();
-               }
-
-       };
-
-       // FIXME is it really still needed?
-       /**
-        * When setup is called the session has not yet been saved and we don't want to
-        * save it since there maybe other data which would be inconsistent. So we keep
-        * a reference to this node which will then be used (an reset to null) when
-        * handling the PBE callback. We keep one per thread in case multiple users are
-        * accessing the same instance of a keyring.
-        */
-       // private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
-       //
-       // @Override
-       // protected Node initialValue() {
-       // return null;
-       // }
-       // };
-
-       public JcrKeyring(Repository repository) {
-               this.repository = repository;
-       }
-
-       private Session session() {
-               Session session = this.sessionThreadLocal.get();
-               if (!session.isLive()) {
-                       session = login();
-                       sessionThreadLocal.set(session);
-               }
-               return session;
-       }
-
-       private Session login() {
-               try {
-                       return repository.login(CmsConstants.HOME_WORKSPACE);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot login key ring session", e);
-               }
-       }
-
-       @Override
-       protected synchronized Boolean isSetup() {
-               Session session = null;
-               try {
-                       // if (notYetSavedKeyring.get() != null)
-                       // return true;
-                       session = session();
-                       session.refresh(true);
-                       Node userHome = CmsJcrUtils.getUserHome(session);
-                       return userHome.hasNode(ARGEO_KEYRING);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check whether keyring is setup", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       @Override
-       protected synchronized void setup(char[] password) {
-               Binary binary = null;
-               // InputStream in = null;
-               try {
-                       session().refresh(true);
-                       Node userHome = CmsJcrUtils.getUserHome(session());
-                       Node keyring;
-                       if (userHome.hasNode(ARGEO_KEYRING)) {
-                               throw new IllegalArgumentException("Keyring already set up");
-                       } else {
-                               keyring = userHome.addNode(ARGEO_KEYRING);
-                       }
-                       keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
-
-                       // deterministic salt and iteration count based on username
-                       String username = session().getUserID();
-                       byte[] salt = new byte[8];
-                       byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
-                       for (int i = 0; i < salt.length; i++) {
-                               if (i < usernameBytes.length)
-                                       salt[i] = usernameBytes[i];
-                               else
-                                       salt[i] = 0;
-                       }
-                       try (InputStream in = new ByteArrayInputStream(salt);) {
-                               binary = session().getValueFactory().createBinary(in);
-                               keyring.setProperty(ARGEO_SALT, binary);
-                       } catch (IOException e) {
-                               throw new RuntimeException("Cannot set keyring salt", e);
-                       }
-
-                       Integer iterationCount = username.length() * iterationCountFactor;
-                       keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
-
-                       // default algo
-                       // TODO check if algo and key length are available, use DES if not
-                       keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName);
-                       keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength);
-                       keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption);
-                       keyring.setProperty(ARGEO_CIPHER, cipherName);
-
-                       keyring.getSession().save();
-
-                       // encrypted password hash
-                       // IOUtils.closeQuietly(in);
-                       // JcrUtils.closeQuietly(binary);
-                       // byte[] btPass = hash(password, salt, iterationCount);
-                       // in = new ByteArrayInputStream(btPass);
-                       // binary = session().getValueFactory().createBinary(in);
-                       // keyring.setProperty(ARGEO_PASSWORD, binary);
-
-                       // notYetSavedKeyring.set(keyring);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot setup keyring", e);
-               } finally {
-                       JcrUtils.closeQuietly(binary);
-                       // IOUtils.closeQuietly(in);
-                       // JcrUtils.discardQuietly(session());
-               }
-       }
-
-       @Override
-       protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
-               Session session = null;
-               try {
-                       session = session();
-                       session.refresh(true);
-                       Node userHome = CmsJcrUtils.getUserHome(session);
-                       Node keyring;
-                       if (userHome.hasNode(ARGEO_KEYRING))
-                               keyring = userHome.getNode(ARGEO_KEYRING);
-                       // else if (notYetSavedKeyring.get() != null)
-                       // keyring = notYetSavedKeyring.get();
-                       else
-                               throw new IllegalStateException("Keyring not setup");
-
-                       pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(),
-                                       JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)),
-                                       (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
-                                       (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
-                                       keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString());
-
-                       // if (notYetSavedKeyring.get() != null)
-                       // notYetSavedKeyring.remove();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot handle key spec callback", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       /** The parent node must already exist at this path. */
-       @Override
-       protected synchronized void encrypt(String path, InputStream unencrypted) {
-               // should be called first for lazy initialization
-               SecretKey secretKey = getSecretKey(null);
-               Cipher cipher = createCipher();
-
-               // Binary binary = null;
-               // InputStream in = null;
-               try {
-                       session().refresh(true);
-                       Node node;
-                       if (!session().nodeExists(path)) {
-                               String parentPath = JcrUtils.parentPath(path);
-                               if (!session().nodeExists(parentPath))
-                                       throw new IllegalStateException("No parent node of " + path);
-                               Node parentNode = session().getNode(parentPath);
-                               node = parentNode.addNode(JcrUtils.nodeNameFromPath(path));
-                       } else {
-                               node = session().getNode(path);
-                       }
-                       encrypt(secretKey, cipher, node, unencrypted);
-                       // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
-                       // SecureRandom random = new SecureRandom();
-                       // byte[] iv = new byte[16];
-                       // random.nextBytes(iv);
-                       // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
-                       // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
-                       //
-                       // try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
-                       // binary = session().getValueFactory().createBinary(in);
-                       // node.setProperty(Property.JCR_DATA, binary);
-                       // session().save();
-                       // }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot encrypt", e);
-               } finally {
-                       try {
-                               unencrypted.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-                       // IOUtils.closeQuietly(unencrypted);
-                       // IOUtils.closeQuietly(in);
-                       // JcrUtils.closeQuietly(binary);
-                       JcrUtils.logoutQuietly(session());
-               }
-       }
-
-       protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) {
-               try {
-                       node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
-                       SecureRandom random = new SecureRandom();
-                       byte[] iv = new byte[16];
-                       random.nextBytes(iv);
-                       cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
-                       JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
-
-                       Binary binary = null;
-                       try (InputStream in = new CipherInputStream(unencrypted, cipher);) {
-                               binary = session().getValueFactory().createBinary(in);
-                               node.setProperty(Property.JCR_DATA, binary);
-                               session().save();
-                       } finally {
-                               JcrUtils.closeQuietly(binary);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot encrypt", e);
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot encrypt", e);
-               }
-       }
-
-       @Override
-       protected synchronized InputStream decrypt(String path) {
-               Binary binary = null;
-               try {
-                       session().refresh(true);
-                       if (!session().nodeExists(path)) {
-                               char[] password = ask();
-                               Reader reader = new CharArrayReader(password);
-                               return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8));
-                       } else {
-                               // should be called first for lazy initialisation
-                               SecretKey secretKey = getSecretKey(null);
-                               Cipher cipher = createCipher();
-                               Node node = session().getNode(path);
-                               return decrypt(secretKey, cipher, node);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot decrypt", e);
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot decrypt", e);
-               } finally {
-                       JcrUtils.closeQuietly(binary);
-                       JcrUtils.logoutQuietly(session());
-               }
-       }
-
-       protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node)
-                       throws RepositoryException, GeneralSecurityException {
-               if (node.hasProperty(ARGEO_IV)) {
-                       byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV));
-                       cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
-               } else {
-                       cipher.init(Cipher.DECRYPT_MODE, secretKey);
-               }
-
-               Binary binary = node.getProperty(Property.JCR_DATA).getBinary();
-               InputStream encrypted = binary.getStream();
-               return new CipherInputStream(encrypted, cipher);
-       }
-
-       protected Cipher createCipher() {
-               try {
-                       Node userHome = CmsJcrUtils.getUserHome(session());
-                       if (!userHome.hasNode(ARGEO_KEYRING))
-                               throw new IllegalArgumentException("Keyring not setup");
-                       Node keyring = userHome.getNode(ARGEO_KEYRING);
-                       String cipherName = keyring.getProperty(ARGEO_CIPHER).getString();
-                       Provider securityProvider = getSecurityProvider();
-                       Cipher cipher;
-                       if (securityProvider == null)// TODO use BC?
-                               cipher = Cipher.getInstance(cipherName);
-                       else
-                               cipher = Cipher.getInstance(cipherName, securityProvider);
-                       return cipher;
-               } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-                       throw new IllegalArgumentException("Cannot get cipher", e);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get cipher", e);
-               } finally {
-
-               }
-       }
-
-       public synchronized void changePassword(char[] oldPassword, char[] newPassword) {
-               // TODO make it XA compatible
-               SecretKey oldSecretKey = getSecretKey(oldPassword);
-               SecretKey newSecretKey = getSecretKey(newPassword);
-               Session session = session();
-               try {
-                       NodeIterator encryptedNodes = session.getWorkspace().getQueryManager()
-                                       .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes();
-                       while (encryptedNodes.hasNext()) {
-                               Node node = encryptedNodes.nextNode();
-                               InputStream in = decrypt(oldSecretKey, createCipher(), node);
-                               encrypt(newSecretKey, createCipher(), node, in);
-                               if (log.isDebugEnabled())
-                                       log.debug("Converted keyring encrypted value of " + node.getPath());
-                       }
-               } catch (GeneralSecurityException e) {
-                       throw new RuntimeException("Cannot change JCR keyring password", e);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot change JCR keyring password", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       // public synchronized void setSession(Session session) {
-       // this.session = session;
-       // }
-
-       public void setIterationCountFactor(Integer iterationCountFactor) {
-               this.iterationCountFactor = iterationCountFactor;
-       }
-
-       public void setSecretKeyLength(Long keyLength) {
-               this.secretKeyLength = keyLength;
-       }
-
-       public void setSecretKeyFactoryName(String secreteKeyFactoryName) {
-               this.secretKeyFactoryName = secreteKeyFactoryName;
-       }
-
-       public void setSecretKeyEncryption(String secreteKeyEncryption) {
-               this.secretKeyEncryption = secreteKeyEncryption;
-       }
-
-       public void setCipherName(String cipherName) {
-               this.cipherName = cipherName;
-       }
-
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java
deleted file mode 100644 (file)
index 342c1ad..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/**
- * OSGi-aware Jackrabbit repository factory which can retrieve/publish
- * {@link Repository} as OSGi services.
- */
-public class JcrRepositoryFactory implements RepositoryFactory {
-       private final CmsLog log = CmsLog.getLog(getClass());
-//     private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       // private Resource fileRepositoryConfiguration = new ClassPathResource(
-       // "/org/argeo/cms/internal/kernel/repository-localfs.xml");
-
-       protected Repository getRepositoryByAlias(String alias) {
-               BundleContext bundleContext = CmsJcrActivator.getBundleContext();
-               if (bundleContext != null) {
-                       try {
-                               Collection<ServiceReference<Repository>> srs = bundleContext.getServiceReferences(Repository.class,
-                                               "(" + CmsConstants.CN + "=" + alias + ")");
-                               if (srs.size() == 0)
-                                       throw new IllegalArgumentException("No repository with alias " + alias + " found in OSGi registry");
-                               else if (srs.size() > 1)
-                                       throw new IllegalArgumentException(
-                                                       srs.size() + " repositories with alias " + alias + " found in OSGi registry");
-                               return bundleContext.getService(srs.iterator().next());
-                       } catch (InvalidSyntaxException e) {
-                               throw new IllegalArgumentException("Cannot find repository with alias " + alias, e);
-                       }
-               } else {
-                       // TODO ability to filter static services
-                       return null;
-               }
-       }
-
-       // private void publish(String alias, Repository repository, Properties
-       // properties) {
-       // if (bundleContext != null) {
-       // // do not modify reference
-       // Hashtable<String, String> props = new Hashtable<String, String>();
-       // props.putAll(props);
-       // props.put(JCR_REPOSITORY_ALIAS, alias);
-       // bundleContext.registerService(Repository.class.getName(), repository,
-       // props);
-       // }
-       // }
-
-       @SuppressWarnings({ "rawtypes" })
-       public Repository getRepository(Map parameters) throws RepositoryException {
-               // // check if can be found by alias
-               // Repository repository = super.getRepository(parameters);
-               // if (repository != null)
-               // return repository;
-
-               // check if remote
-               Repository repository;
-               String uri = null;
-               if (parameters.containsKey(RepoConf.labeledUri.name()))
-                       uri = parameters.get(CmsConstants.LABELED_URI).toString();
-               else if (parameters.containsKey(KernelConstants.JACKRABBIT_REPOSITORY_URI))
-                       uri = parameters.get(KernelConstants.JACKRABBIT_REPOSITORY_URI).toString();
-
-               if (uri != null) {
-                       if (uri.startsWith("http")) {// http, https
-                               Object defaultWorkspace = parameters.get(RepoConf.defaultWorkspace.name());
-                               repository = createRemoteRepository(uri, defaultWorkspace != null ? defaultWorkspace.toString() : null);
-                       } else if (uri.startsWith("file"))// http, https
-                               repository = createFileRepository(uri, parameters);
-                       else if (uri.startsWith("vm")) {
-                               // log.warn("URI " + uri + " should have been managed by generic
-                               // JCR repository factory");
-                               repository = getRepositoryByAlias(getAliasFromURI(uri));
-                       } else
-                               throw new IllegalArgumentException("Unrecognized URI format " + uri);
-
-               }
-
-               else if (parameters.containsKey(CmsConstants.CN)) {
-                       // Properties properties = new Properties();
-                       // properties.putAll(parameters);
-                       String alias = parameters.get(CmsConstants.CN).toString();
-                       // publish(alias, repository, properties);
-                       // log.info("Registered JCR repository under alias '" + alias + "'
-                       // with properties " + properties);
-                       repository = getRepositoryByAlias(alias);
-               } else
-                       throw new IllegalArgumentException("Not enough information in " + parameters);
-
-               if (repository == null)
-                       throw new IllegalArgumentException("Repository not found " + parameters);
-
-               return repository;
-       }
-
-       protected Repository createRemoteRepository(String uri, String defaultWorkspace) throws RepositoryException {
-               Map<String, String> params = new HashMap<String, String>();
-               params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri);
-               if (defaultWorkspace != null)
-                       params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace);
-               Repository repository = new Jcr2davRepositoryFactory().getRepository(params);
-               if (repository == null)
-                       throw new IllegalArgumentException("Remote Davex repository " + uri + " not found");
-               log.info("Initialized remote Jackrabbit repository from uri " + uri);
-               return repository;
-       }
-
-       @SuppressWarnings({ "rawtypes" })
-       protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException {
-               throw new UnsupportedOperationException();
-               // InputStream configurationIn = null;
-               // try {
-               // Properties vars = new Properties();
-               // vars.putAll(parameters);
-               // String dirPath = uri.substring("file:".length());
-               // File homeDir = new File(dirPath);
-               // if (homeDir.exists() && !homeDir.isDirectory())
-               // throw new ArgeoJcrException("Repository home " + dirPath + " is not a
-               // directory");
-               // if (!homeDir.exists())
-               // homeDir.mkdirs();
-               // configurationIn = fileRepositoryConfiguration.getInputStream();
-               // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE,
-               // homeDir.getCanonicalPath());
-               // RepositoryConfig repositoryConfig = RepositoryConfig.create(new
-               // InputSource(configurationIn), vars);
-               //
-               // // TransientRepository repository = new
-               // // TransientRepository(repositoryConfig);
-               // final RepositoryImpl repository =
-               // RepositoryImpl.create(repositoryConfig);
-               // Session session = repository.login();
-               // // FIXME make it generic
-               // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN",
-               // "jcr:all");
-               // org.argeo.jcr.JcrUtils.logoutQuietly(session);
-               // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository
-               // " + uri) {
-               // public void run() {
-               // repository.shutdown();
-               // log.info("Destroyed repository " + uri);
-               // }
-               // });
-               // log.info("Initialized file Jackrabbit repository from uri " + uri);
-               // return repository;
-               // } catch (Exception e) {
-               // throw new ArgeoJcrException("Cannot create repository " + uri, e);
-               // } finally {
-               // IOUtils.closeQuietly(configurationIn);
-               // }
-       }
-
-       protected String getAliasFromURI(String uri) {
-               try {
-                       URI uriObj = new URI(uri);
-                       String alias = uriObj.getPath();
-                       if (alias.charAt(0) == '/')
-                               alias = alias.substring(1);
-                       if (alias.charAt(alias.length() - 1) == '/')
-                               alias = alias.substring(0, alias.length() - 1);
-                       return alias;
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Cannot interpret URI " + uri, e);
-               }
-       }
-
-       /**
-        * Called after the repository has been initialised. Does nothing by default.
-        */
-       @SuppressWarnings("rawtypes")
-       protected void postInitialization(Repository repository, Map parameters) {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java
deleted file mode 100644 (file)
index 93f29fb..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import org.argeo.api.cms.CmsConstants;
-
-/** Internal CMS constants. */
-@Deprecated
-public interface KernelConstants {
-       // Directories
-       String DIR_NODE = "node";
-       String DIR_REPOS = "repos";
-       String DIR_INDEXES = "indexes";
-       String DIR_TRANSACTIONS = "transactions";
-
-       // Files
-       String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
-       String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
-       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
-       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
-       String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
-
-       // Security
-       String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg";
-       String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg";
-
-       // Java
-       String JAAS_CONFIG_PROP = "java.security.auth.login.config";
-
-       // DEFAULTS JCR PATH
-       String DEFAULT_HOME_BASE_PATH = "/home";
-       String DEFAULT_USERS_BASE_PATH = "/users";
-       String DEFAULT_GROUPS_BASE_PATH = "/groups";
-       
-       // KERBEROS
-       String DEFAULT_KERBEROS_SERVICE = "HTTP";
-
-       // HTTP client
-       String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
-
-       // RWT / RAP
-       // String PATH_WORKBENCH = "/ui";
-       // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
-
-       String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
-       String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
-       // default Jetty server configured via JettyConfigurator
-       String DEFAULT_JETTY_SERVER = "default";
-       String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-
-       // avoid dependencies
-       String CONTEXT_NAME_PROP = "contextName";
-       String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
-       String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java
deleted file mode 100644 (file)
index edfe87a..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.PrivilegedAction;
-import java.security.URIParameter;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.argeo.cms.osgi.DataModelNamespace;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Package utilities */
-class KernelUtils implements KernelConstants {
-       final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
-       final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
-
-       static void setJaasConfiguration(URL jaasConfigurationUrl) {
-               try {
-                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
-                       javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration
-                                       .getInstance("JavaLoginConfig", uriParameter);
-                       javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration);
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e);
-               }
-       }
-
-       static Dictionary<String, ?> asDictionary(Properties props) {
-               Hashtable<String, Object> hashtable = new Hashtable<String, Object>();
-               for (Object key : props.keySet()) {
-                       hashtable.put(key.toString(), props.get(key));
-               }
-               return hashtable;
-       }
-
-       static Dictionary<String, ?> asDictionary(ClassLoader cl, String resource) {
-               Properties props = new Properties();
-               try {
-                       props.load(cl.getResourceAsStream(resource));
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e);
-               }
-               return asDictionary(props);
-       }
-
-       static File getExecutionDir(String relativePath) {
-               File executionDir = new File(getFrameworkProp("user.dir"));
-               if (relativePath == null)
-                       return executionDir;
-               try {
-                       return new File(executionDir, relativePath).getCanonicalFile();
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot get canonical file", e);
-               }
-       }
-
-       static File getOsgiInstanceDir() {
-               return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
-                               .getAbsoluteFile();
-       }
-
-       static Path getOsgiInstancePath(String relativePath) {
-               return Paths.get(getOsgiInstanceUri(relativePath));
-       }
-
-       static URI getOsgiInstanceUri(String relativePath) {
-               String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
-               if (osgiInstanceBaseUri != null)
-                       return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
-               else
-                       return Paths.get(System.getProperty("user.dir")).toUri();
-       }
-
-       static File getOsgiConfigurationFile(String relativePath) {
-               try {
-                       return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
-                                       .getCanonicalFile();
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
-               }
-       }
-
-       static String getFrameworkProp(String key, String def) {
-               BundleContext bundleContext = CmsJcrActivator.getBundleContext();
-               String value;
-               if (bundleContext != null)
-                       value = bundleContext.getProperty(key);
-               else
-                       value = System.getProperty(key);
-               if (value == null)
-                       return def;
-               return value;
-       }
-
-       static String getFrameworkProp(String key) {
-               return getFrameworkProp(key, null);
-       }
-
-       // Security
-       // static Subject anonymousLogin() {
-       // Subject subject = new Subject();
-       // LoginContext lc;
-       // try {
-       // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
-       // lc.login();
-       // return subject;
-       // } catch (LoginException e) {
-       // throw new CmsException("Cannot login as anonymous", e);
-       // }
-       // }
-
-       static void logFrameworkProperties(CmsLog log) {
-               BundleContext bc = getBundleContext();
-               for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
-                       log.debug(sysProp + "=" + bc.getProperty(sysProp.toString()));
-               }
-               // String[] keys = { Constants.FRAMEWORK_STORAGE,
-               // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
-               // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
-               // Constants.FRAMEWORK_TRUST_REPOSITORIES,
-               // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
-               // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
-               // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
-               // for (String key : keys)
-               // log.debug(key + "=" + bc.getProperty(key));
-       }
-
-       static void printSystemProperties(PrintStream out) {
-               TreeMap<String, String> display = new TreeMap<>();
-               for (Object key : System.getProperties().keySet())
-                       display.put(key.toString(), System.getProperty(key.toString()));
-               for (String key : display.keySet())
-                       out.println(key + "=" + display.get(key));
-       }
-
-       static Session openAdminSession(Repository repository) {
-               return openAdminSession(repository, null);
-       }
-
-       static Session openAdminSession(final Repository repository, final String workspaceName) {
-               LoginContext loginContext = loginAsDataAdmin();
-               return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-
-                       @Override
-                       public Session run() {
-                               try {
-                                       return repository.login(workspaceName);
-                               } catch (RepositoryException e) {
-                                       throw new IllegalStateException("Cannot open admin session", e);
-                               } finally {
-                                       try {
-                                               loginContext.logout();
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException(e);
-                                       }
-                               }
-                       }
-
-               });
-       }
-
-       static LoginContext loginAsDataAdmin() {
-               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
-               LoginContext loginContext;
-               try {
-                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
-                       loginContext.login();
-               } catch (LoginException e1) {
-                       throw new IllegalStateException("Could not login as data admin", e1);
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentCl);
-               }
-               return loginContext;
-       }
-
-       static void doAsDataAdmin(Runnable action) {
-               LoginContext loginContext = loginAsDataAdmin();
-               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               try {
-                                       action.run();
-                                       return null;
-                               } finally {
-                                       try {
-                                               loginContext.logout();
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException(e);
-                                       }
-                               }
-                       }
-
-               });
-       }
-
-       static void asyncOpen(ServiceTracker<?, ?> st) {
-               Runnable run = new Runnable() {
-
-                       @Override
-                       public void run() {
-                               st.open();
-                       }
-               };
-//             Activator.getInternalExecutorService().execute(run);
-               new Thread(run, "Open service tracker " + st).start();
-       }
-
-       static BundleContext getBundleContext() {
-               return CmsJcrActivator.getBundleContext();
-       }
-
-       static boolean asBoolean(String value) {
-               if (value == null)
-                       return false;
-               switch (value) {
-               case "true":
-                       return true;
-               case "false":
-                       return false;
-               default:
-                       throw new IllegalArgumentException(
-                                       "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value);
-               }
-       }
-
-       private static URI safeUri(String uri) {
-               if (uri == null)
-                       throw new IllegalArgumentException("URI cannot be null");
-               try {
-                       return new URI(uri);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
-               }
-       }
-
-       private KernelUtils() {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java
deleted file mode 100644 (file)
index 0bac94c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import javax.jcr.Repository;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.JcrRepositoryWrapper;
-
-class LocalRepository extends JcrRepositoryWrapper {
-       private final String cn;
-
-       public LocalRepository(Repository repository, String cn) {
-               super(repository);
-               this.cn = cn;
-               // Map<String, Object> attrs = dataModelCapability.getAttributes();
-               // cn = (String) attrs.get(DataModelNamespace.NAME);
-               putDescriptor(CmsConstants.CN, cn);
-       }
-
-       String getCn() {
-               return cn;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java
deleted file mode 100644 (file)
index 9cd1f72..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.util.Dictionary;
-
-import javax.jcr.Repository;
-
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedService;
-
-class NodeKeyRing extends JcrKeyring implements ManagedService{
-       
-       public NodeKeyRing(Repository repository) {
-               super(repository);
-       }
-
-       @Override
-       public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java
deleted file mode 100644 (file)
index e05a002..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.cms.internal.jcr.RepositoryBuilder;
-import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator;
-import org.argeo.util.LangUtils;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */
-public class RepositoryContextsFactory implements ManagedServiceFactory {
-       private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class);
-//     private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext();
-
-       private Map<String, RepositoryContext> repositories = new HashMap<String, RepositoryContext>();
-       private Map<String, Object> pidToCn = new HashMap<String, Object>();
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-               for (String pid : repositories.keySet()) {
-                       try {
-                               RepositoryContext repositoryContext = repositories.get(pid);
-                               // Must start in another thread otherwise shutdown is interrupted
-                               // TODO use an executor?
-                               new Thread(() -> {
-                                       repositoryContext.getRepository().shutdown();
-                                       if (log.isDebugEnabled())
-                                               log.debug("Shut down repository " + pid
-                                                               + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : ""));
-                               }, "Shutdown JCR repository " + pid).start();
-                       } catch (Exception e) {
-                               log.error("Error when shutting down Jackrabbit repository " + pid, e);
-                       }
-               }
-       }
-
-       @Override
-       public String getName() {
-               return "Jackrabbit repository service factory";
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
-               if (repositories.containsKey(pid))
-                       throw new IllegalArgumentException("Already a repository registered for " + pid);
-
-               if (properties == null)
-                       return;
-
-               Object cn = properties.get(CmsConstants.CN);
-               if (cn != null)
-                       for (String otherPid : pidToCn.keySet()) {
-                               Object o = pidToCn.get(otherPid);
-                               if (cn.equals(o)) {
-                                       RepositoryContext repositoryContext = repositories.remove(otherPid);
-                                       repositories.put(pid, repositoryContext);
-                                       if (log.isDebugEnabled())
-                                               log.debug("Ignoring update of Jackrabbit repository " + cn);
-                                       // FIXME perform a proper update (also of the OSGi service)
-                                       return;
-                               }
-                       }
-
-               try {
-                       Object labeledUri = properties.get(RepoConf.labeledUri.name());
-                       if (labeledUri == null) {
-                               RepositoryBuilder repositoryBuilder = new RepositoryBuilder();
-                               RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties);
-                               repositories.put(pid, repositoryContext);
-                               Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
-                               // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI,
-                               // properties.get(RepoConf.labeledUri.name()));
-                               if (cn != null) {
-                                       props.put(CmsConstants.CN, cn);
-                                       // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
-                                       pidToCn.put(pid, cn);
-                               }
-                               CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props);
-                       } else {
-                               Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name());
-                               if (defaultWorkspace == null)
-                                       defaultWorkspace = RepoConf.defaultWorkspace.getDefault();
-                               URI uri = new URI(labeledUri.toString());
-//                                     RepositoryFactory repositoryFactory = bc
-//                                                     .getService(bc.getServiceReference(RepositoryFactory.class));
-                               RepositoryFactory repositoryFactory = CmsJcrActivator.getService(RepositoryFactory.class);
-                               Map<String, String> parameters = new HashMap<String, String>();
-                               parameters.put(RepoConf.labeledUri.name(), uri.toString());
-                               parameters.put(RepoConf.defaultWorkspace.name(), defaultWorkspace.toString());
-                               Repository repository = repositoryFactory.getRepository(parameters);
-                               // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory,
-                               // uri.toString());
-                               Dictionary<String, Object> props = LangUtils.dict(Constants.SERVICE_PID, pid);
-                               props.put(RepoConf.labeledUri.name(),
-                                               new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null)
-                                                               .toString());
-                               if (cn != null) {
-                                       props.put(CmsConstants.CN, cn);
-                                       // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
-                                       pidToCn.put(pid, cn);
-                               }
-                               CmsJcrActivator.registerService(Repository.class, repository, props);
-
-                               // home
-                               if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
-                                       Dictionary<String, Object> homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
-                                       EgoRepository homeRepository = new EgoRepository(repository, true);
-                                       CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps);
-                               }
-                       }
-               } catch (RepositoryException | URISyntaxException | IOException e) {
-                       throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e);
-               }
-
-       }
-
-       @Override
-       public void deleted(String pid) {
-               RepositoryContext repositoryContext = repositories.remove(pid);
-               repositoryContext.getRepository().shutdown();
-               if (log.isDebugEnabled())
-                       log.debug("Deleted repository " + pid);
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java
deleted file mode 100644 (file)
index 5a2cd5b..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.argeo.cms.jcr.internal;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-import org.apache.jackrabbit.api.stats.RepositoryStatistics;
-import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
-import org.argeo.api.cms.CmsLog;
-
-/**
- * Background thread started by the kernel, which gather statistics and
- * monitor/control other processes.
- */
-public class StatisticsThread extends Thread {
-       private final static CmsLog log = CmsLog.getLog(StatisticsThread.class);
-
-       private RepositoryStatisticsImpl repoStats;
-
-       /** The smallest period of operation, in ms */
-       private final long PERIOD = 60 * 1000l;
-       /** One ms in ns */
-       private final static long m = 1000l * 1000l;
-       private final static long M = 1024l * 1024l;
-
-       private boolean running = true;
-
-       private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel");
-       private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node");
-
-       @SuppressWarnings("unused")
-       private long cycle = 0l;
-
-       public StatisticsThread(String name) {
-               super(name);
-       }
-
-       private void doSmallestPeriod() {
-               // Clean expired sessions
-               // FIXME re-enable it in CMS
-               //CmsSessionImpl.closeInvalidSessions();
-
-               if (kernelStatsLog.isDebugEnabled()) {
-                       StringBuilder line = new StringBuilder(64);
-                       line.append("§\t");
-                       long freeMem = Runtime.getRuntime().freeMemory() / M;
-                       long totalMem = Runtime.getRuntime().totalMemory() / M;
-                       long maxMem = Runtime.getRuntime().maxMemory() / M;
-                       double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage();
-                       // in min
-                       boolean min = true;
-                       long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60);
-                       if (uptime > 24 * 60) {
-                               min = false;
-                               uptime = uptime / 60;
-                       }
-                       line.append(uptime).append(min ? " min" : " h").append('\t');
-                       line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem)
-                                       .append('\t');
-                       kernelStatsLog.debug(line);
-               }
-
-               if (nodeStatsLog.isDebugEnabled()) {
-                       File dataDir = KernelUtils.getOsgiInstanceDir();
-                       long freeSpace = dataDir.getUsableSpace() / M;
-                       // File currentRoot = null;
-                       // for (File root : File.listRoots()) {
-                       // String rootPath = root.getAbsolutePath();
-                       // if (dataDir.getAbsolutePath().startsWith(rootPath)) {
-                       // if (currentRoot == null
-                       // || (rootPath.length() > currentRoot.getPath()
-                       // .length())) {
-                       // currentRoot = root;
-                       // }
-                       // }
-                       // }
-                       // long totalSpace = currentRoot.getTotalSpace();
-                       StringBuilder line = new StringBuilder(128);
-                       line.append("§\t").append(freeSpace).append(" MB left in " + dataDir);
-                       line.append('\n');
-                       if (repoStats != null)
-                               for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) {
-                                       long[] vals = repoStats.getTimeSeries(type).getValuePerMinute();
-                                       long val = vals[vals.length - 1];
-                                       line.append(type.name()).append('\t').append(val).append('\n');
-                               }
-                       nodeStatsLog.debug(line);
-               }
-       }
-
-       @Override
-       public void run() {
-               if (log.isTraceEnabled())
-                       log.trace("Kernel thread started.");
-               final long periodNs = PERIOD * m;
-               while (running) {
-                       long beginNs = System.nanoTime();
-                       doSmallestPeriod();
-
-                       long waitNs = periodNs - (System.nanoTime() - beginNs);
-                       if (waitNs < 0)
-                               continue;
-                       // wait
-                       try {
-                               sleep(waitNs / m, (int) (waitNs % m));
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       cycle++;
-               }
-       }
-
-       public synchronized void destroyAndJoin() {
-               running = false;
-               notifyAll();
-//             interrupt();
-//             try {
-//                     join(PERIOD * 2);
-//             } catch (InterruptedException e) {
-//                     // throw new CmsException("Kernel thread destruction was interrupted");
-//                     log.error("Kernel thread destruction was interrupted", e);
-//             }
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java
deleted file mode 100644 (file)
index 57860d8..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.argeo.cms.jcr.internal.osgi;
-
-import java.util.Dictionary;
-
-import org.argeo.cms.jcr.internal.StatisticsThread;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-public class CmsJcrActivator implements BundleActivator {
-       private static BundleContext bundleContext;
-
-//     private List<Runnable> stopHooks = new ArrayList<>();
-       private StatisticsThread kernelThread;
-
-//     private JackrabbitRepositoryContextsFactory repositoryServiceFactory;
-//     private CmsJcrDeployment jcrDeployment;
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-               bundleContext = context;
-               
-               // kernel thread
-               kernelThread = new StatisticsThread("Kernel Thread");
-               kernelThread.setContextClassLoader(getClass().getClassLoader());
-               kernelThread.start();
-
-               // JCR
-//             repositoryServiceFactory = new JackrabbitRepositoryContextsFactory();
-////           stopHooks.add(() -> repositoryServiceFactory.shutdown());
-//             registerService(ManagedServiceFactory.class, repositoryServiceFactory,
-//                             LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_REPOS_FACTORY_PID));
-
-//             JcrRepositoryFactory repositoryFactory = new JcrRepositoryFactory();
-//             registerService(RepositoryFactory.class, repositoryFactory, null);
-
-               // File System
-//             CmsJcrFsProvider cmsFsProvider = new CmsJcrFsProvider();
-//             ServiceLoader<FileSystemProvider> fspSl = ServiceLoader.load(FileSystemProvider.class);
-//             for (FileSystemProvider fsp : fspSl) {
-//                     log.debug("FileSystemProvider " + fsp);
-//                     if (fsp instanceof CmsFsProvider) {
-//                             cmsFsProvider = (CmsFsProvider) fsp;
-//                     }
-//             }
-//             for (FileSystemProvider fsp : FileSystemProvider.installedProviders()) {
-//                     log.debug("Installed FileSystemProvider " + fsp);
-//             }
-//             registerService(FileSystemProvider.class, cmsFsProvider,
-//                             LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_FS_PROVIDER_PID));
-
-//             jcrDeployment = new CmsJcrDeployment();
-//             jcrDeployment.init();
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-//             if (jcrDeployment != null)
-//                     jcrDeployment.destroy();
-
-//             if (repositoryServiceFactory != null)
-//                     repositoryServiceFactory.shutdown();
-
-               if (kernelThread != null)
-                       kernelThread.destroyAndJoin();
-
-               bundleContext = null;
-       }
-
-       @Deprecated
-       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
-               if (bundleContext != null) {
-                       bundleContext.registerService(clss, service, properties);
-               }
-
-       }
-
-       @Deprecated
-       public static BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       @Deprecated
-       public static <T> T getService(Class<T> clss) {
-               if (bundleContext != null) {
-                       return bundleContext.getService(bundleContext.getServiceReference(clss));
-               } else {
-                       return null;
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java
deleted file mode 100644 (file)
index fa3f87f..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.server.SessionProvider;
-import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
-import org.argeo.api.cms.CmsConstants;
-
-/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */
-public class CmsRemotingServlet extends JcrRemotingServlet {
-       private static final long serialVersionUID = 6459455509684213633L;
-       private Repository repository;
-       private SessionProvider sessionProvider;
-
-       public CmsRemotingServlet() {
-       }
-
-       public CmsRemotingServlet(String alias, Repository repository) {
-               this.repository = repository;
-               this.sessionProvider = new CmsSessionProvider(alias);
-       }
-
-       @Override
-       public Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository, Map<String, String> properties) {
-               this.repository = repository;
-               String alias = properties.get(CmsConstants.CN);
-               if (alias != null)
-                       sessionProvider = new CmsSessionProvider(alias);
-               else
-                       throw new IllegalArgumentException("Only aliased repositories are supported");
-       }
-
-       @Override
-       protected SessionProvider getSessionProvider() {
-               return sessionProvider;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java
deleted file mode 100644 (file)
index 4e067ee..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.io.Serializable;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.jackrabbit.server.SessionProvider;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.auth.RemoteAuthUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * Implements an open session in view patter: a new JCR session is created for
- * each request
- */
-public class CmsSessionProvider implements SessionProvider, Serializable {
-       private static final long serialVersionUID = -1358136599534938466L;
-
-       private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
-
-       private final String alias;
-
-       private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
-
-       public CmsSessionProvider(String alias) {
-               this.alias = alias;
-       }
-
-       public Session getSession(HttpServletRequest request, Repository rep, String workspace)
-                       throws javax.jcr.LoginException, ServletException, RepositoryException {
-
-               // a client is scanning parent URLs.
-//             if (workspace == null)
-//                     return null;
-
-               CmsSession cmsSession = RemoteAuthUtils.getCmsSession(new ServletHttpRequest(request));
-               // CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
-               if (log.isTraceEnabled()) {
-                       log.trace("Get JCR session from " + cmsSession);
-               }
-               if (cmsSession == null)
-                       throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
-               CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
-               Session session = cmsDataSession.getDataSession(alias, workspace, rep);
-               cmsSessions.put(session, cmsDataSession);
-               return session;
-       }
-
-       public void releaseSession(Session session) {
-//             JcrUtils.logoutQuietly(session);
-               if (cmsSessions.containsKey(session)) {
-                       CmsDataSession cmsDataSession = cmsSessions.get(session);
-                       cmsDataSession.releaseDataSession(alias, session);
-               } else {
-                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       static class CmsDataSession {
-               private CmsSession cmsSession;
-
-               private Map<String, Session> dataSessions = new HashMap<>();
-               private Set<String> dataSessionsInUse = new HashSet<>();
-               private Set<Session> additionalDataSessions = new HashSet<>();
-
-               private CmsDataSession(CmsSession cmsSession) {
-                       this.cmsSession = cmsSession;
-                       cmsSession.addOnCloseCallback((sess) -> close());
-               }
-
-               public Session newDataSession(String cn, String workspace, Repository repository) {
-                       checkValid();
-                       return login(repository, workspace);
-               }
-
-               public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
-                       checkValid();
-                       // FIXME make it more robust
-                       if (workspace == null)
-                               workspace = CmsConstants.SYS_WORKSPACE;
-                       String path = cn + '/' + workspace;
-                       if (dataSessionsInUse.contains(path)) {
-                               try {
-                                       wait(1000);
-                                       if (dataSessionsInUse.contains(path)) {
-                                               Session session = login(repository, workspace);
-                                               additionalDataSessions.add(session);
-                                               if (log.isTraceEnabled())
-                                                       log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
-                                               return session;
-                                       }
-                               } catch (InterruptedException e) {
-                                       // silent
-                               }
-                       }
-
-                       Session session = null;
-                       if (dataSessions.containsKey(path)) {
-                               session = dataSessions.get(path);
-                       } else {
-                               session = login(repository, workspace);
-                               dataSessions.put(path, session);
-                               if (log.isTraceEnabled())
-                                       log.trace("New data session " + path + " for " + cmsSession.getUserDn());
-                       }
-                       dataSessionsInUse.add(path);
-                       return session;
-               }
-
-               private Session login(Repository repository, String workspace) {
-                       try {
-                               return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
-                                       @Override
-                                       public Session run() throws Exception {
-                                               return repository.login(workspace);
-                                       }
-                               });
-                       } catch (PrivilegedActionException e) {
-                               throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
-                       }
-               }
-
-               public synchronized void releaseDataSession(String cn, Session session) {
-                       if (additionalDataSessions.contains(session)) {
-                               JcrUtils.logoutQuietly(session);
-                               additionalDataSessions.remove(session);
-                               if (log.isTraceEnabled())
-                                       log.trace("Remove additional data session " + session);
-                               return;
-                       }
-                       String path = cn + '/' + session.getWorkspace().getName();
-                       if (!dataSessionsInUse.contains(path))
-                               log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
-                       dataSessionsInUse.remove(path);
-                       Session registeredSession = dataSessions.get(path);
-                       if (session != registeredSession)
-                               log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
-                       if (log.isTraceEnabled())
-                               log.trace("Released data session " + session + " for " + path);
-                       notifyAll();
-               }
-
-               private void checkValid() {
-                       if (!cmsSession.isValid())
-                               throw new IllegalStateException(
-                                               "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
-               }
-
-               protected void close() {
-                       synchronized (this) {
-                               // TODO check data session in use ?
-                               for (String path : dataSessions.keySet())
-                                       JcrUtils.logoutQuietly(dataSessions.get(path));
-                               for (Session session : additionalDataSessions)
-                                       JcrUtils.logoutQuietly(session);
-                       }
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java
deleted file mode 100644 (file)
index 0f0858f..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
-import org.argeo.api.cms.CmsConstants;
-
-/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */
-public class CmsWebDavServlet extends SimpleWebdavServlet {
-       private static final long serialVersionUID = 7485800288686328063L;
-       private Repository repository;
-
-       public CmsWebDavServlet() {
-       }
-
-       public CmsWebDavServlet(String alias, Repository repository) {
-               this.repository = repository;
-               setSessionProvider(new CmsSessionProvider(alias));
-       }
-
-       @Override
-       public Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository, Map<String, String> properties) {
-               this.repository = repository;
-               String alias = properties.get(CmsConstants.CN);
-               if (alias != null)
-                       setSessionProvider(new CmsSessionProvider(alias));
-               else
-                       throw new IllegalArgumentException("Only aliased repositories are supported");
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java
deleted file mode 100644 (file)
index 2f60e97..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import org.argeo.cms.servlet.CmsServletContext;
-
-/** Internal subclass, so that config resources can be loaded from our bundle. */
-public class DataServletContext extends CmsServletContext {
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java
deleted file mode 100644 (file)
index 11e903d..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsLog;
-
-public class JcrHttpUtils {
-       public final static String HEADER_AUTHORIZATION = "Authorization";
-       public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
-       public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml";
-       public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml";
-
-       static boolean isBrowser(String userAgent) {
-               return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
-                               || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
-                               || userAgent.contains("opera") || userAgent.contains("browser");
-       }
-
-       public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
-               if (!log.isDebugEnabled())
-                       return;
-               for (String headerName : response.getHeaderNames()) {
-                       Object headerValue = response.getHeader(headerName);
-                       log.debug(headerName + ": " + headerValue);
-               }
-       }
-
-       public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
-               if (!log.isDebugEnabled())
-                       return;
-               for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
-                       String headerName = headerNames.nextElement();
-                       Object headerValue = request.getHeader(headerName);
-                       log.debug(headerName + ": " + headerValue);
-               }
-               log.debug(request.getRequestURI() + "\n");
-       }
-
-       public static void logRequest(CmsLog log, HttpServletRequest request) {
-               log.debug("contextPath=" + request.getContextPath());
-               log.debug("servletPath=" + request.getServletPath());
-               log.debug("requestURI=" + request.getRequestURI());
-               log.debug("queryString=" + request.getQueryString());
-               StringBuilder buf = new StringBuilder();
-               // headers
-               Enumeration<String> en = request.getHeaderNames();
-               while (en.hasMoreElements()) {
-                       String header = en.nextElement();
-                       Enumeration<String> values = request.getHeaders(header);
-                       while (values.hasMoreElements())
-                               buf.append("  " + header + ": " + values.nextElement());
-                       buf.append('\n');
-               }
-
-               // attributed
-               Enumeration<String> an = request.getAttributeNames();
-               while (an.hasMoreElements()) {
-                       String attr = an.nextElement();
-                       Object value = request.getAttribute(attr);
-                       buf.append("  " + attr + ": " + value);
-                       buf.append('\n');
-               }
-               log.debug("\n" + buf);
-       }
-
-       private JcrHttpUtils() {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java
deleted file mode 100644 (file)
index 21046f3..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import org.argeo.cms.servlet.PrivateWwwAuthServletContext;
-
-/** Internal subclass, so that config resources can be loaded from our bundle. */
-public class JcrServletContext extends PrivateWwwAuthServletContext {
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java
deleted file mode 100644 (file)
index 62cdc5f..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-package org.argeo.cms.jcr.internal.servlet;
-
-import static javax.jcr.Property.JCR_DESCRIPTION;
-import static javax.jcr.Property.JCR_LAST_MODIFIED;
-import static javax.jcr.Property.JCR_TITLE;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.PrivilegedExceptionAction;
-import java.util.Calendar;
-import java.util.Collection;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-
-public class LinkServlet extends HttpServlet {
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private static final long serialVersionUID = 3749990143146845708L;
-
-       @Override
-       protected void service(HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-               String path = request.getPathInfo();
-               String userAgent = request.getHeader("User-Agent").toLowerCase();
-               boolean isBot = false;
-               // boolean isCompatibleBrowser = false;
-               if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) {
-                       isBot = true;
-               }
-               // else if (userAgent.contains("webkit") ||
-               // userAgent.contains("gecko") || userAgent.contains("firefox")
-               // || userAgent.contains("msie") || userAgent.contains("chrome") ||
-               // userAgent.contains("chromium")
-               // || userAgent.contains("opera") || userAgent.contains("browser"))
-               // {
-               // isCompatibleBrowser = true;
-               // }
-
-               if (isBot) {
-                       // log.warn("# BOT " + request.getHeader("User-Agent"));
-                       canonicalAnswer(request, response, path);
-                       return;
-               }
-
-               // if (isCompatibleBrowser && log.isTraceEnabled())
-               // log.trace("# BWS " + request.getHeader("User-Agent"));
-               redirectTo(response, "/#" + path);
-       }
-
-       private void redirectTo(HttpServletResponse response, String location) {
-               response.setHeader("Location", location);
-               response.setStatus(HttpServletResponse.SC_FOUND);
-       }
-
-       // private boolean canonicalAnswerNeededBy(HttpServletRequest request) {
-       // String userAgent = request.getHeader("User-Agent").toLowerCase();
-       // return userAgent.startsWith("facebookexternalhit/");
-       // }
-
-       /** For bots which don't understand RWT. */
-       private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) {
-               Session session = null;
-               try {
-                       PrintWriter writer = response.getWriter();
-                       session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction<Session>() {
-
-                               @Override
-                               public Session run() throws Exception {
-                                       Collection<ServiceReference<Repository>> srs = bc.getServiceReferences(Repository.class,
-                                                       "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")");
-                                       Repository repository = bc.getService(srs.iterator().next());
-                                       return repository.login();
-                               }
-
-                       });
-                       Node node = session.getNode(path);
-                       String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName();
-                       String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null;
-                       Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate()
-                                       : null;
-                       String url = getCanonicalUrl(node, request);
-                       String imgUrl = null;
-                       // TODO support images
-//                     loop: for (NodeIterator it = node.getNodes(); it.hasNext();) {
-//                             // Takes the first found cms:image
-//                             Node child = it.nextNode();
-//                             if (child.isNodeType(CMS_IMAGE)) {
-//                                     imgUrl = getDataUrl(child, request);
-//                                     break loop;
-//                             }
-//                     }
-                       StringBuilder buf = new StringBuilder();
-                       buf.append("<html>");
-                       buf.append("<head>");
-                       writeMeta(buf, "og:title", escapeHTML(title));
-                       writeMeta(buf, "og:type", "website");
-                       buf.append("<meta name='twitter:card' content='summary' />");
-                       buf.append("<meta name='twitter:site' content='@argeo_org' />");
-                       writeMeta(buf, "og:url", url);
-                       if (desc != null)
-                               writeMeta(buf, "og:description", escapeHTML(desc));
-                       if (imgUrl != null)
-                               writeMeta(buf, "og:image", imgUrl);
-                       if (lastUpdate != null)
-                               writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime()));
-                       buf.append("</head>");
-                       buf.append("<body>");
-                       buf.append("<p><b>!! This page is meant for indexing robots, not for real people," + " visit <a href='/#")
-                                       .append(path).append("'>").append(escapeHTML(title)).append("</a> instead.</b></p>");
-                       writeCanonical(buf, node);
-                       buf.append("</body>");
-                       buf.append("</html>");
-                       writer.print(buf.toString());
-
-                       response.setHeader("Content-Type", "text/html");
-                       writer.flush();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot write canonical answer", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       /**
-        * From http://stackoverflow.com/questions/1265282/recommended-method-for-
-        * escaping-html-in-java (+ escaping '). TODO Use
-        * org.apache.commons.lang.StringEscapeUtils
-        */
-       private String escapeHTML(String s) {
-               StringBuilder out = new StringBuilder(Math.max(16, s.length()));
-               for (int i = 0; i < s.length(); i++) {
-                       char c = s.charAt(i);
-                       if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') {
-                               out.append("&#");
-                               out.append((int) c);
-                               out.append(';');
-                       } else {
-                               out.append(c);
-                       }
-               }
-               return out.toString();
-       }
-
-       private void writeMeta(StringBuilder buf, String tag, String value) {
-               buf.append("<meta property='").append(tag).append("' content='").append(value).append("'/>");
-       }
-
-       private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException {
-               buf.append("<div>");
-               if (node.hasProperty(JCR_TITLE))
-                       buf.append("<p>").append(node.getProperty(JCR_TITLE).getString()).append("</p>");
-               if (node.hasProperty(JCR_DESCRIPTION))
-                       buf.append("<p>").append(node.getProperty(JCR_DESCRIPTION).getString()).append("</p>");
-               NodeIterator children = node.getNodes();
-               while (children.hasNext()) {
-                       writeCanonical(buf, children.nextNode());
-               }
-               buf.append("</div>");
-       }
-
-       // DATA
-       private StringBuilder getServerBaseUrl(HttpServletRequest request) {
-               try {
-                       URL url = new URL(request.getRequestURL().toString());
-                       StringBuilder buf = new StringBuilder();
-                       buf.append(url.getProtocol()).append("://").append(url.getHost());
-                       if (url.getPort() != -1)
-                               buf.append(':').append(url.getPort());
-                       return buf;
-               } catch (MalformedURLException e) {
-                       throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e);
-               }
-       }
-
-       private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException {
-               try {
-                       StringBuilder buf = getServerBaseUrl(request);
-                       buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node));
-                       return new URL(buf.toString()).toString();
-               } catch (MalformedURLException e) {
-                       throw new CmsException("Cannot build data URL for " + node, e);
-               }
-       }
-
-       // public static String getDataPath(Node node) throws
-       // RepositoryException {
-       // assert node != null;
-       // String userId = node.getSession().getUserID();
-       //// if (log.isTraceEnabled())
-       //// log.trace(userId + " : " + node.getPath());
-       // StringBuilder buf = new StringBuilder();
-       // boolean isAnonymous =
-       // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
-       // if (isAnonymous)
-       // buf.append(WEBDAV_PUBLIC);
-       // else
-       // buf.append(WEBDAV_PRIVATE);
-       // Session session = node.getSession();
-       // Repository repository = session.getRepository();
-       // String cn;
-       // if (repository.isSingleValueDescriptor(NodeConstants.CN)) {
-       // cn = repository.getDescriptor(NodeConstants.CN);
-       // } else {
-       //// log.warn("No cn defined in repository, using " +
-       // NodeConstants.NODE);
-       // cn = NodeConstants.NODE;
-       // }
-       // return
-       // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath())
-       // .toString();
-       // }
-
-       private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException {
-               try {
-                       StringBuilder buf = getServerBaseUrl(request);
-                       buf.append('/').append('!').append(node.getPath());
-                       return new URL(buf.toString()).toString();
-               } catch (MalformedURLException e) {
-                       throw new CmsException("Cannot build data URL for " + node, e);
-               }
-               // return request.getRequestURL().append('!').append(node.getPath())
-               // .toString();
-       }
-
-       private Subject anonymousLogin() {
-               Subject subject = new Subject();
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject);
-                       lc.login();
-                       return subject;
-               } catch (LoginException e) {
-                       throw new CmsException("Cannot login as anonymous", e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml
deleted file mode 100644 (file)
index 59f22cd..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<config>
-       <protecteditemremovehandler>
-               <class name="org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler" />
-       </protecteditemremovehandler>
-</config>
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml
deleted file mode 100644 (file)
index 4363898..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-  -->
-<!--
-<!DOCTYPE config [
-        <!ELEMENT config (iomanager , propertymanager, (collection | noncollection)? , filter?, mimetypeproperties?) >
-
-        <!ELEMENT iomanager (class, iohandler*) >
-        <!ELEMENT iohandler (class) >
-
-        <!ELEMENT propertymanager (class, propertyhandler*) >
-        <!ELEMENT propertyhandler (class) >
-
-        <!ELEMENT collection (nodetypes) >
-        <!ELEMENT noncollection (nodetypes) >
-
-        <!ELEMENT filter (class, namespaces?, nodetypes?) >
-
-        <!ELEMENT class >
-        <!ATTLIST class
-            name  CDATA #REQUIRED
-        >
-        <!ELEMENT namespaces (prefix | uri)* >
-        <!ELEMENT prefix (CDATA) >
-        <!ELEMENT uri (CDATA) >
-
-        <!ELEMENT nodetypes (nodetype)* >
-        <!ELEMENT nodetype (CDATA) >
-
-        <!ELEMENT mimetypeproperties (mimemapping*, defaultmimetype) >
-
-        <!ELEMENT mimemapping >
-        <!ATTLIST mimemapping
-            extension  CDATA #REQUIRED
-            mimetype  CDATA #REQUIRED
-        >
-
-        <!ELEMENT defaultmimetype (CDATA) >
-]>
--->
-
-<config>
-    <!--
-     Defines the IOManager implementation that is responsible for passing
-     import/export request to the individual IO-handlers.
-    -->
-    <iomanager>
-        <!-- class element defines the manager to be used. The specified class
-             must implement the IOManager interface.
-             Note, that the handlers are being added and called in the order
-             they appear in the configuration.
-        -->
-        <class name="org.apache.jackrabbit.server.io.IOManagerImpl" />
-        <iohandler>
-            <class name="org.apache.jackrabbit.server.io.VersionHandler" />
-        </iohandler>
-        <iohandler>
-            <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
-        </iohandler>
-<!--         <iohandler> -->
-<!--             <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!--         </iohandler> -->
-<!--         <iohandler> -->
-<!--             <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!--         </iohandler> -->
-        <iohandler>
-            <class name="org.apache.jackrabbit.server.io.DirListingExportHandler" />
-        </iohandler>
-        <iohandler>
-            <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
-        </iohandler>
-    </iomanager>
-    <!--
-     Example config for iomanager that populates its list of handlers with
-     default values. Therefore the 'iohandler' elements are omited.
-    -->
-    <!--
-    <iomanager>
-        <class name="org.apache.jackrabbit.server.io.DefaultIOManager" />
-    </iomanager>
-    -->
-    <!--
-     Defines the PropertyManager implementation that is responsible for export
-     and import of resource properties.
-    -->
-    <propertymanager>
-        <!-- class element defines the manager to be used. The specified class
-             must implement the PropertyManager interface.
-             Note, that the handlers are being added and called in the order
-             they appear in the configuration.
-        -->
-        <class name="org.apache.jackrabbit.server.io.PropertyManagerImpl" />
-        <propertyhandler>
-            <class name="org.apache.jackrabbit.server.io.VersionHandler" />
-        </propertyhandler>
-        <propertyhandler>
-            <class name="org.apache.jackrabbit.server.io.VersionHistoryHandler" />
-        </propertyhandler>
-<!--         <propertyhandler> -->
-<!--             <class name="org.apache.jackrabbit.server.io.ZipHandler" /> -->
-<!--         </propertyhandler> -->
-<!--         <propertyhandler> -->
-<!--             <class name="org.apache.jackrabbit.server.io.XmlHandler" /> -->
-<!--         </propertyhandler> -->
-        <propertyhandler>
-            <class name="org.apache.jackrabbit.server.io.DefaultHandler" />
-        </propertyhandler>
-    </propertymanager>
-    <!--
-     Define nodetypes, that should never by displayed as 'collection'
-    -->
-    <noncollection>
-        <nodetypes>
-            <nodetype>nt:file</nodetype>
-            <nodetype>nt:resource</nodetype>
-        </nodetypes>
-    </noncollection>
-    <!--
-     Example: Defines nodetypes, that should always be displayed as 'collection'.
-    -->
-    <!--
-    <collection>
-        <nodetypes>
-            <nodetype>nt:folder</nodetype>
-            <nodetype>rep:root</nodetype>
-        </nodetypes>
-    </collection>
-    -->
-    <!--
-     Filter that allows to prevent certain items from being displayed.
-     Please note, that this has an effect on PROPFIND calls only and does not
-     provide limited access to those items matching any of the filters.
-
-     However specifying a filter may cause problems with PUT or MKCOL if the
-     resource to be created is being filtered out, thus resulting in inconsistent
-     responses (e.g. PUT followed by PROPFIND on parent).
-     -->
-    <filter>
-        <!-- class element defines the resource filter to be used. The specified class
-             must implement the ItemFilter interface -->
-        <class name="org.apache.jackrabbit.webdav.simple.DefaultItemFilter" />
-        <!--
-         Nodetype names to be used to filter child nodes.
-         A child node can be filtered if the declaring nodetype of its definition
-         is one of the nodetype names specified in the nodetypes Element.
-         E.g. defining 'rep:root' as filtered nodetype whould result in jcr:system
-         being hidden but no other child node of the root node, since those
-         are defined by the nodetype nt:unstructered.
-        -->
-        <!--
-        <nodetypes>
-            <nodetype>rep:root</nodetype>
-        </nodetypes>
-        -->
-        <!--
-         Namespace prefixes or uris. Items having a name that matches any of the
-         entries will be filtered.
-        -->
-        <namespaces>
-            <prefix>rep</prefix>
-            <prefix>jcr</prefix>
-            <!-- Argeo namespaces -->
-            <prefix>node</prefix>
-            <prefix>argeo</prefix>
-            <prefix>cms</prefix>
-            <prefix>slc</prefix>
-            <prefix>connect</prefix>
-            <prefix>activities</prefix>
-            <prefix>people</prefix>
-            <prefix>documents</prefix>
-            <prefix>tracker</prefix>
-            <!--
-            <uri>internal</uri>
-            <uri>http://www.jcp.org/jcr/1.0</uri>
-            -->
-        </namespaces>
-    </filter>
-    
-    <!--
-     Optional 'mimetypeproperties' element.
-     It defines additional or replaces existing mappings for the MimeResolver
-     instance created by the ResourceConfig.
-     The default mappings are defined in org.apache.jackrabbit.server.io.mimetypes.properties.
-     If the default mime type defined by MimeResolver is 'application/octet-stream'.
-    -->
-    <!--
-    <mimetypeproperties>
-        <mimemapping extension="rtf" mimetype="application/rtf" />
-        <mimemapping extension="ott" mimetype="application/vnd.oasis.opendocument.text-template" />
-        <defaultmimetype>text/html</defaultmimetype>
-    </mimetypeproperties>
-    -->
-</config>
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd
deleted file mode 100644 (file)
index a2306c6..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<ldap = 'http://www.argeo.org/ns/ldap'>
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd
deleted file mode 100644 (file)
index d8a26b6..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<node = 'http://www.argeo.org/ns/node'>
-
-[node:userHome]
-mixin
-- ldap:uid (STRING) m
-
-[node:groupHome]
-mixin
-- ldap:cn (STRING) m
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java
deleted file mode 100644 (file)
index ccd543f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.jcr.tabular;
-
-import java.io.OutputStream;
-
-import org.argeo.cms.tabular.TabularWriter;
-import org.argeo.util.CsvWriter;
-
-/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */
-public class CsvTabularWriter implements TabularWriter {
-       private CsvWriter csvWriter;
-
-       public CsvTabularWriter(OutputStream out) {
-               this.csvWriter = new CsvWriter(out);
-       }
-
-       public void appendRow(Object[] row) {
-               csvWriter.writeLine(row);
-       }
-
-       public void close() {
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java
deleted file mode 100644 (file)
index d1d9b58..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.argeo.cms.jcr.tabular;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.tabular.ArrayTabularRow;
-import org.argeo.cms.tabular.TabularColumn;
-import org.argeo.cms.tabular.TabularRow;
-import org.argeo.cms.tabular.TabularRowIterator;
-import org.argeo.jcr.JcrException;
-import org.argeo.util.CsvParser;
-
-/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */
-public class JcrTabularRowIterator implements TabularRowIterator {
-       private Boolean hasNext = null;
-       private Boolean parsingCompleted = false;
-
-       private Long currentRowNumber = 0l;
-
-       private List<TabularColumn> header = new ArrayList<TabularColumn>();
-
-       /** referenced so that we can close it */
-       private Binary binary;
-       private InputStream in;
-
-       private CsvParser csvParser;
-       private ArrayBlockingQueue<List<String>> textLines;
-
-       public JcrTabularRowIterator(Node tableNode) {
-               try {
-                       for (NodeIterator it = tableNode.getNodes(); it.hasNext();) {
-                               Node node = it.nextNode();
-                               if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) {
-                                       Integer type = PropertyType.valueFromName(node.getProperty(
-                                                       Property.JCR_REQUIRED_TYPE).getString());
-                                       TabularColumn tc = new TabularColumn(node.getProperty(
-                                                       Property.JCR_TITLE).getString(), type);
-                                       header.add(tc);
-                               }
-                       }
-                       Node contentNode = tableNode.getNode(Property.JCR_CONTENT);
-                       if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) {
-                               textLines = new ArrayBlockingQueue<List<String>>(1000);
-                               csvParser = new CsvParser() {
-                                       protected void processLine(Integer lineNumber,
-                                                       List<String> header, List<String> tokens) {
-                                               try {
-                                                       textLines.put(tokens);
-                                               } catch (InterruptedException e) {
-                                                       // TODO Auto-generated catch block
-                                                       e.printStackTrace();
-                                               }
-                                               // textLines.add(tokens);
-                                               if (hasNext == null) {
-                                                       hasNext = true;
-                                                       synchronized (JcrTabularRowIterator.this) {
-                                                               JcrTabularRowIterator.this.notifyAll();
-                                                       }
-                                               }
-                                       }
-                               };
-                               csvParser.setNoHeader(true);
-                               binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
-                               in = binary.getStream();
-                               Thread thread = new Thread(contentNode.getPath() + " reader") {
-                                       public void run() {
-                                               try {
-                                                       csvParser.parse(in);
-                                               } finally {
-                                                       parsingCompleted = true;
-                                                       IOUtils.closeQuietly(in);
-                                               }
-                                       }
-                               };
-                               thread.start();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot read table " + tableNode, e);
-               }
-       }
-
-       public synchronized boolean hasNext() {
-               // we don't know if there is anything available
-               // while (hasNext == null)
-               // try {
-               // wait();
-               // } catch (InterruptedException e) {
-               // // silent
-               // // FIXME better deal with interruption
-               // Thread.currentThread().interrupt();
-               // break;
-               // }
-
-               // buffer not empty
-               if (!textLines.isEmpty())
-                       return true;
-
-               // maybe the parsing is finished but the flag has not been set
-               while (!parsingCompleted && textLines.isEmpty())
-                       try {
-                               wait(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                               // FIXME better deal with interruption
-                               Thread.currentThread().interrupt();
-                               break;
-                       }
-
-               // buffer not empty
-               if (!textLines.isEmpty())
-                       return true;
-
-               // (parsingCompleted && textLines.isEmpty())
-               return false;
-
-               // if (!hasNext && textLines.isEmpty()) {
-               // if (in != null) {
-               // IOUtils.closeQuietly(in);
-               // in = null;
-               // }
-               // if (binary != null) {
-               // JcrUtils.closeQuietly(binary);
-               // binary = null;
-               // }
-               // return false;
-               // } else
-               // return true;
-       }
-
-       public synchronized TabularRow next() {
-               try {
-                       List<String> tokens = textLines.take();
-                       List<Object> objs = new ArrayList<Object>(tokens.size());
-                       for (String token : tokens) {
-                               // TODO convert to other formats using header
-                               objs.add(token);
-                       }
-                       currentRowNumber++;
-                       return new ArrayTabularRow(objs);
-               } catch (InterruptedException e) {
-                       // silent
-                       // FIXME better deal with interruption
-               }
-               return null;
-       }
-
-       public void remove() {
-               throw new UnsupportedOperationException();
-       }
-
-       public Long getCurrentRowNumber() {
-               return currentRowNumber;
-       }
-
-       public List<TabularColumn> getHeader() {
-               return header;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java
deleted file mode 100644 (file)
index cc3e0d7..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.jcr.tabular;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.tabular.TabularColumn;
-import org.argeo.cms.tabular.TabularWriter;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.CsvWriter;
-
-/** Write / reference tabular content in a JCR repository. */
-public class JcrTabularWriter implements TabularWriter {
-       private Node contentNode;
-       private ByteArrayOutputStream out;
-       private CsvWriter csvWriter;
-       
-       @SuppressWarnings("unused")
-       private final List<TabularColumn> columns;
-
-       /** Creates a table node */
-       public JcrTabularWriter(Node tableNode, List<TabularColumn> columns,
-                       String contentNodeType) {
-               try {
-                       this.columns = columns;
-                       for (TabularColumn column : columns) {
-                               String normalized = JcrUtils.replaceInvalidChars(column
-                                               .getName());
-                               Node columnNode = tableNode.addNode(normalized,
-                                               ArgeoTypes.ARGEO_COLUMN);
-                               columnNode.setProperty(Property.JCR_TITLE, column.getName());
-                               if (column.getType() != null)
-                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
-                                                       PropertyType.nameFromValue(column.getType()));
-                               else
-                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
-                                                       PropertyType.TYPENAME_STRING);
-                       }
-                       contentNode = tableNode.addNode(Property.JCR_CONTENT,
-                                       contentNodeType);
-                       if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) {
-                               contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv");
-                               contentNode.setProperty(Property.JCR_ENCODING, "UTF-8");
-                               out = new ByteArrayOutputStream();
-                               csvWriter = new CsvWriter(out);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create table node " + tableNode, e);
-               }
-       }
-
-       public void appendRow(Object[] row) {
-               csvWriter.writeLine(row);
-       }
-
-       public void close() {
-               Binary binary = null;
-               InputStream in = null;
-               try {
-                       // TODO parallelize with pipes and writing from another thread
-                       in = new ByteArrayInputStream(out.toByteArray());
-                       binary = contentNode.getSession().getValueFactory()
-                                       .createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot store data in " + contentNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java
deleted file mode 100644 (file)
index 506a6ac..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */
-package org.argeo.cms.jcr.tabular;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java
deleted file mode 100644 (file)
index 7396c87..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.jackrabbit;
-
-import java.util.Map;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-
-@Deprecated
-public class JackrabbitAdminLoginModule implements LoginModule {
-       private Subject subject;
-
-       @Override
-       public void initialize(Subject subject, CallbackHandler callbackHandler,
-                       Map<String, ?> sharedState, Map<String, ?> options) {
-               this.subject = subject;
-       }
-
-       @Override
-       public boolean login() throws LoginException {
-               // TODO check permission?
-               return true;
-       }
-
-       @Override
-       public boolean commit() throws LoginException {
-               subject.getPrincipals().add(
-                               new AdminPrincipal(SecurityConstants.ADMIN_ID));
-               return true;
-       }
-
-       @Override
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       @Override
-       public boolean logout() throws LoginException {
-               subject.getPrincipals().removeAll(
-                               subject.getPrincipals(AdminPrincipal.class));
-               return true;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java
deleted file mode 100644 (file)
index 8c267e3..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-package org.argeo.jackrabbit;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.commons.cnd.ParseException;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.apache.jackrabbit.core.fs.FileSystemException;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrCallback;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Migrate the data in a Jackrabbit repository. */
-@Deprecated
-public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
-       private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class);
-
-       private String dataModelNodePath;
-       private String targetVersion;
-       private URL migrationCnd;
-       private JcrCallback dataModification;
-
-       /**
-        * Expects an already started repository with the old data model to migrate.
-        * Expects to be run with admin rights (Repository.login() will be used).
-        * 
-        * @return true if a migration was performed and the repository needs to be
-        *         restarted and its caches cleared.
-        */
-       public Boolean migrate(Session session) {
-               long begin = System.currentTimeMillis();
-               Reader reader = null;
-               try {
-                       // check if already migrated
-                       if (!session.itemExists(dataModelNodePath)) {
-//                             log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate.");
-                               return false;
-                       }
-//                     Node dataModelNode = session.getNode(dataModelNodePath);
-//                     if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) {
-//                             String currentVersion = dataModelNode.getProperty(
-//                                             ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString();
-//                             if (compareVersions(currentVersion, targetVersion) >= 0) {
-//                                     log.info("Data model at version " + currentVersion
-//                                                     + ", no need to migrate.");
-//                                     return false;
-//                             }
-//                     }
-
-                       // apply transitional CND
-                       if (migrationCnd != null) {
-                               reader = new InputStreamReader(migrationCnd.openStream());
-                               CndImporter.registerNodeTypes(reader, session, true);
-                               session.save();
-//                             log.info("Registered migration node types from " + migrationCnd);
-                       }
-
-                       // modify data
-                       dataModification.execute(session);
-
-                       // apply changes
-                       session.save();
-
-                       long duration = System.currentTimeMillis() - begin;
-//                     log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in "
-//                                     + duration + "ms");
-                       return true;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
-                                       e);
-               } catch (ParseException | IOException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new RuntimeException(
-                                       "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-                       IOUtils.closeQuietly(reader);
-               }
-       }
-
-       protected static int compareVersions(String version1, String version2) {
-               // TODO do a proper version analysis and comparison
-               return version1.compareTo(version2);
-       }
-
-       /** To be called on a stopped repository. */
-       public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) {
-               try {
-                       String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
-                       // FIXME causes weird error in Eclipse
-                       repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
-                       if (log.isDebugEnabled())
-                               log.debug("Cleared " + customeNodeTypesPath);
-               } catch (RuntimeException e) {
-                       throw e;
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } catch (FileSystemException e) {
-                       throw new RuntimeException("Cannot clear node types cache.",e);
-               }
-
-               // File customNodeTypes = new File(home.getPath()
-               // + "/repository/nodetypes/custom_nodetypes.xml");
-               // if (customNodeTypes.exists()) {
-               // customNodeTypes.delete();
-               // if (log.isDebugEnabled())
-               // log.debug("Cleared " + customNodeTypes);
-               // } else {
-               // log.warn("File " + customNodeTypes + " not found.");
-               // }
-       }
-
-       /*
-        * FOR USE IN (SORTED) SETS
-        */
-
-       public int compareTo(JackrabbitDataModelMigration dataModelMigration) {
-               // TODO make ordering smarter
-               if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath))
-                       return compareVersions(targetVersion, dataModelMigration.targetVersion);
-               else
-                       return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath);
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof JackrabbitDataModelMigration))
-                       return false;
-               JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj;
-               return dataModelNodePath.equals(dataModelMigration.dataModelNodePath)
-                               && targetVersion.equals(dataModelMigration.targetVersion);
-       }
-
-       @Override
-       public int hashCode() {
-               return targetVersion.hashCode();
-       }
-
-       public void setDataModelNodePath(String dataModelNodePath) {
-               this.dataModelNodePath = dataModelNodePath;
-       }
-
-       public void setTargetVersion(String targetVersion) {
-               this.targetVersion = targetVersion;
-       }
-
-       public void setMigrationCnd(URL migrationCnd) {
-               this.migrationCnd = migrationCnd;
-       }
-
-       public void setDataModification(JcrCallback dataModification) {
-               this.dataModification = dataModification;
-       }
-
-       public String getDataModelNodePath() {
-               return dataModelNodePath;
-       }
-
-       public String getTargetVersion() {
-               return targetVersion;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java
deleted file mode 100644 (file)
index 77ad527..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
-import org.apache.jackrabbit.spi.RepositoryServiceFactory;
-
-/** A customised {@link RepositoryFactory} access a remote DAVEX service. */
-public class ClientDavexRepositoryFactory implements RepositoryFactory {
-       public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI;
-       public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT;
-
-       @SuppressWarnings("rawtypes")
-       @Override
-       public Repository getRepository(Map parameters) throws RepositoryException {
-               RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory();
-               return RepositoryImpl
-                               .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java
deleted file mode 100644 (file)
index 7d86af2..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.jackrabbit.client;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.jackrabbit.spi.SessionInfo;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
-
-/**
- * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
- * {@link HttpClientContext}.
- */
-public class ClientDavexRepositoryService extends RepositoryServiceImpl {
-
-       public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
-                       throws RepositoryException {
-               super(jcrServerURI, batchReadConfig);
-       }
-       
-       
-
-//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
-//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize, ConnectionOptions connectionOptions)
-//                     throws RepositoryException {
-//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, connectionOptions);
-//             // TODO Auto-generated constructor stub
-//     }
-
-
-
-//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
-//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
-//                     throws RepositoryException {
-//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
-//     }
-//
-//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
-//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
-//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
-//     }
-
-       @Override
-       protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
-               HttpClientContext result = HttpClientContext.create();
-               result.setAuthCache(new NonSerialBasicAuthCache());
-               return result;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java
deleted file mode 100644 (file)
index 2af0835..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.spi.RepositoryService;
-import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
-
-/**
- * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
- * {@link ClientDavexRepositoryService}.
- */
-public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
-       @Override
-       public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
-               // retrieve the repository uri
-               String uri;
-               if (parameters == null) {
-                       uri = System.getProperty(PARAM_REPOSITORY_URI);
-               } else {
-                       Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
-                       uri = (repoUri == null) ? null : repoUri.toString();
-               }
-               if (uri == null) {
-                       uri = DEFAULT_REPOSITORY_URI;
-               }
-
-               // load other optional configuration parameters
-               BatchReadConfig brc = null;
-               int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
-               int maximumHttpConnections = 0;
-
-               // since JCR-4120 the default workspace name is no longer set to 'default'
-               // note: if running with JCR Server < 1.5 a default workspace name must
-               // therefore be configured
-               String workspaceNameDefault = null;
-
-               if (parameters != null) {
-                       // batchRead config
-                       Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
-                       if (param != null && param instanceof BatchReadConfig) {
-                               brc = (BatchReadConfig) param;
-                       }
-
-                       // itemCache size config
-                       param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
-                       if (param != null) {
-                               try {
-                                       itemInfoCacheSize = Integer.parseInt(param.toString());
-                               } catch (NumberFormatException e) {
-                                       // ignore, use default
-                               }
-                       }
-
-                       // max connections config
-                       param = parameters.get(PARAM_MAX_CONNECTIONS);
-                       if (param != null) {
-                               try {
-                                       maximumHttpConnections = Integer.parseInt(param.toString());
-                               } catch (NumberFormatException e) {
-                                       // using default
-                               }
-                       }
-
-                       param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
-                       if (param != null) {
-                               workspaceNameDefault = param.toString();
-                       }
-               }
-
-               // FIXME adapt to changes in Jackrabbit
-//             if (maximumHttpConnections > 0) {
-//                     return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
-//                                     maximumHttpConnections);
-//             } else {
-//                     return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
-//             }
-               return null;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java
deleted file mode 100644 (file)
index 3a122f1..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-package org.argeo.jackrabbit.client;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.protocol.HttpContext;
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
-import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
-import org.apache.jackrabbit.spi.RepositoryService;
-import org.apache.jackrabbit.spi.RepositoryServiceFactory;
-import org.apache.jackrabbit.spi.SessionInfo;
-import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
-import org.apache.jackrabbit.spi2davex.BatchReadConfig;
-import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
-import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
-import org.argeo.jcr.JcrUtils;
-
-/** Minimal client to test JCR DAVEX connectivity. */
-public class JackrabbitClient {
-       final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
-       final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
-       final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
-
-       public static void main(String[] args) {
-               String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
-               String workspace = args.length < 2 ? "home" : args[1];
-
-               Repository repository = null;
-               Session session = null;
-
-               URI uri;
-               try {
-                       uri = new URI(repoUri);
-               } catch (URISyntaxException e1) {
-                       throw new IllegalArgumentException(e1);
-               }
-
-               if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
-
-                       RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
-                               @SuppressWarnings("rawtypes")
-                               public Repository getRepository(Map parameters) throws RepositoryException {
-                                       RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
-
-                                               @Override
-                                               public RepositoryService createRepositoryService(Map<?, ?> parameters)
-                                                               throws RepositoryException {
-                                                       Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
-                                                       Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
-                                                       BatchReadConfig brc = null;
-                                                       // FIXME adapt to change in Jackrabbit
-//                                                     return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
-//                                                                     ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
-//
-//                                                             @Override
-//                                                             protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
-//                                                                     HttpClientContext result = HttpClientContext.create();
-//                                                                     result.setAuthCache(new NonSerialBasicAuthCache());
-//                                                                     return result;
-//                                                             }
-//
-//                                                     };
-                                                       return null;
-                                               }
-                                       };
-                                       return RepositoryImpl.create(
-                                                       new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
-                               }
-                       };
-                       Map<String, String> params = new HashMap<String, String>();
-                       params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
-                       // FIXME make it configurable
-                       params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
-
-                       try {
-                               repository = repositoryFactory.getRepository(params);
-                               if (repository != null)
-                                       session = repository.login(workspace);
-                               else
-                                       throw new IllegalArgumentException("Repository " + repoUri + " not found");
-                       } catch (RepositoryException e) {
-                               e.printStackTrace();
-                       }
-
-               } else {
-                       Path path = Paths.get(uri.getPath());
-               }
-
-               try {
-                       Node rootNode = session.getRootNode();
-                       NodeIterator nit = rootNode.getNodes();
-                       while (nit.hasNext()) {
-                               System.out.println(nit.nextNode().getPath());
-                       }
-
-                       Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
-                       System.out.println("Created folder " + newNode.getPath());
-                       Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
-                       System.out.println("Created file " + newFile.getPath());
-                       try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
-                               System.out.println("Read " + reader.readLine());
-                       } catch (IOException e) {
-                               e.printStackTrace();
-                       }
-                       newNode.getParent().remove();
-                       System.out.println("Removed new nodes");
-               } catch (RepositoryException e) {
-                       e.printStackTrace();
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java
deleted file mode 100644 (file)
index 3fb0db9..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.jackrabbit.client;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.http.HttpHost;
-import org.apache.http.auth.AuthScheme;
-import org.apache.http.client.AuthCache;
-
-/**
- * Implementation of {@link AuthCache} which doesn't use serialization, as it is
- * not supported by GraalVM at this stage.
- */
-public class NonSerialBasicAuthCache implements AuthCache {
-       private final Map<HttpHost, AuthScheme> cache;
-
-       public NonSerialBasicAuthCache() {
-               cache = new ConcurrentHashMap<HttpHost, AuthScheme>();
-       }
-
-       @Override
-       public void put(HttpHost host, AuthScheme authScheme) {
-               cache.put(host, authScheme);
-       }
-
-       @Override
-       public AuthScheme get(HttpHost host) {
-               return cache.get(host);
-       }
-
-       @Override
-       public void remove(HttpHost host) {
-               cache.remove(host);
-       }
-
-       @Override
-       public void clear() {
-               cache.clear();
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java
deleted file mode 100644 (file)
index a2eb983..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.jackrabbit.fs;
-
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-
-public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider {
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java
deleted file mode 100644 (file)
index 1cae6e4..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystemAlreadyExistsException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFsException;
-
-/**
- * A file system provider based on a JCR repository remotely accessed via the
- * DAVEX protocol.
- */
-public class DavexFsProvider extends AbstractJackrabbitFsProvider {
-       final static String DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys";
-
-       private Map<String, JcrFileSystem> fileSystems = new HashMap<>();
-
-       @Override
-       public String getScheme() {
-               return "davex";
-       }
-
-       @Override
-       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-               if (uri.getHost() == null)
-                       throw new IllegalArgumentException("An host should be provided");
-               try {
-                       URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
-                       String repoKey = repoUri.toString();
-                       if (fileSystems.containsKey(repoKey))
-                               throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey);
-                       RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
-                       return tryGetRepo(repositoryFactory, repoUri, "home");
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Cannot open file system " + uri, e);
-               }
-       }
-
-       private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace)
-                       throws IOException {
-               Map<String, String> params = new HashMap<String, String>();
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString());
-               // TODO better integrate with OSGi or other configuration than system
-               // properties.
-               String remoteDefaultWorkspace = System.getProperty(
-                               ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE,
-                               DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace);
-               Repository repository = null;
-               Session session = null;
-               try {
-                       repository = repositoryFactory.getRepository(params);
-                       if (repository != null)
-                               session = repository.login(workspace);
-               } catch (Exception e) {
-                       // silent
-               }
-
-               if (session == null) {
-                       if (repoUri.getPath() == null || repoUri.getPath().equals("/"))
-                               return null;
-                       String repoUriStr = repoUri.toString();
-                       if (repoUriStr.endsWith("/"))
-                               repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1);
-                       String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/'));
-                       String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1);
-                       URI nextUri;
-                       try {
-                               nextUri = new URI(nextRepoUriStr);
-                       } catch (URISyntaxException e) {
-                               throw new IllegalArgumentException("Badly formatted URI", e);
-                       }
-                       return tryGetRepo(repositoryFactory, nextUri, nextWorkspace);
-               } else {
-                       JcrFileSystem fileSystem = new JcrFileSystem(this, repository);
-                       fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem);
-                       return fileSystem;
-               }
-       }
-
-       @Override
-       public FileSystem getFileSystem(URI uri) {
-               return currentUserFileSystem(uri);
-       }
-
-       @Override
-       public Path getPath(URI uri) {
-               JcrFileSystem fileSystem = currentUserFileSystem(uri);
-               if (fileSystem == null)
-                       try {
-                               fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap<String, Object>());
-                               if (fileSystem == null)
-                                       throw new IllegalArgumentException("No file system found for " + uri);
-                       } catch (IOException e) {
-                               throw new JcrFsException("Could not autocreate file system", e);
-                       }
-               URI repoUri = null;
-               try {
-                       repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               String uriStr = repoUri.toString();
-               String localPath = null;
-               for (String key : fileSystems.keySet()) {
-                       if (uriStr.startsWith(key)) {
-                               localPath = uriStr.toString().substring(key.length());
-                       }
-               }
-               if ("".equals(localPath))
-                       localPath = "/";
-               return fileSystem.getPath(localPath);
-       }
-
-       private JcrFileSystem currentUserFileSystem(URI uri) {
-               for (String key : fileSystems.keySet()) {
-                       if (uri.toString().startsWith(key))
-                               return fileSystems.get(key);
-               }
-               return null;
-       }
-
-       public static void main(String args[]) {
-               try {
-                       DavexFsProvider fsProvider = new DavexFsProvider();
-                       Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/"));
-                       System.out.println(path);
-                       DirectoryStream<Path> ds = Files.newDirectoryStream(path);
-                       for (Path p : ds) {
-                               System.out.println("- " + p);
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java
deleted file mode 100644 (file)
index e3a70d0..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.jackrabbit.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFsException;
-
-public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider {
-       private RepositoryImpl repository;
-       private JcrFileSystem fileSystem;
-
-       private Credentials credentials;
-
-       public JackrabbitMemoryFsProvider() {
-               String username = System.getProperty("user.name");
-               credentials = new SimpleCredentials(username, username.toCharArray());
-       }
-
-       @Override
-       public String getScheme() {
-               return "jcr+memory";
-       }
-
-       @Override
-       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-               try {
-                       Path tempDir = Files.createTempDirectory("fs-memory");
-                       URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml");
-                       RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString());
-                       repository = RepositoryImpl.create(repositoryConfig);
-                       postRepositoryCreation(repository);
-                       fileSystem = new JcrFileSystem(this, repository, credentials);
-                       return fileSystem;
-               } catch (RepositoryException | URISyntaxException e) {
-                       throw new IOException("Cannot login to repository", e);
-               }
-       }
-
-       @Override
-       public FileSystem getFileSystem(URI uri) {
-               return fileSystem;
-       }
-
-       @Override
-       public Path getPath(URI uri) {
-               String path = uri.getPath();
-               if (fileSystem == null)
-                       try {
-                               newFileSystem(uri, new HashMap<String, Object>());
-                       } catch (IOException e) {
-                               throw new JcrFsException("Could not autocreate file system", e);
-                       }
-               return fileSystem.getPath(path);
-       }
-
-       public Repository getRepository() {
-               return repository;
-       }
-
-       public Session login() throws RepositoryException {
-               return getRepository().login(credentials);
-       }
-
-       /**
-        * Called after the repository has been created and before the file system is
-        * created.
-        */
-       protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml
deleted file mode 100644 (file)
index f2541fb..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem
-               class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="main" configRootPath="/workspaces" />
-       <Workspace name="${wsp.name}">
-               <FileSystem
-                       class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-               </PersistenceManager>
-               <SearchIndex
-                       class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="directoryManagerClass"
-                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-                       <param name="extractorPoolSize" value="0" />
-                       <FileSystem
-                               class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem
-                       class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex
-               class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="directoryManagerClass"
-                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-               <param name="extractorPoolSize" value="0" />
-               <FileSystem
-                       class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <LoginModule
-                       class="org.apache.jackrabbit.core.security.SimpleLoginModule" />
-               <!-- <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager" -->
-               <!-- workspaceName="security" /> -->
-               <!-- <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" 
-                       /> -->
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java
deleted file mode 100644 (file)
index c9ec2c3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Java NIO file system implementation based on Jackrabbit. */
-package org.argeo.jackrabbit.fs;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java
deleted file mode 100644 (file)
index 17497d6..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic Jackrabbit utilities. */
-package org.argeo.jackrabbit;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml
deleted file mode 100644 (file)
index 0526762..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.h2.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="h2" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="default" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml
deleted file mode 100644 (file)
index 3d24708..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-               <param name="path" value="${rep.home}/repository" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-                       <param name="path" value="${wsp.home}" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-                       <param name="path" value="${rep.home}/version" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml
deleted file mode 100644 (file)
index ecee5bd..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" configRootPath="/workspaces" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="directoryManagerClass"
-                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="directoryManagerClass"
-                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml
deleted file mode 100644 (file)
index 07a0d04..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
-               <param name="path" value="${rep.home}/datastore" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml
deleted file mode 100644 (file)
index 9677828..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "Jackrabbit 2.6" "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.postgresql.Driver" />
-                       <param name="url" value="${dburl}" />
-                       <param name="user" value="${dbuser}" />
-                       <param name="password" value="${dbpassword}" />
-                       <param name="databaseType" value="postgresql" />
-                       <param name="maxPoolSize" value="${maxPoolSize}" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="postgresql" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="${defaultWorkspace}" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-                       <param name="extractorPoolSize" value="${extractorPoolSize}" />
-                       <param name="cacheSize" value="${searchCacheSize}" />
-                       <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-               </SearchIndex>
-               <WorkspaceSecurity>
-                       <AccessControlProvider
-                               class="org.argeo.security.jackrabbit.ArgeoAccessControlProvider" />
-               </WorkspaceSecurity>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="postgresql" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.PostgreSQLPersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-                       <param name="bundleCacheSize" value="${bundleCacheMB}" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/index" />
-               <param name="extractorPoolSize" value="${extractorPoolSize}" />
-               <param name="cacheSize" value="${searchCacheSize}" />
-               <param name="maxVolatileIndexSize" value="${maxVolatileIndexSize}" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
-                       workspaceName="security" />
-               <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager" />
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java
deleted file mode 100644 (file)
index f98cf99..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.argeo.jackrabbit.security;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrUtils;
-
-/** Utilities around Jackrabbit security extensions. */
-public class JackrabbitSecurityUtils {
-       private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class);
-
-       /**
-        * Convenience method for denying a single privilege to a principal (user or
-        * role), typically jcr:all
-        */
-       public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege)
-                       throws RepositoryException {
-               List<Privilege> privileges = new ArrayList<Privilege>();
-               privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
-               denyPrivileges(session, path, () -> principal, privileges);
-       }
-
-       /**
-        * Deny privileges on a path to a {@link Principal}. The path must already
-        * exist. Session is saved. Synchronized to prevent concurrent modifications of
-        * the same node.
-        */
-       public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal,
-                       List<Privilege> privs) throws RepositoryException {
-               // make sure the session is in line with the persisted state
-               session.refresh(false);
-               JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager();
-               JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path);
-
-//             accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-//                     Principal currentPrincipal = ace.getPrincipal();
-//                     if (currentPrincipal.getName().equals(principal.getName())) {
-//                             Privilege[] currentPrivileges = ace.getPrivileges();
-//                             if (currentPrivileges.length != privs.size())
-//                                     break accessControlEntries;
-//                             for (int i = 0; i < currentPrivileges.length; i++) {
-//                                     Privilege currP = currentPrivileges[i];
-//                                     Privilege p = privs.get(i);
-//                                     if (!currP.getName().equals(p.getName())) {
-//                                             break accessControlEntries;
-//                                     }
-//                             }
-//                             return false;
-//                     }
-//             }
-
-               Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
-               acl.addEntry(principal, privileges, false);
-               acm.setPolicy(path, acl);
-               if (log.isDebugEnabled()) {
-                       StringBuffer privBuf = new StringBuffer();
-                       for (Privilege priv : privs)
-                               privBuf.append(priv.getName());
-                       log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
-                                       + session.getWorkspace().getName() + "'");
-               }
-               session.refresh(true);
-               session.save();
-               return true;
-       }
-
-       /** Singleton. */
-       private JackrabbitSecurityUtils() {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java
deleted file mode 100644 (file)
index f3a282c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic Jackrabbit security utilities. */
-package org.argeo.jackrabbit.security;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java
deleted file mode 100644 (file)
index 0418810..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-
-/**
- * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use
- * in try/catch blocks.
- */
-public class Bin implements Binary, AutoCloseable {
-       private final Binary wrappedBinary;
-
-       public Bin(Property property) throws RepositoryException {
-               this(property.getBinary());
-       }
-
-       public Bin(Binary wrappedBinary) {
-               if (wrappedBinary == null)
-                       throw new IllegalArgumentException("Wrapped binary cannot be null");
-               this.wrappedBinary = wrappedBinary;
-       }
-
-       // private static Binary getBinary(Property property) throws IOException {
-       // try {
-       // return property.getBinary();
-       // } catch (RepositoryException e) {
-       // throw new IOException("Cannot get binary from property " + property, e);
-       // }
-       // }
-
-       @Override
-       public void close() {
-               dispose();
-       }
-
-       @Override
-       public InputStream getStream() throws RepositoryException {
-               return wrappedBinary.getStream();
-       }
-
-       @Override
-       public int read(byte[] b, long position) throws IOException, RepositoryException {
-               return wrappedBinary.read(b, position);
-       }
-
-       @Override
-       public long getSize() throws RepositoryException {
-               return wrappedBinary.getSize();
-       }
-
-       @Override
-       public void dispose() {
-               wrappedBinary.dispose();
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java
deleted file mode 100644 (file)
index b4124ee..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.jcr;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
-public class CollectionNodeIterator implements NodeIterator {
-       private final Long collectionSize;
-       private final Iterator<Node> iterator;
-       private Integer position = 0;
-
-       public CollectionNodeIterator(Collection<Node> nodes) {
-               super();
-               this.collectionSize = (long) nodes.size();
-               this.iterator = nodes.iterator();
-       }
-
-       public void skip(long skipNum) {
-               if (skipNum < 0)
-                       throw new IllegalArgumentException(
-                                       "Skip count has to be positive: " + skipNum);
-
-               for (long i = 0; i < skipNum; i++) {
-                       if (!hasNext())
-                               throw new NoSuchElementException("Last element past (position="
-                                               + getPosition() + ")");
-                       nextNode();
-               }
-       }
-
-       public long getSize() {
-               return collectionSize;
-       }
-
-       public long getPosition() {
-               return position;
-       }
-
-       public boolean hasNext() {
-               return iterator.hasNext();
-       }
-
-       public Object next() {
-               return nextNode();
-       }
-
-       public void remove() {
-               iterator.remove();
-       }
-
-       public Node nextNode() {
-               Node node = iterator.next();
-               position++;
-               return node;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java
deleted file mode 100644 (file)
index d873ef6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.observation.ObservationManager;
-
-import org.argeo.api.cms.CmsLog;
-
-/** To be overridden */
-public class DefaultJcrListener implements EventListener {
-       private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class);
-       private Session session;
-       private String path = "/";
-       private Boolean deep = true;
-
-       public void start() {
-               try {
-                       addEventListener(session().getWorkspace().getObservationManager());
-                       if (log.isDebugEnabled())
-                               log.debug("Registered JCR event listener on " + path);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot register event listener", e);
-               }
-       }
-
-       public void stop() {
-               try {
-                       session().getWorkspace().getObservationManager()
-                                       .removeEventListener(this);
-                       if (log.isDebugEnabled())
-                               log.debug("Unregistered JCR event listener on " + path);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot unregister event listener", e);
-               }
-       }
-
-       /** Default is listen to all events */
-       protected Integer getEvents() {
-               return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
-                               | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
-       }
-
-       /** To be overidden */
-       public void onEvent(EventIterator events) {
-               while (events.hasNext()) {
-                       Event event = events.nextEvent();
-                       log.debug(event);
-               }
-       }
-
-       /** To be overidden */
-       protected void addEventListener(ObservationManager observationManager)
-                       throws RepositoryException {
-               observationManager.addEventListener(this, getEvents(), path, deep,
-                               null, null, false);
-       }
-
-       private Session session() {
-               return session;
-       }
-
-       public void setPath(String path) {
-               this.path = path;
-       }
-
-       public void setDeep(Boolean deep) {
-               this.deep = deep;
-       }
-
-       public void setSession(Session session) {
-               this.session = session;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java
deleted file mode 100644 (file)
index 1936f23..0000000
+++ /dev/null
@@ -1,994 +0,0 @@
-package org.argeo.jcr;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.text.MessageFormat;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-import javax.jcr.query.Row;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * Utility class whose purpose is to make using JCR less verbose by
- * systematically using unchecked exceptions and returning <code>null</code>
- * when something is not found. This is especially useful when writing user
- * interfaces (such as with SWT) where listeners and callbacks expect unchecked
- * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
- */
-public class Jcr {
-       /**
-        * The name of a node which will be serialized as XML text, as per section 7.3.1
-        * of the JCR 2.0 specifications.
-        */
-       public final static String JCR_XMLTEXT = "jcr:xmltext";
-       /**
-        * The name of a property which will be serialized as XML text, as per section
-        * 7.3.1 of the JCR 2.0 specifications.
-        */
-       public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
-       /**
-        * <code>jcr:name</code>, when used in another context than
-        * {@link Property#JCR_NAME}, typically to name a node rather than a property.
-        */
-       public final static String JCR_NAME = "jcr:name";
-       /**
-        * <code>jcr:path</code>, when used in another context than
-        * {@link Property#JCR_PATH}, typically to name a node rather than a property.
-        */
-       public final static String JCR_PATH = "jcr:path";
-       /**
-        * <code>jcr:primaryType</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_PRIMARY_TYPE}.
-        */
-       public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
-       /**
-        * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_MIXIN_TYPES}.
-        */
-       public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
-       /**
-        * <code>jcr:uuid</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_UUID}.
-        */
-       public final static String JCR_UUID = "jcr:uuid";
-       /**
-        * <code>jcr:created</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_CREATED}.
-        */
-       public final static String JCR_CREATED = "jcr:created";
-       /**
-        * <code>jcr:createdBy</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_CREATED_BY}.
-        */
-       public final static String JCR_CREATED_BY = "jcr:createdBy";
-       /**
-        * <code>jcr:lastModified</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_LAST_MODIFIED}.
-        */
-       public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
-       /**
-        * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_LAST_MODIFIED_BY}.
-        */
-       public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-
-       /**
-        * @see Node#isNodeType(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean isNodeType(Node node, String nodeTypeName) {
-               try {
-                       return node.isNodeType(nodeTypeName);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
-               }
-       }
-
-       /**
-        * @see Node#hasNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean hasNodes(Node node) {
-               try {
-                       return node.hasNodes();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get whether " + node + " has children.", e);
-               }
-       }
-
-       /**
-        * @see Node#getParent()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getParent(Node node) {
-               try {
-                       return isRoot(node) ? null : node.getParent();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get parent of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getParent()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getParentPath(Node node) {
-               return getPath(getParent(node));
-       }
-
-       /**
-        * Whether this node is the root node.
-        * 
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean isRoot(Node node) {
-               try {
-                       return node.getDepth() == 0;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get depth of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getPath()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getPath(Node node) {
-               try {
-                       return node.getPath();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get path of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getSession()
-        * @see Session#getWorkspace()
-        * @see Workspace#getName()
-        */
-       public static String getWorkspaceName(Node node) {
-               return session(node).getWorkspace().getName();
-       }
-
-       /**
-        * @see Node#getIdentifier()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getIdentifier(Node node) {
-               try {
-                       return node.getIdentifier();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get identifier of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getName()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getName(Node node) {
-               try {
-                       return node.getName();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name of " + node, e);
-               }
-       }
-
-       /**
-        * Returns the node name with its current index (useful for re-ordering).
-        * 
-        * @see Node#getName()
-        * @see Node#getIndex()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getIndexedName(Node node) {
-               try {
-                       return node.getName() + "[" + node.getIndex() + "]";
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getProperty(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Property getProperty(Node node, String property) {
-               try {
-                       if (node.hasProperty(property))
-                               return node.getProperty(property);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + property + " of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getIndex()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static int getIndex(Node node) {
-               try {
-                       return node.getIndex();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get index of " + node, e);
-               }
-       }
-
-       /**
-        * If node has mixin {@link NodeType#MIX_TITLE}, return
-        * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
-        */
-       public static String getTitle(Node node) {
-               if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
-                       return get(node, Property.JCR_TITLE);
-               else
-                       return Jcr.getName(node);
-       }
-
-       /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
-       @SuppressWarnings("unchecked")
-       public static Iterable<Node> iterate(NodeIterator nodeIterator) {
-               return new Iterable<Node>() {
-
-                       @Override
-                       public Iterator<Node> iterator() {
-                               return nodeIterator;
-                       }
-               };
-       }
-
-       /**
-        * @return the children as an {@link Iterable} for use in for-each llops.
-        * @see Node#getNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Iterable<Node> nodes(Node node) {
-               try {
-                       return iterate(node.getNodes());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get children of " + node, e);
-               }
-       }
-
-       /**
-        * @return the children as a (possibly empty) {@link List}.
-        * @see Node#getNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static List<Node> getNodes(Node node) {
-               List<Node> nodes = new ArrayList<>();
-               try {
-                       if (node.hasNodes()) {
-                               NodeIterator nit = node.getNodes();
-                               while (nit.hasNext())
-                                       nodes.add(nit.nextNode());
-                               return nodes;
-                       } else
-                               return nodes;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get children of " + node, e);
-               }
-       }
-
-       /**
-        * @return the child or <code>null</node> if not found
-        * @see Node#getNode(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNode(Node node, String child) {
-               try {
-                       if (node.hasNode(child))
-                               return node.getNode(child);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get child of " + node, e);
-               }
-       }
-
-       /**
-        * @return the node at this path or <code>null</node> if not found
-        * @see Session#getNode(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNode(Session session, String path) {
-               try {
-                       if (session.nodeExists(path))
-                               return session.getNode(path);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node " + path, e);
-               }
-       }
-
-       /**
-        * Add a node to this parent, setting its primary type and its mixins.
-        * 
-        * @param parent      the parent node
-        * @param name        the name of the node, if <code>null</code>, the primary
-        *                    type will be used (typically for XML structures)
-        * @param primaryType the primary type, if <code>null</code>
-        *                    {@link NodeType#NT_UNSTRUCTURED} will be used.
-        * @param mixins      the mixins
-        * @return the created node
-        * @see Node#addNode(String, String)
-        * @see Node#addMixin(String)
-        */
-       public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
-               if (name == null && primaryType == null)
-                       throw new IllegalArgumentException("Both node name and primary type cannot be null");
-               try {
-                       Node newNode = parent.addNode(name == null ? primaryType : name,
-                                       primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
-                       for (String mixin : mixins) {
-                               newNode.addMixin(mixin);
-                       }
-                       return newNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
-               }
-       }
-
-       /**
-        * Add an {@link NodeType#NT_BASE} node to this parent.
-        * 
-        * @param parent the parent node
-        * @param name   the name of the node, cannot be <code>null</code>
-        * @return the created node
-        * 
-        * @see Node#addNode(String)
-        */
-       public static Node addNode(Node parent, String name) {
-               if (name == null)
-                       throw new IllegalArgumentException("Node name cannot be null");
-               try {
-                       Node newNode = parent.addNode(name);
-                       return newNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
-               }
-       }
-
-       /**
-        * Add mixins to a node.
-        * 
-        * @param node   the node
-        * @param mixins the mixins
-        * @return the created node
-        * @see Node#addMixin(String)
-        */
-       public static void addMixin(Node node, String... mixins) {
-               try {
-                       for (String mixin : mixins) {
-                               node.addMixin(mixin);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
-               }
-       }
-
-       /**
-        * Removes this node.
-        * 
-        * @see Node#remove()
-        */
-       public static void remove(Node node) {
-               try {
-                       node.remove();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot remove node " + node, e);
-               }
-       }
-
-       /**
-        * @return the node with htis id or <code>null</node> if not found
-        * @see Session#getNodeByIdentifier(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNodeById(Session session, String id) {
-               try {
-                       return session.getNodeByIdentifier(id);
-               } catch (ItemNotFoundException e) {
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node with id " + id, e);
-               }
-       }
-
-       /**
-        * Set a property to the given value, or remove it if the value is
-        * <code>null</code>.
-        * 
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static void set(Node node, String property, Object value) {
-               try {
-                       if (!node.hasProperty(property)) {
-                               if (value != null) {
-                                       if (value instanceof List) {// multiple
-                                               List<?> lst = (List<?>) value;
-                                               String[] values = new String[lst.size()];
-                                               for (int i = 0; i < lst.size(); i++) {
-                                                       values[i] = lst.get(i).toString();
-                                               }
-                                               node.setProperty(property, values);
-                                       } else {
-                                               node.setProperty(property, value.toString());
-                                       }
-                               }
-                               return;
-                       }
-                       Property prop = node.getProperty(property);
-                       if (value == null) {
-                               prop.remove();
-                               return;
-                       }
-
-                       // multiple
-                       if (value instanceof List) {
-                               List<?> lst = (List<?>) value;
-                               String[] values = new String[lst.size()];
-                               // TODO better cast?
-                               for (int i = 0; i < lst.size(); i++) {
-                                       values[i] = lst.get(i).toString();
-                               }
-                               if (!prop.isMultiple())
-                                       prop.remove();
-                               node.setProperty(property, values);
-                               return;
-                       }
-
-                       // single
-                       if (prop.isMultiple()) {
-                               prop.remove();
-                               node.setProperty(property, value.toString());
-                               return;
-                       }
-
-                       if (value instanceof String)
-                               prop.setValue((String) value);
-                       else if (value instanceof Long)
-                               prop.setValue((Long) value);
-                       else if (value instanceof Integer)
-                               prop.setValue(((Integer) value).longValue());
-                       else if (value instanceof Double)
-                               prop.setValue((Double) value);
-                       else if (value instanceof Float)
-                               prop.setValue(((Float) value).doubleValue());
-                       else if (value instanceof Calendar)
-                               prop.setValue((Calendar) value);
-                       else if (value instanceof BigDecimal)
-                               prop.setValue((BigDecimal) value);
-                       else if (value instanceof Boolean)
-                               prop.setValue((Boolean) value);
-                       else if (value instanceof byte[])
-                               JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
-                       else if (value instanceof Instant) {
-                               Instant instant = (Instant) value;
-                               GregorianCalendar calendar = new GregorianCalendar();
-                               calendar.setTime(Date.from(instant));
-                               prop.setValue(calendar);
-                       } else // try with toString()
-                               prop.setValue(value.toString());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
-               }
-       }
-
-       /**
-        * Get property as {@link String}.
-        * 
-        * @return the value of
-        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
-        *         <code>null</code> if the property does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String get(Node node, String property) {
-               return get(node, property, null);
-       }
-
-       /**
-        * Get property as a {@link String}. If the property is multiple it returns the
-        * first value.
-        * 
-        * @return the value of
-        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
-        *         <code>defaultValue</code> if the property does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String get(Node node, String property, String defaultValue) {
-               try {
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               if (!p.isMultiple())
-                                       return p.getString();
-                               else {
-                                       Value[] values = p.getValues();
-                                       if (values.length == 0)
-                                               return defaultValue;
-                                       else
-                                               return values[0].getString();
-                               }
-                       } else
-                               return defaultValue;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get property as a {@link Value}.
-        * 
-        * @return {@link Node#getProperty(String)} or <code>null</code> if the property
-        *         does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Value getValue(Node node, String property) {
-               try {
-                       if (node.hasProperty(property))
-                               return node.getProperty(property).getValue();
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get property doing a best effort to cast it as the target object.
-        * 
-        * @return the value of {@link Node#getProperty(String)} or
-        *         <code>defaultValue</code> if the property does not exist.
-        * @throws IllegalArgumentException if the value could not be cast
-        * @throws JcrException             in case of unexpected
-        *                                  {@link RepositoryException}
-        */
-       @SuppressWarnings("unchecked")
-       public static <T> T getAs(Node node, String property, T defaultValue) {
-               try {
-                       // TODO deal with multiple
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               try {
-                                       if (p.isMultiple()) {
-                                               throw new UnsupportedOperationException("Multiple values properties are not supported");
-                                       }
-                                       Value value = p.getValue();
-                                       return (T) get(value);
-                               } catch (ClassCastException e) {
-                                       throw new IllegalArgumentException(
-                                                       "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
-                               }
-                       } else {
-                               return defaultValue;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-
-       public static <T> T getAs(Node node, String property, Class<T> clss) {
-               if (String.class.isAssignableFrom(clss)) {
-                       return (T) get(node, property);
-               } else if (Long.class.isAssignableFrom(clss)) {
-                       return (T) get(node, property);
-               } else {
-                       throw new IllegalArgumentException("Unsupported format " + clss);
-               }
-       }
-
-       /**
-        * Get a multiple property as a list, doing a best effort to cast it as the
-        * target list.
-        * 
-        * @return the value of {@link Node#getProperty(String)}.
-        * @throws IllegalArgumentException if the value could not be cast
-        * @throws JcrException             in case of unexpected
-        *                                  {@link RepositoryException}
-        */
-       public static <T> List<T> getMultiple(Node node, String property) {
-               try {
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               return getMultiple(p);
-                       } else {
-                               return null;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get a multiple property as a list, doing a best effort to cast it as the
-        * target list.
-        */
-       @SuppressWarnings("unchecked")
-       public static <T> List<T> getMultiple(Property p) {
-               try {
-                       List<T> res = new ArrayList<>();
-                       if (!p.isMultiple()) {
-                               res.add((T) get(p.getValue()));
-                               return res;
-                       }
-                       Value[] values = p.getValues();
-                       for (Value value : values) {
-                               res.add((T) get(value));
-                       }
-                       return res;
-               } catch (ClassCastException | RepositoryException e) {
-                       throw new IllegalArgumentException("Cannot get property " + p, e);
-               }
-       }
-
-       /** Cast a {@link Value} to a standard Java object. */
-       public static Object get(Value value) {
-               Binary binary = null;
-               try {
-                       switch (value.getType()) {
-                       case PropertyType.STRING:
-                               return value.getString();
-                       case PropertyType.DOUBLE:
-                               return (Double) value.getDouble();
-                       case PropertyType.LONG:
-                               return (Long) value.getLong();
-                       case PropertyType.BOOLEAN:
-                               return (Boolean) value.getBoolean();
-                       case PropertyType.DATE:
-                               return value.getDate();
-                       case PropertyType.BINARY:
-                               binary = value.getBinary();
-                               byte[] arr = null;
-                               try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
-                                       IOUtils.copy(in, out);
-                                       arr = out.toByteArray();
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot read binary from " + value, e);
-                               }
-                               return arr;
-                       default:
-                               return value.getString();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot cast value from " + value, e);
-               } finally {
-                       if (binary != null)
-                               binary.dispose();
-               }
-       }
-
-       /**
-        * Retrieves the {@link Session} related to this node.
-        * 
-        * @deprecated Use {@link #getSession(Node)} instead.
-        */
-       @Deprecated
-       public static Session session(Node node) {
-               return getSession(node);
-       }
-
-       /** Retrieves the {@link Session} related to this node. */
-       public static Session getSession(Node node) {
-               try {
-                       return node.getSession();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve session related to " + node, e);
-               }
-       }
-
-       /** Retrieves the root node related to this session. */
-       public static Node getRootNode(Session session) {
-               try {
-                       return session.getRootNode();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get root node for " + session, e);
-               }
-       }
-
-       /** Whether this item exists. */
-       public static boolean itemExists(Session session, String path) {
-               try {
-                       return session.itemExists(path);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check whether " + path + " exists", e);
-               }
-       }
-
-       /**
-        * Saves the {@link Session} related to this node. Note that all other unrelated
-        * modifications in this session will also be saved.
-        */
-       public static void save(Node node) {
-               try {
-                       Session session = node.getSession();
-//                     if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-//                             set(node, Property.JCR_LAST_MODIFIED, Instant.now());
-//                             set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
-//                     }
-                       if (session.hasPendingChanges())
-                               session.save();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot save session related to " + node + " in workspace "
-                                       + session(node).getWorkspace().getName(), e);
-               }
-       }
-
-       /** Login to a JCR repository. */
-       public static Session login(Repository repository, String workspace) {
-               try {
-                       return repository.login(workspace);
-               } catch (RepositoryException e) {
-                       throw new IllegalArgumentException("Cannot login to repository", e);
-               }
-       }
-
-       /** Safely and silently logs out a session. */
-       public static void logout(Session session) {
-               try {
-                       if (session != null)
-                               if (session.isLive())
-                                       session.logout();
-               } catch (Exception e) {
-                       // silent
-               }
-       }
-
-       /** Safely and silently logs out the underlying session. */
-       public static void logout(Node node) {
-               Jcr.logout(session(node));
-       }
-
-       /*
-        * SECURITY
-        */
-       /**
-        * Add a single privilege to a node.
-        * 
-        * @see Privilege
-        */
-       public static void addPrivilege(Node node, String principal, String privilege) {
-               try {
-                       Session session = node.getSession();
-                       JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
-               }
-       }
-
-       /*
-        * VERSIONING
-        */
-       /** Get checked out status. */
-       public static boolean isCheckedOut(Node node) {
-               try {
-                       return node.isCheckedOut();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve checked out status of " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkpoint(String) */
-       public static void checkpoint(Node node) {
-               try {
-                       versionManager(node).checkpoint(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check in " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkin(String) */
-       public static void checkin(Node node) {
-               try {
-                       versionManager(node).checkin(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check in " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkout(String) */
-       public static void checkout(Node node) {
-               try {
-                       versionManager(node).checkout(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check out " + node, e);
-               }
-       }
-
-       /** Get the {@link VersionManager} related to this node. */
-       public static VersionManager versionManager(Node node) {
-               try {
-                       return node.getSession().getWorkspace().getVersionManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get version manager from " + node, e);
-               }
-       }
-
-       /** Get the {@link VersionHistory} related to this node. */
-       public static VersionHistory getVersionHistory(Node node) {
-               try {
-                       return versionManager(node).getVersionHistory(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get version history from " + node, e);
-               }
-       }
-
-       /**
-        * The linear versions of this version history in reverse order and without the
-        * root version.
-        */
-       public static List<Version> getLinearVersions(VersionHistory versionHistory) {
-               try {
-                       List<Version> lst = new ArrayList<>();
-                       VersionIterator vit = versionHistory.getAllLinearVersions();
-                       while (vit.hasNext())
-                               lst.add(vit.nextVersion());
-                       lst.remove(0);
-                       Collections.reverse(lst);
-                       return lst;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get linear versions from " + versionHistory, e);
-               }
-       }
-
-       /** The frozen node related to this {@link Version}. */
-       public static Node getFrozenNode(Version version) {
-               try {
-                       return version.getFrozenNode();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get frozen node from " + version, e);
-               }
-       }
-
-       /** Get the base {@link Version} related to this node. */
-       public static Version getBaseVersion(Node node) {
-               try {
-                       return versionManager(node).getBaseVersion(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get base version from " + node, e);
-               }
-       }
-
-       /*
-        * FILES
-        */
-       /**
-        * Returns the size of this file.
-        * 
-        * @see NodeType#NT_FILE
-        */
-       public static long getFileSize(Node fileNode) {
-               try {
-                       if (!fileNode.isNodeType(NodeType.NT_FILE))
-                               throw new IllegalArgumentException(fileNode + " must be a file.");
-                       return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get file size of " + fileNode, e);
-               }
-       }
-
-       /** Returns the size of this {@link Binary}. */
-       public static long getBinarySize(Binary binaryArg) {
-               try {
-                       try (Bin binary = new Bin(binaryArg)) {
-                               return binary.getSize();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get file size of binary " + binaryArg, e);
-               }
-       }
-
-       // QUERY
-       /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
-       public static Query createQuery(QueryManager qm, String sql, Object... args) {
-               // fix single quotes
-               sql = sql.replaceAll("'", "''");
-               String query = MessageFormat.format(sql, args);
-               try {
-                       return qm.createQuery(query, Query.JCR_SQL2);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
-               }
-       }
-
-       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
-       public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
-               Query query = createQuery(qm, sql, args);
-               try {
-                       return query.execute().getNodes();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
-               }
-       }
-
-       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
-       public static NodeIterator executeQuery(Session session, String sql, Object... args) {
-               QueryManager queryManager;
-               try {
-                       queryManager = session.getWorkspace().getQueryManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get query manager from session " + session, e);
-               }
-               return executeQuery(queryManager, sql, args);
-       }
-
-       /**
-        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
-        * single node at most.
-        * 
-        * @return the node or <code>null</code> if not found.
-        */
-       public static Node getNode(QueryManager qm, String sql, Object... args) {
-               NodeIterator nit = executeQuery(qm, sql, args);
-               if (nit.hasNext()) {
-                       Node node = nit.nextNode();
-                       if (nit.hasNext())
-                               throw new IllegalStateException(
-                                               "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
-                       return node;
-               } else {
-                       return null;
-               }
-       }
-
-       /**
-        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
-        * single node at most.
-        * 
-        * @return the node or <code>null</code> if not found.
-        */
-       public static Node getNode(Session session, String sql, Object... args) {
-               QueryManager queryManager;
-               try {
-                       queryManager = session.getWorkspace().getQueryManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get query manager from session " + session, e);
-               }
-               return getNode(queryManager, sql, args);
-       }
-
-       public static Node getRowNode(Row row, String selectorName) {
-               try {
-                       return row.getNode(selectorName);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node " + selectorName + " from row", e);
-               }
-       }
-
-       /** Singleton. */
-       private Jcr() {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java
deleted file mode 100644 (file)
index 351929f..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-package org.argeo.jcr;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.Privilege;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Apply authorizations to a JCR repository. */
-public class JcrAuthorizations implements Runnable {
-       // private final static Log log =
-       // LogFactory.getLog(JcrAuthorizations.class);
-
-       private Repository repository;
-       private String workspace = null;
-
-       private String securityWorkspace = "security";
-
-       /**
-        * key := privilege1,privilege2/path/to/node<br/>
-        * value := group1,group2,user1
-        */
-       private Map<String, String> principalPrivileges = new HashMap<String, String>();
-
-       public void run() {
-               String currentWorkspace = workspace;
-               Session session = null;
-               try {
-                       if (workspace != null && workspace.equals("*")) {
-                               session = repository.login();
-                               String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames();
-                               JcrUtils.logoutQuietly(session);
-                               for (String wksp : workspaces) {
-                                       currentWorkspace = wksp;
-                                       if (currentWorkspace.equals(securityWorkspace))
-                                               continue;
-                                       session = repository.login(currentWorkspace);
-                                       initAuthorizations(session);
-                                       JcrUtils.logoutQuietly(session);
-                               }
-                       } else {
-                               session = repository.login(workspace);
-                               initAuthorizations(session);
-                       }
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new JcrException(
-                                       "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       protected void processWorkspace(String workspace) {
-               Session session = null;
-               try {
-                       session = repository.login(workspace);
-                       initAuthorizations(session);
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new JcrException(
-                                       "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       /** @deprecated call {@link #run()} instead. */
-       @Deprecated
-       public void init() {
-               run();
-       }
-
-       protected void initAuthorizations(Session session) throws RepositoryException {
-               AccessControlManager acm = session.getAccessControlManager();
-
-               for (String privileges : principalPrivileges.keySet()) {
-                       String path = null;
-                       int slashIndex = privileges.indexOf('/');
-                       if (slashIndex == 0) {
-                               throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /");
-                       } else if (slashIndex > 0) {
-                               path = privileges.substring(slashIndex);
-                               privileges = privileges.substring(0, slashIndex);
-                       }
-
-                       if (path == null)
-                               path = "/";
-
-                       List<Privilege> privs = new ArrayList<Privilege>();
-                       for (String priv : privileges.split(",")) {
-                               privs.add(acm.privilegeFromName(priv));
-                       }
-
-                       String principalNames = principalPrivileges.get(privileges);
-                       try {
-                               new LdapName(principalNames);
-                               // TODO differentiate groups and users ?
-                               Principal principal = getOrCreatePrincipal(session, principalNames);
-                               JcrUtils.addPrivileges(session, path, principal, privs);
-                       } catch (InvalidNameException e) {
-                               for (String principalName : principalNames.split(",")) {
-                                       Principal principal = getOrCreatePrincipal(session, principalName);
-                                       JcrUtils.addPrivileges(session, path, principal, privs);
-                                       // if (log.isDebugEnabled()) {
-                                       // StringBuffer privBuf = new StringBuffer();
-                                       // for (Privilege priv : privs)
-                                       // privBuf.append(priv.getName());
-                                       // log.debug("Added privileges " + privBuf + " to "
-                                       // + principal.getName() + " on " + path + " in '"
-                                       // + session.getWorkspace().getName() + "'");
-                                       // }
-                               }
-                       }
-               }
-
-               // if (log.isDebugEnabled())
-               // log.debug("JCR authorizations applied on '"
-               // + session.getWorkspace().getName() + "'");
-       }
-
-       /**
-        * Returns a {@link SimplePrincipal}, does not check whether it exists since
-        * such capabilities is not provided by the standard JCR API. Can be
-        * overridden to provide smarter handling
-        */
-       protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException {
-               return new SimplePrincipal(principalName);
-       }
-
-       // public static void addPrivileges(Session session, Principal principal,
-       // String path, List<Privilege> privs) throws RepositoryException {
-       // AccessControlManager acm = session.getAccessControlManager();
-       // // search for an access control list
-       // AccessControlList acl = null;
-       // AccessControlPolicyIterator policyIterator = acm
-       // .getApplicablePolicies(path);
-       // if (policyIterator.hasNext()) {
-       // while (policyIterator.hasNext()) {
-       // AccessControlPolicy acp = policyIterator
-       // .nextAccessControlPolicy();
-       // if (acp instanceof AccessControlList)
-       // acl = ((AccessControlList) acp);
-       // }
-       // } else {
-       // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
-       // for (AccessControlPolicy acp : existingPolicies) {
-       // if (acp instanceof AccessControlList)
-       // acl = ((AccessControlList) acp);
-       // }
-       // }
-       //
-       // if (acl != null) {
-       // acl.addAccessControlEntry(principal,
-       // privs.toArray(new Privilege[privs.size()]));
-       // acm.setPolicy(path, acl);
-       // session.save();
-       // if (log.isDebugEnabled()) {
-       // StringBuffer buf = new StringBuffer("");
-       // for (int i = 0; i < privs.size(); i++) {
-       // if (i != 0)
-       // buf.append(',');
-       // buf.append(privs.get(i).getName());
-       // }
-       // log.debug("Added privilege(s) '" + buf + "' to '"
-       // + principal.getName() + "' on " + path
-       // + " from workspace '"
-       // + session.getWorkspace().getName() + "'");
-       // }
-       // } else {
-       // throw new ArgeoJcrException("Don't know how to apply privileges "
-       // + privs + " to " + principal + " on " + path
-       // + " from workspace '" + session.getWorkspace().getName()
-       // + "'");
-       // }
-       // }
-
-       @Deprecated
-       public void setGroupPrivileges(Map<String, String> groupPrivileges) {
-               this.principalPrivileges = groupPrivileges;
-       }
-
-       public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
-               this.principalPrivileges = principalPrivileges;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       public void setSecurityWorkspace(String securityWorkspace) {
-               this.securityWorkspace = securityWorkspace;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java
deleted file mode 100644 (file)
index efbaabe..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.jcr;
-
-import java.util.function.Function;
-
-import javax.jcr.Session;
-
-/** An arbitrary execution on a JCR session, optionally returning a result. */
-@FunctionalInterface
-public interface JcrCallback extends Function<Session, Object> {
-       /** @deprecated Use {@link #apply(Session)} instead. */
-       @Deprecated
-       public default Object execute(Session session) {
-               return apply(session);
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java
deleted file mode 100644 (file)
index c778743..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Wraps a {@link RepositoryException} in a {@link RuntimeException}.
- */
-public class JcrException extends IllegalStateException {
-       private static final long serialVersionUID = -4530350094877964989L;
-
-       public JcrException(String message, RepositoryException e) {
-               super(message, e);
-       }
-
-       public JcrException(RepositoryException e) {
-               super(e);
-       }
-
-       public RepositoryException getRepositoryCause() {
-               return (RepositoryException) getCause();
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java
deleted file mode 100644 (file)
index 71cf961..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.jcr;
-
-
-/**
- * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without
- * dependency to it.
- */
-public interface JcrMonitor {
-       /**
-        * Constant indicating an unknown amount of work.
-        */
-       public final static int UNKNOWN = -1;
-
-       /**
-        * Notifies that the main task is beginning. This must only be called once
-        * on a given progress monitor instance.
-        * 
-        * @param name
-        *            the name (or description) of the main task
-        * @param totalWork
-        *            the total number of work units into which the main task is
-        *            been subdivided. If the value is <code>UNKNOWN</code> the
-        *            implementation is free to indicate progress in a way which
-        *            doesn't require the total number of work units in advance.
-        */
-       public void beginTask(String name, int totalWork);
-
-       /**
-        * Notifies that the work is done; that is, either the main task is
-        * completed or the user canceled it. This method may be called more than
-        * once (implementations should be prepared to handle this case).
-        */
-       public void done();
-
-       /**
-        * Returns whether cancelation of current operation has been requested.
-        * Long-running operations should poll to see if cancelation has been
-        * requested.
-        * 
-        * @return <code>true</code> if cancellation has been requested, and
-        *         <code>false</code> otherwise
-        * @see #setCanceled(boolean)
-        */
-       public boolean isCanceled();
-
-       /**
-        * Sets the cancel state to the given value.
-        * 
-        * @param value
-        *            <code>true</code> indicates that cancelation has been
-        *            requested (but not necessarily acknowledged);
-        *            <code>false</code> clears this flag
-        * @see #isCanceled()
-        */
-       public void setCanceled(boolean value);
-
-       /**
-        * Sets the task name to the given value. This method is used to restore the
-        * task label after a nested operation was executed. Normally there is no
-        * need for clients to call this method.
-        * 
-        * @param name
-        *            the name (or description) of the main task
-        * @see #beginTask(java.lang.String, int)
-        */
-       public void setTaskName(String name);
-
-       /**
-        * Notifies that a subtask of the main task is beginning. Subtasks are
-        * optional; the main task might not have subtasks.
-        * 
-        * @param name
-        *            the name (or description) of the subtask
-        */
-       public void subTask(String name);
-
-       /**
-        * Notifies that a given number of work unit of the main task has been
-        * completed. Note that this amount represents an installment, as opposed to
-        * a cumulative amount of work done to date.
-        * 
-        * @param work
-        *            a non-negative number of work units just completed
-        */
-       public void worked(int work);
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java
deleted file mode 100644 (file)
index 3228eee..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-package org.argeo.jcr;
-
-import java.io.InputStream;
-import java.math.BigDecimal;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.LoginException;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-/**
- * Wrapper around a JCR repository which allows to simplify configuration and
- * intercept some actions. It exposes itself as a {@link Repository}.
- */
-public abstract class JcrRepositoryWrapper implements Repository {
-       // private final static Log log = LogFactory
-       // .getLog(JcrRepositoryWrapper.class);
-
-       // wrapped repository
-       private Repository repository;
-
-       private Map<String, String> additionalDescriptors = new HashMap<>();
-
-       private Boolean autocreateWorkspaces = false;
-
-       public JcrRepositoryWrapper(Repository repository) {
-               setRepository(repository);
-       }
-
-       /**
-        * Empty constructor
-        */
-       public JcrRepositoryWrapper() {
-       }
-
-       // /** Initializes */
-       // public void init() {
-       // }
-       //
-       // /** Shutdown the repository */
-       // public void destroy() throws Exception {
-       // }
-
-       protected void putDescriptor(String key, String value) {
-               if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key))
-                       throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository");
-               if (value == null)
-                       additionalDescriptors.remove(key);
-               else
-                       additionalDescriptors.put(key, value);
-       }
-
-       /*
-        * DELEGATED JCR REPOSITORY METHODS
-        */
-
-       public String getDescriptor(String key) {
-               if (additionalDescriptors.containsKey(key))
-                       return additionalDescriptors.get(key);
-               return getRepository().getDescriptor(key);
-       }
-
-       public String[] getDescriptorKeys() {
-               if (additionalDescriptors.size() == 0)
-                       return getRepository().getDescriptorKeys();
-               List<String> keys = Arrays.asList(getRepository().getDescriptorKeys());
-               keys.addAll(additionalDescriptors.keySet());
-               return keys.toArray(new String[keys.size()]);
-       }
-
-       /** Central login method */
-       public Session login(Credentials credentials, String workspaceName)
-                       throws LoginException, NoSuchWorkspaceException, RepositoryException {
-               Session session;
-               try {
-                       session = getRepository(workspaceName).login(credentials, workspaceName);
-               } catch (NoSuchWorkspaceException e) {
-                       if (autocreateWorkspaces && workspaceName != null)
-                               session = createWorkspaceAndLogsIn(credentials, workspaceName);
-                       else
-                               throw e;
-               }
-               processNewSession(session, workspaceName);
-               return session;
-       }
-
-       public Session login() throws LoginException, RepositoryException {
-               return login(null, null);
-       }
-
-       public Session login(Credentials credentials) throws LoginException, RepositoryException {
-               return login(credentials, null);
-       }
-
-       public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException {
-               return login(null, workspaceName);
-       }
-
-       /** Called after a session has been created, does nothing by default. */
-       protected void processNewSession(Session session, String workspaceName) {
-       }
-
-       /**
-        * Wraps access to the repository, making sure it is available.
-        * 
-        * @deprecated Use {@link #getDefaultRepository()} instead.
-        */
-       @Deprecated
-       protected synchronized Repository getRepository() {
-               return getDefaultRepository();
-       }
-
-       protected synchronized Repository getDefaultRepository() {
-               return repository;
-       }
-
-       protected synchronized Repository getRepository(String workspaceName) {
-               return getDefaultRepository();
-       }
-
-       /**
-        * Logs in to the default workspace, creates the required workspace, logs out,
-        * logs in to the required workspace.
-        */
-       protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName)
-                       throws RepositoryException {
-               if (workspaceName == null)
-                       throw new IllegalArgumentException("No workspace specified.");
-               Session session = getRepository(workspaceName).login(credentials);
-               session.getWorkspace().createWorkspace(workspaceName);
-               session.logout();
-               return getRepository(workspaceName).login(credentials, workspaceName);
-       }
-
-       public boolean isStandardDescriptor(String key) {
-               return getRepository().isStandardDescriptor(key);
-       }
-
-       public boolean isSingleValueDescriptor(String key) {
-               if (additionalDescriptors.containsKey(key))
-                       return true;
-               return getRepository().isSingleValueDescriptor(key);
-       }
-
-       public Value getDescriptorValue(String key) {
-               if (additionalDescriptors.containsKey(key))
-                       return new StrValue(additionalDescriptors.get(key));
-               return getRepository().getDescriptorValue(key);
-       }
-
-       public Value[] getDescriptorValues(String key) {
-               return getRepository().getDescriptorValues(key);
-       }
-
-       public synchronized void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
-               this.autocreateWorkspaces = autocreateWorkspaces;
-       }
-
-       protected static class StrValue implements Value {
-               private final String str;
-
-               public StrValue(String str) {
-                       this.str = str;
-               }
-
-               @Override
-               public String getString() throws ValueFormatException, IllegalStateException, RepositoryException {
-                       return str;
-               }
-
-               @Override
-               public InputStream getStream() throws RepositoryException {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public Binary getBinary() throws RepositoryException {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public long getLong() throws ValueFormatException, RepositoryException {
-                       try {
-                               return Long.parseLong(str);
-                       } catch (NumberFormatException e) {
-                               throw new ValueFormatException("Cannot convert", e);
-                       }
-               }
-
-               @Override
-               public double getDouble() throws ValueFormatException, RepositoryException {
-                       try {
-                               return Double.parseDouble(str);
-                       } catch (NumberFormatException e) {
-                               throw new ValueFormatException("Cannot convert", e);
-                       }
-               }
-
-               @Override
-               public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
-                       try {
-                               return new BigDecimal(str);
-                       } catch (NumberFormatException e) {
-                               throw new ValueFormatException("Cannot convert", e);
-                       }
-               }
-
-               @Override
-               public Calendar getDate() throws ValueFormatException, RepositoryException {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public boolean getBoolean() throws ValueFormatException, RepositoryException {
-                       try {
-                               return Boolean.parseBoolean(str);
-                       } catch (NumberFormatException e) {
-                               throw new ValueFormatException("Cannot convert", e);
-                       }
-               }
-
-               @Override
-               public int getType() {
-                       return PropertyType.STRING;
-               }
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java
deleted file mode 100644 (file)
index 82a65e7..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
-public class JcrUrlStreamHandler extends URLStreamHandler {
-       private final Session session;
-
-       public JcrUrlStreamHandler(Session session) {
-               this.session = session;
-       }
-
-       @Override
-       protected URLConnection openConnection(final URL u) throws IOException {
-               // TODO Auto-generated method stub
-               return new URLConnection(u) {
-
-                       @Override
-                       public void connect() throws IOException {
-                               String itemPath = u.getPath();
-                               try {
-                                       if (!session.itemExists(itemPath))
-                                               throw new IOException("No item under " + itemPath);
-
-                                       Item item = session.getItem(u.getPath());
-                                       if (item.isNode()) {
-                                               // this should be a nt:file node
-                                               Node node = (Node) item;
-                                               if (!node.getPrimaryNodeType().isNodeType(
-                                                               NodeType.NT_FILE))
-                                                       throw new IOException("Node " + node + " is not a "
-                                                                       + NodeType.NT_FILE);
-
-                                       } else {
-                                               Property property = (Property) item;
-                                               if(property.getType()==PropertyType.BINARY){
-                                                       //Binary binary = property.getBinary();
-                                                       
-                                               }
-                                       }
-                               } catch (RepositoryException e) {
-                                       IOException ioe = new IOException(
-                                                       "Unexpected JCR exception");
-                                       ioe.initCause(e);
-                                       throw ioe;
-                               }
-                       }
-
-                       @Override
-                       public InputStream getInputStream() throws IOException {
-                               // TODO Auto-generated method stub
-                               return super.getInputStream();
-                       }
-
-               };
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java
deleted file mode 100644 (file)
index 3be8be1..0000000
+++ /dev/null
@@ -1,1778 +0,0 @@
-package org.argeo.jcr;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PipedInputStream;
-import java.io.PipedOutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Binary;
-import javax.jcr.Credentials;
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.EventListener;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryResult;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlList;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
-
-import org.apache.commons.io.IOUtils;
-
-/** Utility methods to simplify common JCR operations. */
-public class JcrUtils {
-
-//     final private static Log log = LogFactory.getLog(JcrUtils.class);
-
-       /**
-        * Not complete yet. See
-        * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
-        * %20Names
-        */
-       public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<',
-                       '>', '&' };
-
-       /** Prevents instantiation */
-       private JcrUtils() {
-       }
-
-       /**
-        * Queries one single node.
-        * 
-        * @return one single node or null if none was found
-        * @throws JcrException if more than one node was found
-        */
-       public static Node querySingleNode(Query query) {
-               NodeIterator nodeIterator;
-               try {
-                       QueryResult queryResult = query.execute();
-                       nodeIterator = queryResult.getNodes();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot execute query " + query, e);
-               }
-               Node node;
-               if (nodeIterator.hasNext())
-                       node = nodeIterator.nextNode();
-               else
-                       return null;
-
-               if (nodeIterator.hasNext())
-                       throw new IllegalArgumentException("Query returned more than one node.");
-               return node;
-       }
-
-       /** Retrieves the node name from the provided path */
-       public static String nodeNameFromPath(String path) {
-               if (path.equals("/"))
-                       return "";
-               if (path.charAt(0) != '/')
-                       throw new IllegalArgumentException("Path " + path + " must start with a '/'");
-               String pathT = path;
-               if (pathT.charAt(pathT.length() - 1) == '/')
-                       pathT = pathT.substring(0, pathT.length() - 2);
-
-               int index = pathT.lastIndexOf('/');
-               return pathT.substring(index + 1);
-       }
-
-       /** Retrieves the parent path of the provided path */
-       public static String parentPath(String path) {
-               if (path.equals("/"))
-                       throw new IllegalArgumentException("Root path '/' has no parent path");
-               if (path.charAt(0) != '/')
-                       throw new IllegalArgumentException("Path " + path + " must start with a '/'");
-               String pathT = path;
-               if (pathT.charAt(pathT.length() - 1) == '/')
-                       pathT = pathT.substring(0, pathT.length() - 2);
-
-               int index = pathT.lastIndexOf('/');
-               return pathT.substring(0, index);
-       }
-
-       /** The provided data as a path ('/' at the end, not the beginning) */
-       public static String dateAsPath(Calendar cal) {
-               return dateAsPath(cal, false);
-       }
-
-       /**
-        * Creates a deep path based on a URL:
-        * http://subdomain.example.com/to/content?args becomes
-        * com/example/subdomain/to/content
-        */
-       public static String urlAsPath(String url) {
-               try {
-                       URL u = new URL(url);
-                       StringBuffer path = new StringBuffer(url.length());
-                       // invert host
-                       path.append(hostAsPath(u.getHost()));
-                       // we don't put port since it may not always be there and may change
-                       path.append(u.getPath());
-                       return path.toString();
-               } catch (MalformedURLException e) {
-                       throw new IllegalArgumentException("Cannot generate URL path for " + url, e);
-               }
-       }
-
-       /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
-       public static void urlToAddressProperties(Node node, String url) {
-               try {
-                       URL u = new URL(url);
-                       node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
-                       node.setProperty(Property.JCR_HOST, u.getHost());
-                       node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
-                       node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set URL " + url + " as nt:address properties", e);
-               } catch (MalformedURLException e) {
-                       throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e);
-               }
-       }
-
-       /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
-       public static String urlFromAddressProperties(Node node) {
-               try {
-                       URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(),
-                                       node.getProperty(Property.JCR_HOST).getString(),
-                                       (int) node.getProperty(Property.JCR_PORT).getLong(),
-                                       node.getProperty(Property.JCR_PATH).getString());
-                       return u.toString();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get URL from nt:address properties of " + node, e);
-               } catch (MalformedURLException e) {
-                       throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e);
-               }
-       }
-
-       /*
-        * PATH UTILITIES
-        */
-
-       /**
-        * Make sure that: starts with '/', do not end with '/', do not have '//'
-        */
-       public static String normalizePath(String path) {
-               List<String> tokens = tokenize(path);
-               StringBuffer buf = new StringBuffer(path.length());
-               for (String token : tokens) {
-                       buf.append('/');
-                       buf.append(token);
-               }
-               return buf.toString();
-       }
-
-       /**
-        * Creates a path from a FQDN, inverting the order of the component:
-        * www.argeo.org becomes org.argeo.www
-        */
-       public static String hostAsPath(String host) {
-               StringBuffer path = new StringBuffer(host.length());
-               String[] hostTokens = host.split("\\.");
-               for (int i = hostTokens.length - 1; i >= 0; i--) {
-                       path.append(hostTokens[i]);
-                       if (i != 0)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes
-        * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
-        */
-       public static String uuidAsPath(String uuid) {
-               StringBuffer path = new StringBuffer(uuid.length());
-               String[] tokens = uuid.split("-");
-               for (int i = 0; i < tokens.length; i++) {
-                       path.append(tokens[i]);
-                       if (i != 0)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * The provided data as a path ('/' at the end, not the beginning)
-        * 
-        * @param cal     the date
-        * @param addHour whether to add hour as well
-        */
-       public static String dateAsPath(Calendar cal, Boolean addHour) {
-               StringBuffer buf = new StringBuffer(14);
-               buf.append('Y');
-               buf.append(cal.get(Calendar.YEAR));
-               buf.append('/');
-
-               int month = cal.get(Calendar.MONTH) + 1;
-               buf.append('M');
-               if (month < 10)
-                       buf.append(0);
-               buf.append(month);
-               buf.append('/');
-
-               int day = cal.get(Calendar.DAY_OF_MONTH);
-               buf.append('D');
-               if (day < 10)
-                       buf.append(0);
-               buf.append(day);
-               buf.append('/');
-
-               if (addHour) {
-                       int hour = cal.get(Calendar.HOUR_OF_DAY);
-                       buf.append('H');
-                       if (hour < 10)
-                               buf.append(0);
-                       buf.append(hour);
-                       buf.append('/');
-               }
-               return buf.toString();
-
-       }
-
-       /** Converts in one call a string into a gregorian calendar. */
-       public static Calendar parseCalendar(DateFormat dateFormat, String value) {
-               try {
-                       Date date = dateFormat.parse(value);
-                       Calendar calendar = new GregorianCalendar();
-                       calendar.setTime(date);
-                       return calendar;
-               } catch (ParseException e) {
-                       throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e);
-               }
-
-       }
-
-       /** The last element of a path. */
-       public static String lastPathElement(String path) {
-               if (path.charAt(path.length() - 1) == '/')
-                       throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
-               int index = path.lastIndexOf('/');
-               if (index < 0)
-                       return path;
-               return path.substring(index + 1);
-       }
-
-       /**
-        * Call {@link Node#getName()} without exceptions (useful in super
-        * constructors).
-        */
-       public static String getNameQuietly(Node node) {
-               try {
-                       return node.getName();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name from " + node, e);
-               }
-       }
-
-       /**
-        * Call {@link Node#getProperty(String)} without exceptions (useful in super
-        * constructors).
-        */
-       public static String getStringPropertyQuietly(Node node, String propertyName) {
-               try {
-                       return node.getProperty(propertyName).getString();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name from " + node, e);
-               }
-       }
-
-//     /**
-//      * Routine that get the child with this name, adding it if it does not already
-//      * exist
-//      */
-//     public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException {
-//             return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType);
-//     }
-
-       /**
-        * Routine that get the child with this name, adding it if it does not already
-        * exist
-        */
-       public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes)
-                       throws RepositoryException {
-               Node node;
-               if (parent.hasNode(name)) {
-                       node = parent.getNode(name);
-                       if (primaryNodeType != null && !node.isNodeType(primaryNodeType))
-                               throw new IllegalArgumentException("Node " + node + " exists but is of primary node type "
-                                               + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType);
-                       for (String mixin : mixinNodeTypes) {
-                               if (!node.isNodeType(mixin))
-                                       node.addMixin(mixin);
-                       }
-                       return node;
-               } else {
-                       node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name);
-                       for (String mixin : mixinNodeTypes) {
-                               node.addMixin(mixin);
-                       }
-                       return node;
-               }
-       }
-
-       /**
-        * Routine that get the child with this name, adding it if it does not already
-        * exist
-        */
-       public static Node getOrAdd(Node parent, String name) throws RepositoryException {
-               return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name);
-       }
-
-       /** Convert a {@link NodeIterator} to a list of {@link Node} */
-       public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
-               List<Node> nodes = new ArrayList<Node>();
-               while (nodeIterator.hasNext()) {
-                       nodes.add(nodeIterator.nextNode());
-               }
-               return nodes;
-       }
-
-       /*
-        * PROPERTIES
-        */
-
-       /**
-        * Concisely get the string value of a property or null if this node doesn't
-        * have this property
-        */
-       public static String get(Node node, String propertyName) {
-               try {
-                       if (!node.hasProperty(propertyName))
-                               return null;
-                       return node.getProperty(propertyName).getString();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
-               }
-       }
-
-       /** Concisely get the path of the given node. */
-       public static String getPath(Node node) {
-               try {
-                       return node.getPath();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get path of " + node, e);
-               }
-       }
-
-       /** Concisely get the boolean value of a property */
-       public static Boolean check(Node node, String propertyName) {
-               try {
-                       return node.getProperty(propertyName).getBoolean();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
-               }
-       }
-
-       /** Concisely get the bytes array value of a property */
-       public static byte[] getBytes(Node node, String propertyName) {
-               try {
-                       return getBinaryAsBytes(node.getProperty(propertyName));
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + propertyName + " of " + node, e);
-               }
-       }
-
-       /*
-        * MKDIRS
-        */
-
-       /**
-        * Create sub nodes relative to a parent node
-        */
-       public static Node mkdirs(Node parentNode, String relativePath) {
-               return mkdirs(parentNode, relativePath, null, null);
-       }
-
-       /**
-        * Create sub nodes relative to a parent node
-        * 
-        * @param nodeType the type of the leaf node
-        */
-       public static Node mkdirs(Node parentNode, String relativePath, String nodeType) {
-               return mkdirs(parentNode, relativePath, nodeType, null);
-       }
-
-       /**
-        * Create sub nodes relative to a parent node
-        * 
-        * @param nodeType the type of the leaf node
-        */
-       public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) {
-               List<String> tokens = tokenize(relativePath);
-               Node currParent = parentNode;
-               try {
-                       for (int i = 0; i < tokens.size(); i++) {
-                               String name = tokens.get(i);
-                               if (currParent.hasNode(name)) {
-                                       currParent = currParent.getNode(name);
-                               } else {
-                                       if (i != (tokens.size() - 1)) {// intermediary
-                                               currParent = currParent.addNode(name, intermediaryNodeType);
-                                       } else {// leaf
-                                               currParent = currParent.addNode(name, nodeType);
-                                       }
-                               }
-                       }
-                       return currParent;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e);
-               }
-       }
-
-       /**
-        * Synchronized and save is performed, to avoid race conditions in initializers
-        * leading to duplicate nodes.
-        */
-       public synchronized static Node mkdirsSafe(Session session, String path, String type) {
-               try {
-                       if (session.hasPendingChanges())
-                               throw new IllegalStateException("Session has pending changes, save them first.");
-                       Node node = mkdirs(session, path, type);
-                       session.save();
-                       return node;
-               } catch (RepositoryException e) {
-                       discardQuietly(session);
-                       throw new JcrException("Cannot safely make directories", e);
-               }
-       }
-
-       public synchronized static Node mkdirsSafe(Session session, String path) {
-               return mkdirsSafe(session, path, null);
-       }
-
-       /** Creates the nodes making path, if they don't exist. */
-       public static Node mkdirs(Session session, String path) {
-               return mkdirs(session, path, null, null, false);
-       }
-
-       /**
-        * @param type the type of the leaf node
-        */
-       public static Node mkdirs(Session session, String path, String type) {
-               return mkdirs(session, path, type, null, false);
-       }
-
-       /**
-        * Creates the nodes making path, if they don't exist. This is up to the caller
-        * to save the session. Use with caution since it can create duplicate nodes if
-        * used concurrently. Requires read access to the root node of the workspace.
-        */
-       public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType,
-                       Boolean versioning) {
-               try {
-                       if (path.equals("/"))
-                               return session.getRootNode();
-
-                       if (session.itemExists(path)) {
-                               Node node = session.getNode(path);
-                               // check type
-                               if (type != null && !node.isNodeType(type) && !node.getPath().equals("/"))
-                                       throw new IllegalArgumentException("Node " + node + " exists but is of type "
-                                                       + node.getPrimaryNodeType().getName() + " not of type " + type);
-                               // TODO: check versioning
-                               return node;
-                       }
-
-                       // StringBuffer current = new StringBuffer("/");
-                       // Node currentNode = session.getRootNode();
-
-                       Node currentNode = findClosestExistingParent(session, path);
-                       String closestExistingParentPath = currentNode.getPath();
-                       StringBuffer current = new StringBuffer(closestExistingParentPath);
-                       if (!closestExistingParentPath.endsWith("/"))
-                               current.append('/');
-                       Iterator<String> it = tokenize(path.substring(closestExistingParentPath.length())).iterator();
-                       while (it.hasNext()) {
-                               String part = it.next();
-                               current.append(part).append('/');
-                               if (!session.itemExists(current.toString())) {
-                                       if (!it.hasNext() && type != null)
-                                               currentNode = currentNode.addNode(part, type);
-                                       else if (it.hasNext() && intermediaryNodeType != null)
-                                               currentNode = currentNode.addNode(part, intermediaryNodeType);
-                                       else
-                                               currentNode = currentNode.addNode(part);
-                                       if (versioning)
-                                               currentNode.addMixin(NodeType.MIX_VERSIONABLE);
-//                                     if (log.isTraceEnabled())
-//                                             log.debug("Added folder " + part + " as " + current);
-                               } else {
-                                       currentNode = (Node) session.getItem(current.toString());
-                               }
-                       }
-                       return currentNode;
-               } catch (RepositoryException e) {
-                       discardQuietly(session);
-                       throw new JcrException("Cannot mkdirs " + path, e);
-               } finally {
-               }
-       }
-
-       private static Node findClosestExistingParent(Session session, String path) throws RepositoryException {
-               int idx = path.lastIndexOf('/');
-               if (idx == 0)
-                       return session.getRootNode();
-               String parentPath = path.substring(0, idx);
-               if (session.itemExists(parentPath))
-                       return session.getNode(parentPath);
-               else
-                       return findClosestExistingParent(session, parentPath);
-       }
-
-       /** Convert a path to the list of its tokens */
-       public static List<String> tokenize(String path) {
-               List<String> tokens = new ArrayList<String>();
-               boolean optimized = false;
-               if (!optimized) {
-                       String[] rawTokens = path.split("/");
-                       for (String token : rawTokens) {
-                               if (!token.equals(""))
-                                       tokens.add(token);
-                       }
-               } else {
-                       StringBuffer curr = new StringBuffer();
-                       char[] arr = path.toCharArray();
-                       chars: for (int i = 0; i < arr.length; i++) {
-                               char c = arr[i];
-                               if (c == '/') {
-                                       if (i == 0 || (i == arr.length - 1))
-                                               continue chars;
-                                       if (curr.length() > 0) {
-                                               tokens.add(curr.toString());
-                                               curr = new StringBuffer();
-                                       }
-                               } else
-                                       curr.append(c);
-                       }
-                       if (curr.length() > 0) {
-                               tokens.add(curr.toString());
-                               curr = new StringBuffer();
-                       }
-               }
-               return Collections.unmodifiableList(tokens);
-       }
-
-       // /**
-       // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
-       // *
-       // * @deprecated
-       // */
-       // @Deprecated
-       // public static Node mkdirs(Session session, String path, String type,
-       // Boolean versioning) {
-       // return mkdirs(session, path, type, type, false);
-       // }
-
-       /**
-        * Safe and repository implementation independent registration of a namespace.
-        */
-       public static void registerNamespaceSafely(Session session, String prefix, String uri) {
-               try {
-                       registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot find namespace registry", e);
-               }
-       }
-
-       /**
-        * Safe and repository implementation independent registration of a namespace.
-        */
-       public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) {
-               try {
-                       String[] prefixes = nr.getPrefixes();
-                       for (String pref : prefixes)
-                               if (pref.equals(prefix)) {
-                                       String registeredUri = nr.getURI(pref);
-                                       if (!registeredUri.equals(uri))
-                                               throw new IllegalArgumentException("Prefix " + pref + " already registered for URI "
-                                                               + registeredUri + " which is different from provided URI " + uri);
-                                       else
-                                               return;// skip
-                               }
-                       nr.registerNamespace(prefix, uri);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e);
-               }
-       }
-
-//     /** Recursively outputs the contents of the given node. */
-//     public static void debug(Node node) {
-//             debug(node, log);
-//     }
-//
-//     /** Recursively outputs the contents of the given node. */
-//     public static void debug(Node node, Log log) {
-//             try {
-//                     // First output the node path
-//                     log.debug(node.getPath());
-//                     // Skip the virtual (and large!) jcr:system subtree
-//                     if (node.getName().equals("jcr:system")) {
-//                             return;
-//                     }
-//
-//                     // Then the children nodes (recursive)
-//                     NodeIterator it = node.getNodes();
-//                     while (it.hasNext()) {
-//                             Node childNode = it.nextNode();
-//                             debug(childNode, log);
-//                     }
-//
-//                     // Then output the properties
-//                     PropertyIterator properties = node.getProperties();
-//                     // log.debug("Property are : ");
-//
-//                     properties: while (properties.hasNext()) {
-//                             Property property = properties.nextProperty();
-//                             if (property.getType() == PropertyType.BINARY)
-//                                     continue properties;// skip
-//                             if (property.getDefinition().isMultiple()) {
-//                                     // A multi-valued property, print all values
-//                                     Value[] values = property.getValues();
-//                                     for (int i = 0; i < values.length; i++) {
-//                                             log.debug(property.getPath() + "=" + values[i].getString());
-//                                     }
-//                             } else {
-//                                     // A single-valued property
-//                                     log.debug(property.getPath() + "=" + property.getString());
-//                             }
-//                     }
-//             } catch (Exception e) {
-//                     log.error("Could not debug " + node, e);
-//             }
-//
-//     }
-
-//     /** Logs the effective access control policies */
-//     public static void logEffectiveAccessPolicies(Node node) {
-//             try {
-//                     logEffectiveAccessPolicies(node.getSession(), node.getPath());
-//             } catch (RepositoryException e) {
-//                     log.error("Cannot log effective access policies of " + node, e);
-//             }
-//     }
-//
-//     /** Logs the effective access control policies */
-//     public static void logEffectiveAccessPolicies(Session session, String path) {
-//             if (!log.isDebugEnabled())
-//                     return;
-//
-//             try {
-//                     AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path);
-//                     if (effectivePolicies.length > 0) {
-//                             for (AccessControlPolicy policy : effectivePolicies) {
-//                                     if (policy instanceof AccessControlList) {
-//                                             AccessControlList acl = (AccessControlList) policy;
-//                                             log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl));
-//                                     }
-//                             }
-//                     } else {
-//                             log.debug("No effective access control policy for " + path);
-//                     }
-//             } catch (RepositoryException e) {
-//                     log.error("Cannot log effective access policies of " + path, e);
-//             }
-//     }
-
-       /** Returns a human-readable summary of this access control list. */
-       public static String accessControlListSummary(AccessControlList acl) {
-               StringBuffer buf = new StringBuffer("");
-               try {
-                       for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                               buf.append('\t').append(ace.getPrincipal().getName()).append('\n');
-                               for (Privilege priv : ace.getPrivileges())
-                                       buf.append("\t\t").append(priv.getName()).append('\n');
-                       }
-                       return buf.toString();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot write summary of " + acl, e);
-               }
-       }
-
-       /** Copy the whole workspace via a system view XML. */
-       public static void copyWorkspaceXml(Session fromSession, Session toSession) {
-               Workspace fromWorkspace = fromSession.getWorkspace();
-               Workspace toWorkspace = toSession.getWorkspace();
-               String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML.";
-
-               try (PipedInputStream in = new PipedInputStream(1024 * 1024);) {
-                       new Thread(() -> {
-                               try (PipedOutputStream out = new PipedOutputStream(in)) {
-                                       fromSession.exportSystemView("/", out, false, false);
-                                       out.flush();
-                               } catch (IOException e) {
-                                       throw new RuntimeException(errorMsg, e);
-                               } catch (RepositoryException e) {
-                                       throw new JcrException(errorMsg, e);
-                               }
-                       }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start();
-
-                       toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                       toSession.save();
-               } catch (IOException e) {
-                       throw new RuntimeException(errorMsg, e);
-               } catch (RepositoryException e) {
-                       throw new JcrException(errorMsg, e);
-               }
-       }
-
-       /**
-        * Copies recursively the content of a node to another one. Do NOT copy the
-        * property values of {@link NodeType#MIX_CREATED} and
-        * {@link NodeType#MIX_LAST_MODIFIED}, but update the
-        * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY}
-        * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED}
-        * mixin.
-        */
-       public static void copy(Node fromNode, Node toNode) {
-               try {
-                       if (toNode.getDefinition().isProtected())
-                               return;
-
-                       // add mixins
-                       for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
-                               try {
-                                       toNode.addMixin(mixinType.getName());
-                               } catch (NoSuchNodeTypeException e) {
-                                       // ignore unknown mixins
-                                       // TODO log it
-                               }
-                       }
-
-                       // process properties
-                       PropertyIterator pit = fromNode.getProperties();
-                       properties: while (pit.hasNext()) {
-                               Property fromProperty = pit.nextProperty();
-                               String propertyName = fromProperty.getName();
-                               if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected())
-                                       continue properties;
-
-                               if (fromProperty.getDefinition().isProtected())
-                                       continue properties;
-
-                               if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy")
-                                               || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy"))
-                                       continue properties;
-
-                               if (fromProperty.isMultiple()) {
-                                       toNode.setProperty(propertyName, fromProperty.getValues());
-                               } else {
-                                       toNode.setProperty(propertyName, fromProperty.getValue());
-                               }
-                       }
-
-                       // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
-                       // they existed, before adding the mixins
-                       updateLastModified(toNode, true);
-
-                       // process children nodes
-                       NodeIterator nit = fromNode.getNodes();
-                       while (nit.hasNext()) {
-                               Node fromChild = nit.nextNode();
-                               Integer index = fromChild.getIndex();
-                               String nodeRelPath = fromChild.getName() + "[" + index + "]";
-                               Node toChild;
-                               if (toNode.hasNode(nodeRelPath))
-                                       toChild = toNode.getNode(nodeRelPath);
-                               else {
-                                       try {
-                                               toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName());
-                                       } catch (NoSuchNodeTypeException e) {
-                                               // ignore unknown primary types
-                                               // TODO log it
-                                               return;
-                                       }
-                               }
-                               copy(fromChild, toChild);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e);
-               }
-       }
-
-       /**
-        * Check whether all first-level properties (except jcr:* properties) are equal.
-        * Skip jcr:* properties
-        */
-       public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) {
-               try {
-                       PropertyIterator pit = reference.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property propReference = pit.nextProperty();
-                               String propName = propReference.getName();
-                               if (propName.startsWith("jcr:"))
-                                       continue props;
-
-                               if (!observed.hasProperty(propName))
-                                       if (onlyCommonProperties)
-                                               continue props;
-                                       else
-                                               return false;
-                               // TODO: deal with multiple property values?
-                               if (!observed.getProperty(propName).getValue().equals(propReference.getValue()))
-                                       return false;
-                       }
-                       return true;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e);
-               }
-       }
-
-       public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed) {
-               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
-               diffPropertiesLevel(diffs, null, reference, observed);
-               return diffs;
-       }
-
-       /**
-        * Compare the properties of two nodes. Recursivity to child nodes is not yet
-        * supported. Skip jcr:* properties.
-        */
-       static void diffPropertiesLevel(Map<String, PropertyDiff> diffs, String baseRelPath, Node reference,
-                       Node observed) {
-               try {
-                       // check removed and modified
-                       PropertyIterator pit = reference.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               String name = p.getName();
-                               if (name.startsWith("jcr:"))
-                                       continue props;
-
-                               if (!observed.hasProperty(name)) {
-                                       String relPath = propertyRelPath(baseRelPath, name);
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null);
-                                       diffs.put(relPath, pDiff);
-                               } else {
-                                       if (p.isMultiple()) {
-                                               // FIXME implement multiple
-                                       } else {
-                                               Value referenceValue = p.getValue();
-                                               Value newValue = observed.getProperty(name).getValue();
-                                               if (!referenceValue.equals(newValue)) {
-                                                       String relPath = propertyRelPath(baseRelPath, name);
-                                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue,
-                                                                       newValue);
-                                                       diffs.put(relPath, pDiff);
-                                               }
-                                       }
-                               }
-                       }
-                       // check added
-                       pit = observed.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               String name = p.getName();
-                               if (name.startsWith("jcr:"))
-                                       continue props;
-                               if (!reference.hasProperty(name)) {
-                                       if (p.isMultiple()) {
-                                               // FIXME implement multiple
-                                       } else {
-                                               String relPath = propertyRelPath(baseRelPath, name);
-                                               PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue());
-                                               diffs.put(relPath, pDiff);
-                                       }
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot diff " + reference + " and " + observed, e);
-               }
-       }
-
-       /**
-        * Compare only a restricted list of properties of two nodes. No recursivity.
-        * 
-        */
-       public static Map<String, PropertyDiff> diffProperties(Node reference, Node observed, List<String> properties) {
-               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
-               try {
-                       Iterator<String> pit = properties.iterator();
-
-                       props: while (pit.hasNext()) {
-                               String name = pit.next();
-                               if (!reference.hasProperty(name)) {
-                                       if (!observed.hasProperty(name))
-                                               continue props;
-                                       Value val = observed.getProperty(name).getValue();
-                                       try {
-                                               // empty String but not null
-                                               if ("".equals(val.getString()))
-                                                       continue props;
-                                       } catch (Exception e) {
-                                               // not parseable as String, silent
-                                       }
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val);
-                                       diffs.put(name, pDiff);
-                               } else if (!observed.hasProperty(name)) {
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name,
-                                                       reference.getProperty(name).getValue(), null);
-                                       diffs.put(name, pDiff);
-                               } else {
-                                       Value referenceValue = reference.getProperty(name).getValue();
-                                       Value newValue = observed.getProperty(name).getValue();
-                                       if (!referenceValue.equals(newValue)) {
-                                               PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue);
-                                               diffs.put(name, pDiff);
-                                       }
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot diff " + reference + " and " + observed, e);
-               }
-               return diffs;
-       }
-
-       /** Builds a property relPath to be used in the diff. */
-       private static String propertyRelPath(String baseRelPath, String propertyName) {
-               if (baseRelPath == null)
-                       return propertyName;
-               else
-                       return baseRelPath + '/' + propertyName;
-       }
-
-       /**
-        * Normalizes a name so that it can be stored in contexts not supporting names
-        * with ':' (typically databases). Replaces ':' by '_'.
-        */
-       public static String normalize(String name) {
-               return name.replace(':', '_');
-       }
-
-       /**
-        * Replaces characters which are invalid in a JCR name by '_'. Currently not
-        * exhaustive.
-        * 
-        * @see JcrUtils#INVALID_NAME_CHARACTERS
-        */
-       public static String replaceInvalidChars(String name) {
-               return replaceInvalidChars(name, '_');
-       }
-
-       /**
-        * Replaces characters which are invalid in a JCR name. Currently not
-        * exhaustive.
-        * 
-        * @see JcrUtils#INVALID_NAME_CHARACTERS
-        */
-       public static String replaceInvalidChars(String name, char replacement) {
-               boolean modified = false;
-               char[] arr = name.toCharArray();
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
-                               if (c == invalid) {
-                                       arr[i] = replacement;
-                                       modified = true;
-                                       break invalid;
-                               }
-                       }
-               }
-               if (modified)
-                       return new String(arr);
-               else
-                       // do not create new object if unnecessary
-                       return name;
-       }
-
-       // /**
-       // * Removes forbidden characters from a path, replacing them with '_'
-       // *
-       // * @deprecated use {@link #replaceInvalidChars(String)} instead
-       // */
-       // public static String removeForbiddenCharacters(String str) {
-       // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*',
-       // '_');
-       //
-       // }
-
-       /** Cleanly disposes a {@link Binary} even if it is null. */
-       public static void closeQuietly(Binary binary) {
-               if (binary == null)
-                       return;
-               binary.dispose();
-       }
-
-       /** Retrieve a {@link Binary} as a byte array */
-       public static byte[] getBinaryAsBytes(Property property) {
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream();
-                               Bin binary = new Bin(property);
-                               InputStream in = binary.getStream()) {
-                       IOUtils.copy(in, out);
-                       return out.toByteArray();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot read binary " + property + " as bytes", e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot read binary " + property + " as bytes", e);
-               }
-       }
-
-       /** Writes a {@link Binary} from a byte array */
-       public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
-               Binary binary = null;
-               try (InputStream in = new ByteArrayInputStream(bytes)) {
-                       binary = node.getSession().getValueFactory().createBinary(in);
-                       node.setProperty(property, binary);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set binary " + property + " as bytes", e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot set binary " + property + " as bytes", e);
-               } finally {
-                       closeQuietly(binary);
-               }
-       }
-
-       /** Writes a {@link Binary} from a byte array */
-       public static void setBinaryAsBytes(Property prop, byte[] bytes) {
-               Binary binary = null;
-               try (InputStream in = new ByteArrayInputStream(bytes)) {
-                       binary = prop.getSession().getValueFactory().createBinary(in);
-                       prop.setValue(binary);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set binary " + prop + " as bytes", e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot set binary " + prop + " as bytes", e);
-               } finally {
-                       closeQuietly(binary);
-               }
-       }
-
-       /**
-        * Creates depth from a string (typically a username) by adding levels based on
-        * its first characters: "aBcD",2 becomes a/aB
-        */
-       public static String firstCharsToPath(String str, Integer nbrOfChars) {
-               if (str.length() < nbrOfChars)
-                       throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars);
-               StringBuffer path = new StringBuffer("");
-               StringBuffer curr = new StringBuffer("");
-               for (int i = 0; i < nbrOfChars; i++) {
-                       curr.append(str.charAt(i));
-                       path.append(curr);
-                       if (i < nbrOfChars - 1)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * Discards the current changes in the session attached to this node. To be used
-        * typically in a catch block.
-        * 
-        * @see #discardQuietly(Session)
-        */
-       public static void discardUnderlyingSessionQuietly(Node node) {
-               try {
-                       discardQuietly(node.getSession());
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Discards the current changes in a session by calling
-        * {@link Session#refresh(boolean)} with <code>false</code>, only logging
-        * potential errors when doing so. To be used typically in a catch block.
-        */
-       public static void discardQuietly(Session session) {
-               try {
-                       if (session != null)
-                               session.refresh(false);
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Login to a workspace with implicit credentials, creates the workspace with
-        * these credentials if it does not already exist.
-        */
-       public static Session loginOrCreateWorkspace(Repository repository, String workspaceName)
-                       throws RepositoryException {
-               return loginOrCreateWorkspace(repository, workspaceName, null);
-       }
-
-       /**
-        * Login to a workspace with implicit credentials, creates the workspace with
-        * these credentials if it does not already exist.
-        */
-       public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials)
-                       throws RepositoryException {
-               Session workspaceSession = null;
-               Session defaultSession = null;
-               try {
-                       try {
-                               workspaceSession = repository.login(credentials, workspaceName);
-                       } catch (NoSuchWorkspaceException e) {
-                               // try to create workspace
-                               defaultSession = repository.login(credentials);
-                               defaultSession.getWorkspace().createWorkspace(workspaceName);
-                               workspaceSession = repository.login(credentials, workspaceName);
-                       }
-                       return workspaceSession;
-               } finally {
-                       logoutQuietly(defaultSession);
-               }
-       }
-
-       /**
-        * Logs out the session, not throwing any exception, even if it is null.
-        * {@link Jcr#logout(Session)} should rather be used.
-        */
-       public static void logoutQuietly(Session session) {
-               Jcr.logout(session);
-//             try {
-//                     if (session != null)
-//                             if (session.isLive())
-//                                     session.logout();
-//             } catch (Exception e) {
-//                     // silent
-//             }
-       }
-
-       /**
-        * Convenient method to add a listener. uuids passed as null, deep=true,
-        * local=true, only one node type
-        */
-       public static void addListener(Session session, EventListener listener, int eventTypes, String basePath,
-                       String nodeType) {
-               try {
-                       session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null,
-                                       nodeType == null ? null : new String[] { nodeType }, true);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e);
-               }
-       }
-
-       /** Removes a listener without throwing exception */
-       public static void removeListenerQuietly(Session session, EventListener listener) {
-               if (session == null || !session.isLive())
-                       return;
-               try {
-                       session.getWorkspace().getObservationManager().removeEventListener(listener);
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Quietly unregisters an {@link EventListener} from the udnerlying workspace of
-        * this node.
-        */
-       public static void unregisterQuietly(Node node, EventListener eventListener) {
-               try {
-                       unregisterQuietly(node.getSession().getWorkspace(), eventListener);
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /** Quietly unregisters an {@link EventListener} from this workspace */
-       public static void unregisterQuietly(Workspace workspace, EventListener eventListener) {
-               if (eventListener == null)
-                       return;
-               try {
-                       workspace.getObservationManager().removeEventListener(eventListener);
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
-        * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
-        */
-       public static Instant getModified(Node node) {
-               Calendar calendar = null;
-               try {
-                       if (node.hasProperty(Property.JCR_LAST_MODIFIED))
-                               calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
-                       else if (node.hasProperty(Property.JCR_CREATED))
-                               calendar = node.getProperty(Property.JCR_CREATED).getDate();
-                       else
-                               throw new IllegalArgumentException("No modification time found in " + node);
-                       return calendar.toInstant();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get modification time for " + node, e);
-               }
-
-       }
-
-       /**
-        * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
-        */
-       public static Instant getCreated(Node node) {
-               Calendar calendar = null;
-               try {
-                       if (node.hasProperty(Property.JCR_CREATED))
-                               calendar = node.getProperty(Property.JCR_CREATED).getDate();
-                       else
-                               throw new IllegalArgumentException("No created time found in " + node);
-                       return calendar.toInstant();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get created time for " + node, e);
-               }
-
-       }
-
-       /**
-        * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
-        * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
-        * session user id.
-        */
-       public static void updateLastModified(Node node) {
-               updateLastModified(node, false);
-       }
-
-       /**
-        * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
-        * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
-        * session user id. In Jackrabbit 2.x,
-        * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
-        * not automatically updated</a>, hence the need for manual update. The session
-        * is not saved.
-        */
-       public static void updateLastModified(Node node, boolean addMixin) {
-               try {
-                       if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
-                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                       node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
-                       node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot update last modified on " + node, e);
-               }
-       }
-
-       /**
-        * Update lastModified recursively until this parent.
-        * 
-        * @param node      the node
-        * @param untilPath the base path, null is equivalent to "/"
-        */
-       public static void updateLastModifiedAndParents(Node node, String untilPath) {
-               updateLastModifiedAndParents(node, untilPath, true);
-       }
-
-       /**
-        * Update lastModified recursively until this parent.
-        * 
-        * @param node      the node
-        * @param untilPath the base path, null is equivalent to "/"
-        */
-       public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
-               try {
-                       if (untilPath != null && !node.getPath().startsWith(untilPath))
-                               throw new IllegalArgumentException(node + " is not under " + untilPath);
-                       updateLastModified(node, addMixin);
-                       if (untilPath == null) {
-                               if (!node.getPath().equals("/"))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
-                       } else {
-                               if (!node.getPath().equals(untilPath))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
-               }
-       }
-
-       /**
-        * Returns a String representing the short version (see
-        * <a href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
-        * Notation </a> attributes grammar) of the main business attributes of this
-        * property definition
-        * 
-        * @param prop
-        */
-       public static String getPropertyDefinitionAsString(Property prop) {
-               StringBuffer sbuf = new StringBuffer();
-               try {
-                       if (prop.getDefinition().isAutoCreated())
-                               sbuf.append("a");
-                       if (prop.getDefinition().isMandatory())
-                               sbuf.append("m");
-                       if (prop.getDefinition().isProtected())
-                               sbuf.append("p");
-                       if (prop.getDefinition().isMultiple())
-                               sbuf.append("*");
-               } catch (RepositoryException re) {
-                       throw new JcrException("unexpected error while getting property definition as String", re);
-               }
-               return sbuf.toString();
-       }
-
-       /**
-        * Estimate the sub tree size from current node. Computation is based on the Jcr
-        * {@link Property#getLength()} method. Note : it is not the exact size used on
-        * the disk by the current part of the JCR Tree.
-        */
-
-       public static long getNodeApproxSize(Node node) {
-               long curNodeSize = 0;
-               try {
-                       PropertyIterator pi = node.getProperties();
-                       while (pi.hasNext()) {
-                               Property prop = pi.nextProperty();
-                               if (prop.isMultiple()) {
-                                       int nb = prop.getLengths().length;
-                                       for (int i = 0; i < nb; i++) {
-                                               curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0);
-                                       }
-                               } else
-                                       curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
-                       }
-
-                       NodeIterator ni = node.getNodes();
-                       while (ni.hasNext())
-                               curNodeSize += getNodeApproxSize(ni.nextNode());
-                       return curNodeSize;
-               } catch (RepositoryException re) {
-                       throw new JcrException("Unexpected error while recursively determining node size.", re);
-               }
-       }
-
-       /*
-        * SECURITY
-        */
-
-       /**
-        * Convenience method for adding a single privilege to a principal (user or
-        * role), typically jcr:all
-        */
-       public synchronized static void addPrivilege(Session session, String path, String principal, String privilege)
-                       throws RepositoryException {
-               List<Privilege> privileges = new ArrayList<Privilege>();
-               privileges.add(session.getAccessControlManager().privilegeFromName(privilege));
-               addPrivileges(session, path, new SimplePrincipal(principal), privileges);
-       }
-
-       /**
-        * Add privileges on a path to a {@link Principal}. The path must already exist.
-        * Session is saved. Synchronized to prevent concurrent modifications of the
-        * same node.
-        */
-       public synchronized static Boolean addPrivileges(Session session, String path, Principal principal,
-                       List<Privilege> privs) throws RepositoryException {
-               // make sure the session is in line with the persisted state
-               session.refresh(false);
-               AccessControlManager acm = session.getAccessControlManager();
-               AccessControlList acl = getAccessControlList(acm, path);
-
-               accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                       Principal currentPrincipal = ace.getPrincipal();
-                       if (currentPrincipal.getName().equals(principal.getName())) {
-                               Privilege[] currentPrivileges = ace.getPrivileges();
-                               if (currentPrivileges.length != privs.size())
-                                       break accessControlEntries;
-                               for (int i = 0; i < currentPrivileges.length; i++) {
-                                       Privilege currP = currentPrivileges[i];
-                                       Privilege p = privs.get(i);
-                                       if (!currP.getName().equals(p.getName())) {
-                                               break accessControlEntries;
-                                       }
-                               }
-                               return false;
-                       }
-               }
-
-               Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
-               acl.addAccessControlEntry(principal, privileges);
-               acm.setPolicy(path, acl);
-//             if (log.isDebugEnabled()) {
-//                     StringBuffer privBuf = new StringBuffer();
-//                     for (Privilege priv : privs)
-//                             privBuf.append(priv.getName());
-//                     log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '"
-//                                     + session.getWorkspace().getName() + "'");
-//             }
-               session.refresh(true);
-               session.save();
-               return true;
-       }
-
-       /**
-        * Gets the first available access control list for this path, throws exception
-        * if not found
-        */
-       public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path)
-                       throws RepositoryException {
-               // search for an access control list
-               AccessControlList acl = null;
-               AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path);
-               applicablePolicies: if (policyIterator.hasNext()) {
-                       while (policyIterator.hasNext()) {
-                               AccessControlPolicy acp = policyIterator.nextAccessControlPolicy();
-                               if (acp instanceof AccessControlList) {
-                                       acl = ((AccessControlList) acp);
-                                       break applicablePolicies;
-                               }
-                       }
-               } else {
-                       AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
-                       existingPolicies: for (AccessControlPolicy acp : existingPolicies) {
-                               if (acp instanceof AccessControlList) {
-                                       acl = ((AccessControlList) acp);
-                                       break existingPolicies;
-                               }
-                       }
-               }
-               if (acl != null)
-                       return acl;
-               else
-                       throw new IllegalArgumentException("ACL not found at " + path);
-       }
-
-       /** Clear authorizations for a user at this path */
-       public synchronized static void clearAccessControList(Session session, String path, String username)
-                       throws RepositoryException {
-               AccessControlManager acm = session.getAccessControlManager();
-               AccessControlList acl = getAccessControlList(acm, path);
-               for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                       if (ace.getPrincipal().getName().equals(username)) {
-                               acl.removeAccessControlEntry(ace);
-                       }
-               }
-               // the new access control list must be applied otherwise this call:
-               // acl.removeAccessControlEntry(ace); has no effect
-               acm.setPolicy(path, acl);
-               session.refresh(true);
-               session.save();
-       }
-
-       /*
-        * FILES UTILITIES
-        */
-       /**
-        * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
-        */
-       public static Node mkfolders(Session session, String path) {
-               return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false);
-       }
-
-       /**
-        * Copy only nt:folder and nt:file, without their additional types and
-        * properties.
-        * 
-        * @param recursive if true copies folders as well, otherwise only first level
-        *                  files
-        * @return how many files were copied
-        */
-       public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) {
-               long count = 0l;
-
-               // Binary binary = null;
-               // InputStream in = null;
-               try {
-                       NodeIterator fromChildren = fromNode.getNodes();
-                       children: while (fromChildren.hasNext()) {
-                               if (monitor != null && monitor.isCanceled())
-                                       throw new IllegalStateException("Copy cancelled before it was completed");
-
-                               Node fromChild = fromChildren.nextNode();
-                               String fileName = fromChild.getName();
-                               if (fromChild.isNodeType(NodeType.NT_FILE)) {
-                                       if (onlyAdd && toNode.hasNode(fileName)) {
-                                               monitor.subTask("Skip existing " + fileName);
-                                               continue children;
-                                       }
-
-                                       if (monitor != null)
-                                               monitor.subTask("Copy " + fileName);
-                                       try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA));
-                                                       InputStream in = binary.getStream();) {
-                                               copyStreamAsFile(toNode, fileName, in);
-                                       } catch (IOException e) {
-                                               throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e);
-                                       }
-
-                                       // save session
-                                       toNode.getSession().save();
-                                       count++;
-
-//                                     if (log.isDebugEnabled())
-//                                             log.debug("Copied file " + fromChild.getPath());
-                                       if (monitor != null)
-                                               monitor.worked(1);
-                               } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) {
-                                       Node toChildFolder;
-                                       if (toNode.hasNode(fileName)) {
-                                               toChildFolder = toNode.getNode(fileName);
-                                               if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
-                                                       throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder");
-                                       } else {
-                                               toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER);
-
-                                               // save session
-                                               toNode.getSession().save();
-                                       }
-                                       count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd);
-                               }
-                       }
-                       return count;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e);
-               } finally {
-                       // in case there was an exception
-                       // IOUtils.closeQuietly(in);
-                       // closeQuietly(binary);
-               }
-       }
-
-       /**
-        * Iteratively count all file nodes in subtree, inefficient but can be useful
-        * when query are poorly supported, such as in remoting.
-        */
-       public static Long countFiles(Node node) {
-               Long localCount = 0l;
-               try {
-                       for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
-                               Node child = nit.nextNode();
-                               if (child.isNodeType(NodeType.NT_FOLDER))
-                                       localCount = localCount + countFiles(child);
-                               else if (child.isNodeType(NodeType.NT_FILE))
-                                       localCount = localCount + 1;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot count all children of " + node, e);
-               }
-               return localCount;
-       }
-
-       /**
-        * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is
-        * NOT saved.
-        * 
-        * @return the created file node
-        */
-       @Deprecated
-       public static Node copyFile(Node folderNode, File file) {
-               try (InputStream in = new FileInputStream(file)) {
-                       return copyStreamAsFile(folderNode, file.getName(), in);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e);
-               }
-       }
-
-       /** Copy bytes as an nt:file */
-       public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) {
-               // InputStream in = null;
-               try (InputStream in = new ByteArrayInputStream(bytes)) {
-                       // in = new ByteArrayInputStream(bytes);
-                       return copyStreamAsFile(folderNode, fileName, in);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e);
-                       // } finally {
-                       // IOUtils.closeQuietly(in);
-               }
-       }
-
-       /**
-        * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is
-        * NOT saved.
-        * 
-        * @return the created file node
-        */
-       public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) {
-               Binary binary = null;
-               try {
-                       Node fileNode;
-                       Node contentNode;
-                       if (folderNode.hasNode(fileName)) {
-                               fileNode = folderNode.getNode(fileName);
-                               if (!fileNode.isNodeType(NodeType.NT_FILE))
-                                       throw new IllegalArgumentException(fileNode + " is not of type nt:file");
-                               // we assume that the content node is already there
-                               contentNode = fileNode.getNode(Node.JCR_CONTENT);
-                       } else {
-                               fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
-                               contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
-                       }
-                       binary = contentNode.getSession().getValueFactory().createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-                       updateLastModified(contentNode);
-                       return fileNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e);
-               } finally {
-                       closeQuietly(binary);
-               }
-       }
-
-       /** Read an an nt:file as an {@link InputStream}. */
-       public static InputStream getFileAsStream(Node fileNode) throws RepositoryException {
-               return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream();
-       }
-
-       /**
-        * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this
-        * file node.
-        */
-       public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException {
-               Node contentNode = fileNode.getNode(Node.JCR_CONTENT);
-               if (mimeType != null)
-                       contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
-               if (encoding != null)
-                       contentNode.setProperty(Property.JCR_ENCODING, encoding);
-               // TODO remove properties if args are null?
-       }
-
-       public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) {
-               try {
-                       Files.createDirectories(targetDir);
-                       for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) {
-                               Node node = nit.nextNode();
-                               if (node.isNodeType(NodeType.NT_FILE)) {
-                                       Path filePath = targetDir.resolve(node.getName());
-                                       try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) {
-                                               IOUtils.copy(in, out);
-                                       }
-                               } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) {
-                                       Path dirPath = targetDir.resolve(node.getName());
-                                       copyFilesToFs(node, dirPath, true);
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e);
-               }
-       }
-
-       /**
-        * Computes the checksum of an nt:file.
-        * 
-        * @deprecated use separate digest utilities
-        */
-       @Deprecated
-       public static String checksumFile(Node fileNode, String algorithm) {
-               try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()
-                               .getStream()) {
-                       return digest(algorithm, in);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e);
-               }
-       }
-
-       @Deprecated
-       private static String digest(String algorithm, InputStream in) {
-               final Integer byteBufferCapacity = 100 * 1024;// 100 KB
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       byte[] buffer = new byte[byteBufferCapacity];
-                       int read = 0;
-                       while ((read = in.read(buffer)) > 0) {
-                               digest.update(buffer, 0, read);
-                       }
-
-                       byte[] checksum = digest.digest();
-                       String res = encodeHexString(checksum);
-                       return res;
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot digest with algorithm " + algorithm, e);
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               }
-       }
-
-       /**
-        * From
-        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
-        * -a-hex-string-in-java
-        */
-       @Deprecated
-       private static String encodeHexString(byte[] bytes) {
-               final char[] hexArray = "0123456789abcdef".toCharArray();
-               char[] hexChars = new char[bytes.length * 2];
-               for (int j = 0; j < bytes.length; j++) {
-                       int v = bytes[j] & 0xFF;
-                       hexChars[j * 2] = hexArray[v >>> 4];
-                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
-               }
-               return new String(hexChars);
-       }
-
-       /** Export a subtree as a compact XML without namespaces. */
-       public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException {
-               sb.append('<');
-               String nodeName = node.getName();
-               int colIndex = nodeName.indexOf(':');
-               if (colIndex > 0) {
-                       nodeName = nodeName.substring(colIndex + 1);
-               }
-               sb.append(nodeName);
-               PropertyIterator pit = node.getProperties();
-               properties: while (pit.hasNext()) {
-                       Property p = pit.nextProperty();
-                       // skip multiple properties
-                       if (p.isMultiple())
-                               continue properties;
-                       String propertyName = p.getName();
-                       int pcolIndex = propertyName.indexOf(':');
-                       // skip properties with namespaces
-                       if (pcolIndex > 0)
-                               continue properties;
-                       // skip binaries
-                       if (p.getType() == PropertyType.BINARY) {
-                               continue properties;
-                               // TODO retrieve identifier?
-                       }
-                       sb.append(' ');
-                       sb.append(propertyName);
-                       sb.append('=');
-                       sb.append('\"').append(p.getString()).append('\"');
-               }
-
-               if (node.hasNodes()) {
-                       sb.append('>');
-                       NodeIterator children = node.getNodes();
-                       while (children.hasNext()) {
-                               toSimpleXml(children.nextNode(), sb);
-                       }
-                       sb.append("</");
-                       sb.append(nodeName);
-                       sb.append('>');
-               } else {
-                       sb.append("/>");
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java
deleted file mode 100644 (file)
index 666b259..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.argeo.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-/** Uilities around the JCR extensions. */
-public class JcrxApi {
-       public final static String MD5 = "MD5";
-       public final static String SHA1 = "SHA1";
-       public final static String SHA256 = "SHA-256";
-       public final static String SHA512 = "SHA-512";
-
-       public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
-       public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
-       public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
-       public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
-
-       public final static int LENGTH_MD5 = EMPTY_MD5.length();
-       public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
-       public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
-       public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
-
-       /*
-        * XML
-        */
-       /**
-        * Get the XML text of this child node.
-        */
-       public static String getXmlValue(Node node, String name) {
-               try {
-                       if (!node.hasNode(name))
-                               return null;
-                       Node child = node.getNode(name);
-                       return getXmlValue(child);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get " + name + " as XML text", e);
-               }
-       }
-
-       /**
-        * Get the XML text of this node.
-        */
-       public static String getXmlValue(Node node) {
-               try {
-                       if (!node.hasNode(Jcr.JCR_XMLTEXT))
-                               return null;
-                       Node xmlText = node.getNode(Jcr.JCR_XMLTEXT);
-                       if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS))
-                               throw new IllegalArgumentException(
-                                               "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property");
-                       return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get " + node + " as XML text", e);
-               }
-       }
-
-       /**
-        * Set as a subnode which will be exported as an XML element.
-        */
-       public static void setXmlValue(Node node, String name, String value) {
-               try {
-                       if (node.hasNode(name)) {
-                               Node child = node.getNode(name);
-                               setXmlValue(node, child, value);
-                       } else
-                               node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
-                                               .setProperty(Jcr.JCR_XMLCHARACTERS, value);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set " + name + " as XML text", e);
-               }
-       }
-
-       public static void setXmlValue(Node node, Node child, String value) {
-               try {
-                       if (!child.hasNode(Jcr.JCR_XMLTEXT))
-                               child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT);
-                       child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set " + child + " as XML text", e);
-               }
-       }
-
-       /**
-        * Add a checksum replacing the one which was previously set with the same
-        * length.
-        */
-       public static void addChecksum(Node node, String checksum) {
-               try {
-                       if (!node.hasProperty(JcrxName.JCRX_SUM)) {
-                               node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
-                               return;
-                       } else {
-                               int stringLength = checksum.length();
-                               Property property = node.getProperty(JcrxName.JCRX_SUM);
-                               List<Value> values = Arrays.asList(property.getValues());
-                               Integer indexToRemove = null;
-                               values: for (int i = 0; i < values.size(); i++) {
-                                       Value value = values.get(i);
-                                       if (value.getString().length() == stringLength) {
-                                               indexToRemove = i;
-                                               break values;
-                                       }
-                               }
-                               if (indexToRemove != null)
-                                       values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
-                               else
-                                       values.add(0, node.getSession().getValueFactory().createValue(checksum));
-                               property.setValue(values.toArray(new Value[values.size()]));
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set checksum on " + node, e);
-               }
-       }
-
-       /** Replace all checksums. */
-       public static void setChecksums(Node node, List<String> checksums) {
-               try {
-                       node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set checksums on " + node, e);
-               }
-       }
-
-       /** Replace all checksums. */
-       public static List<String> getChecksums(Node node) {
-               try {
-                       List<String> res = new ArrayList<>();
-                       if (!node.hasProperty(JcrxName.JCRX_SUM))
-                               return res;
-                       Property property = node.getProperty(JcrxName.JCRX_SUM);
-                       for (Value value : property.getValues()) {
-                               res.add(value.getString());
-                       }
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get checksums from " + node, e);
-               }
-       }
-
-//     /** Replace all checksums with this single one. */
-//     public static void setChecksum(Node node, String checksum) {
-//             setChecksums(node, Collections.singletonList(checksum));
-//     }
-
-       /** Retrieves the checksum with this algorithm, or null if not found. */
-       public static String getChecksum(Node node, String algorithm) {
-               int stringLength;
-               switch (algorithm) {
-               case MD5:
-                       stringLength = LENGTH_MD5;
-                       break;
-               case SHA1:
-                       stringLength = LENGTH_SHA1;
-                       break;
-               case SHA256:
-                       stringLength = LENGTH_SHA256;
-                       break;
-               case SHA512:
-                       stringLength = LENGTH_SHA512;
-                       break;
-               default:
-                       throw new IllegalArgumentException("Unkown algorithm " + algorithm);
-               }
-               return getChecksum(node, stringLength);
-       }
-
-       /** Retrieves the checksum with this string length, or null if not found. */
-       public static String getChecksum(Node node, int stringLength) {
-               try {
-                       if (!node.hasProperty(JcrxName.JCRX_SUM))
-                               return null;
-                       Property property = node.getProperty(JcrxName.JCRX_SUM);
-                       for (Value value : property.getValues()) {
-                               String str = value.getString();
-                               if (str.length() == stringLength)
-                                       return str;
-                       }
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get checksum for " + node, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java
deleted file mode 100644 (file)
index 9dd43ad..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.jcr;
-
-/** Names declared by the JCR extensions. */
-public interface JcrxName {
-       /** The multiple property holding various coherent checksums. */
-       public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java
deleted file mode 100644 (file)
index 0cbad33..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.jcr;
-
-/** Node types declared by the JCR extensions. */
-public interface JcrxType {
-       /**
-        * Node type for an XML value, which will be serialized in XML as an element
-        * containing text.
-        */
-       public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue";
-
-       /** Node type for the node containing the text. */
-       public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
-
-       /** Mixin node type for a set of checksums. */
-       public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java
deleted file mode 100644 (file)
index 71e76fe..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.argeo.jcr;
-
-import javax.jcr.Value;
-
-/** The result of the comparison of two JCR properties. */
-public class PropertyDiff {
-       public final static Integer MODIFIED = 0;
-       public final static Integer ADDED = 1;
-       public final static Integer REMOVED = 2;
-
-       private final Integer type;
-       private final String relPath;
-       private final Value referenceValue;
-       private final Value newValue;
-
-       public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) {
-               super();
-
-               if (type == MODIFIED) {
-                       if (referenceValue == null || newValue == null)
-                               throw new IllegalArgumentException("Reference and new values must be specified.");
-               } else if (type == ADDED) {
-                       if (referenceValue != null || newValue == null)
-                               throw new IllegalArgumentException("New value and only it must be specified.");
-               } else if (type == REMOVED) {
-                       if (referenceValue == null || newValue != null)
-                               throw new IllegalArgumentException("Reference value and only it must be specified.");
-               } else {
-                       throw new IllegalArgumentException("Unkown diff type " + type);
-               }
-
-               if (relPath == null)
-                       throw new IllegalArgumentException("Relative path must be specified");
-
-               this.type = type;
-               this.relPath = relPath;
-               this.referenceValue = referenceValue;
-               this.newValue = newValue;
-       }
-
-       public Integer getType() {
-               return type;
-       }
-
-       public String getRelPath() {
-               return relPath;
-       }
-
-       public Value getReferenceValue() {
-               return referenceValue;
-       }
-
-       public Value getNewValue() {
-               return newValue;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java
deleted file mode 100644 (file)
index 4f42f2d..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.argeo.jcr;
-
-import java.security.Principal;
-
-/** Canonical implementation of a {@link Principal} */
-class SimplePrincipal implements Principal {
-       private final String name;
-
-       public SimplePrincipal(String name) {
-               if (name == null)
-                       throw new IllegalArgumentException("Principal name cannot be null");
-               this.name = name;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj == null)
-                       return false;
-               if (obj instanceof Principal)
-                       return name.equals((((Principal) obj).getName()));
-               return name.equals(obj.toString());
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return new SimplePrincipal(name);
-       }
-
-       @Override
-       public String toString() {
-               return name;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
deleted file mode 100644 (file)
index 2208627..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-package org.argeo.jcr;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.argeo.api.cms.CmsLog;
-
-/** Proxy JCR sessions and attach them to calling threads. */
-@Deprecated
-public abstract class ThreadBoundJcrSessionFactory {
-       private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class);
-
-       private Repository repository;
-       /** can be injected as list, only used if repository is null */
-       private List<Repository> repositories;
-
-       private ThreadLocal<Session> session = new ThreadLocal<Session>();
-       private final Session proxiedSession;
-       /** If workspace is null, default will be used. */
-       private String workspace = null;
-
-       private String defaultUsername = "demo";
-       private String defaultPassword = "demo";
-       private Boolean forceDefaultCredentials = false;
-
-       private boolean active = true;
-
-       // monitoring
-       private final List<Thread> threads = Collections.synchronizedList(new ArrayList<Thread>());
-       private final Map<Long, Session> activeSessions = Collections.synchronizedMap(new HashMap<Long, Session>());
-       private MonitoringThread monitoringThread;
-
-       public ThreadBoundJcrSessionFactory() {
-               Class<?>[] interfaces = { Session.class };
-               proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(),
-                               interfaces, new JcrSessionInvocationHandler());
-       }
-
-       /** Logs in to the repository using various strategies. */
-       protected synchronized Session login() {
-               if (!isActive())
-                       throw new IllegalStateException("Thread bound session factory inactive");
-
-               // discard session previously attached to this thread
-               Thread thread = Thread.currentThread();
-               if (activeSessions.containsKey(thread.getId())) {
-                       Session oldSession = activeSessions.remove(thread.getId());
-                       oldSession.logout();
-                       session.remove();
-               }
-
-               Session newSession = null;
-               // first try to login without credentials, assuming the underlying login
-               // module will have dealt with authentication (typically using Spring
-               // Security)
-               if (!forceDefaultCredentials)
-                       try {
-                               newSession = repository().login(workspace);
-                       } catch (LoginException e1) {
-                               log.warn("Cannot login without credentials: " + e1.getMessage());
-                               // invalid credentials, go to the next step
-                       } catch (RepositoryException e1) {
-                               // other kind of exception, fail
-                               throw new JcrException("Cannot log in to repository", e1);
-                       }
-
-               // log using default username / password (useful for testing purposes)
-               if (newSession == null)
-                       try {
-                               SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray());
-                               newSession = repository().login(sc, workspace);
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot log in to repository", e);
-                       }
-
-               session.set(newSession);
-               // Log and monitor new session
-               if (log.isTraceEnabled())
-                       log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID());
-
-               // monitoring
-               activeSessions.put(thread.getId(), newSession);
-               threads.add(thread);
-               return newSession;
-       }
-
-       public Object getObject() {
-               return proxiedSession;
-       }
-
-       public void init() throws Exception {
-               // log.error("SHOULD NOT BE USED ANYMORE");
-               monitoringThread = new MonitoringThread();
-               monitoringThread.start();
-       }
-
-       public void dispose() throws Exception {
-               // if (activeSessions.size() == 0)
-               // return;
-
-               if (log.isTraceEnabled())
-                       log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions...");
-
-               deactivate();
-               for (Session sess : activeSessions.values()) {
-                       JcrUtils.logoutQuietly(sess);
-               }
-               activeSessions.clear();
-       }
-
-       protected Boolean isActive() {
-               return active;
-       }
-
-       protected synchronized void deactivate() {
-               active = false;
-               notifyAll();
-       }
-
-       protected synchronized void removeSession(Thread thread) {
-               if (!isActive())
-                       return;
-               activeSessions.remove(thread.getId());
-               threads.remove(thread);
-       }
-
-       protected synchronized void cleanDeadThreads() {
-               if (!isActive())
-                       return;
-               Iterator<Thread> it = threads.iterator();
-               while (it.hasNext()) {
-                       Thread thread = it.next();
-                       if (!thread.isAlive() && isActive()) {
-                               if (activeSessions.containsKey(thread.getId())) {
-                                       Session session = activeSessions.get(thread.getId());
-                                       activeSessions.remove(thread.getId());
-                                       session.logout();
-                                       if (log.isTraceEnabled())
-                                               log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread "
-                                                               + thread.getId());
-                               }
-                               it.remove();
-                       }
-               }
-               try {
-                       wait(1000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-       }
-
-       public Class<? extends Session> getObjectType() {
-               return Session.class;
-       }
-
-       public boolean isSingleton() {
-               return true;
-       }
-
-       /**
-        * Called before a method is actually called, allowing to check the session or
-        * re-login it (e.g. if authentication has changed). The default implementation
-        * returns the session.
-        */
-       protected Session preCall(Session session) {
-               return session;
-       }
-
-       protected Repository repository() {
-               if (repository != null)
-                       return repository;
-               if (repositories != null) {
-                       // hardened for OSGi dynamic services
-                       Iterator<Repository> it = repositories.iterator();
-                       if (it.hasNext())
-                               return it.next();
-               }
-               throw new IllegalStateException("No repository injected");
-       }
-
-       // /** Useful for declarative registration of OSGi services (blueprint) */
-       // public void register(Repository repository, Map<?, ?> params) {
-       // this.repository = repository;
-       // }
-       //
-       // /** Useful for declarative registration of OSGi services (blueprint) */
-       // public void unregister(Repository repository, Map<?, ?> params) {
-       // this.repository = null;
-       // }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setRepositories(List<Repository> repositories) {
-               this.repositories = repositories;
-       }
-
-       public void setDefaultUsername(String defaultUsername) {
-               this.defaultUsername = defaultUsername;
-       }
-
-       public void setDefaultPassword(String defaultPassword) {
-               this.defaultPassword = defaultPassword;
-       }
-
-       public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
-               this.forceDefaultCredentials = forceDefaultCredentials;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       protected class JcrSessionInvocationHandler implements InvocationHandler {
-
-               public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException {
-                       Session threadSession = session.get();
-                       if (threadSession == null) {
-                               if ("logout".equals(method.getName()))// no need to login
-                                       return Void.TYPE;
-                               else if ("toString".equals(method.getName()))// maybe logging
-                                       return "Uninitialized Argeo thread bound JCR session";
-                               threadSession = login();
-                       }
-
-                       preCall(threadSession);
-                       Object ret;
-                       try {
-                               ret = method.invoke(threadSession, args);
-                       } catch (InvocationTargetException e) {
-                               Throwable cause = e.getCause();
-                               if (cause instanceof RepositoryException)
-                                       throw (RepositoryException) cause;
-                               else
-                                       throw cause;
-                       }
-                       if ("logout".equals(method.getName())) {
-                               session.remove();
-                               Thread thread = Thread.currentThread();
-                               removeSession(thread);
-                               if (log.isTraceEnabled())
-                                       log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread "
-                                                       + thread.getId());
-                       }
-                       return ret;
-               }
-       }
-
-       /** Monitors registered thread in order to clean up dead ones. */
-       private class MonitoringThread extends Thread {
-
-               public MonitoringThread() {
-                       super("ThreadBound JCR Session Monitor");
-               }
-
-               @Override
-               public void run() {
-                       while (isActive()) {
-                               cleanDeadThreads();
-                       }
-               }
-
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java
deleted file mode 100644 (file)
index dab5554..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.jcr;
-
-import java.util.Calendar;
-import java.util.Map;
-
-/**
- * Generic Object that enables the creation of history reports based on a JCR
- * versionable node. userId and creation date are added to the map of
- * PropertyDiff.
- * 
- * These two fields might be null
- * 
- */
-public class VersionDiff {
-
-       private String userId;
-       private Map<String, PropertyDiff> diffs;
-       private Calendar updateTime;
-
-       public VersionDiff(String userId, Calendar updateTime,
-                       Map<String, PropertyDiff> diffs) {
-               this.userId = userId;
-               this.updateTime = updateTime;
-               this.diffs = diffs;
-       }
-
-       public String getUserId() {
-               return userId;
-       }
-
-       public Map<String, PropertyDiff> getDiffs() {
-               return diffs;
-       }
-
-       public Calendar getUpdateTime() {
-               return updateTime;
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java
deleted file mode 100644 (file)
index d6550fe..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.JcrUtils;
-
-/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */
-public class BinaryChannel implements SeekableByteChannel {
-       private final Node file;
-       private Binary binary;
-       private boolean open = true;
-
-       private long position = 0;
-
-       private FileChannel fc = null;
-
-       public BinaryChannel(Node file, Path path) throws RepositoryException, IOException {
-               this.file = file;
-               Session session = file.getSession();
-               synchronized (session) {
-                       if (file.isNodeType(NodeType.NT_FILE)) {
-                               if (file.hasNode(Node.JCR_CONTENT)) {
-                                       Node data = file.getNode(Property.JCR_CONTENT);
-                                       this.binary = data.getProperty(Property.JCR_DATA).getBinary();
-                               } else {
-                                       Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
-                                       data.addMixin(NodeType.MIX_LAST_MODIFIED);
-                                       try (InputStream in = new ByteArrayInputStream(new byte[0])) {
-                                               this.binary = data.getSession().getValueFactory().createBinary(in);
-                                       }
-                                       data.setProperty(Property.JCR_DATA, this.binary);
-
-                                       // MIME type
-                                       String mime = Files.probeContentType(path);
-                                       // String mime = fileTypeMap.getContentType(file.getName());
-                                       data.setProperty(Property.JCR_MIMETYPE, mime);
-
-                                       session.refresh(true);
-                                       session.save();
-                                       session.notifyAll();
-                               }
-                       } else {
-                               throw new IllegalArgumentException(
-                                               "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")");
-                       }
-               }
-       }
-
-       @Override
-       public synchronized boolean isOpen() {
-               return open;
-       }
-
-       @Override
-       public synchronized void close() throws IOException {
-               if (isModified()) {
-                       Binary newBinary = null;
-                       try {
-                               Session session = file.getSession();
-                               synchronized (session) {
-                                       fc.position(0);
-                                       InputStream in = Channels.newInputStream(fc);
-                                       newBinary = session.getValueFactory().createBinary(in);
-                                       file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary);
-                                       session.refresh(true);
-                                       session.save();
-                                       open = false;
-                                       session.notifyAll();
-                               }
-                       } catch (RepositoryException e) {
-                               throw new IOException("Cannot close " + file, e);
-                       } finally {
-                               JcrUtils.closeQuietly(newBinary);
-                               // IOUtils.closeQuietly(fc);
-                               if (fc != null) {
-                                       fc.close();
-                               }
-                       }
-               } else {
-                       clearReadState();
-                       open = false;
-               }
-       }
-
-       @Override
-       public int read(ByteBuffer dst) throws IOException {
-               if (isModified()) {
-                       return fc.read(dst);
-               } else {
-
-                       try {
-                               int read;
-                               byte[] arr = dst.array();
-                               read = binary.read(arr, position);
-
-                               if (read != -1)
-                                       position = position + read;
-                               return read;
-                       } catch (RepositoryException e) {
-                               throw new IOException("Cannot read into buffer", e);
-                       }
-               }
-       }
-
-       @Override
-       public int write(ByteBuffer src) throws IOException {
-               int written = getFileChannel().write(src);
-               return written;
-       }
-
-       @Override
-       public long position() throws IOException {
-               if (isModified())
-                       return getFileChannel().position();
-               else
-                       return position;
-       }
-
-       @Override
-       public SeekableByteChannel position(long newPosition) throws IOException {
-               if (isModified()) {
-                       getFileChannel().position(position);
-               } else {
-                       this.position = newPosition;
-               }
-               return this;
-       }
-
-       @Override
-       public long size() throws IOException {
-               if (isModified()) {
-                       return getFileChannel().size();
-               } else {
-                       try {
-                               return binary.getSize();
-                       } catch (RepositoryException e) {
-                               throw new IOException("Cannot get size", e);
-                       }
-               }
-       }
-
-       @Override
-       public SeekableByteChannel truncate(long size) throws IOException {
-               getFileChannel().truncate(size);
-               return this;
-       }
-
-       private FileChannel getFileChannel() throws IOException {
-               try {
-                       if (fc == null) {
-                               Path tempPath = Files.createTempFile(getClass().getSimpleName(), null);
-                               fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ,
-                                               StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE);
-                               ReadableByteChannel readChannel = Channels.newChannel(binary.getStream());
-                               fc.transferFrom(readChannel, 0, binary.getSize());
-                               clearReadState();
-                       }
-                       return fc;
-               } catch (RepositoryException e) {
-                       throw new IOException("Cannot get temp file channel", e);
-               }
-       }
-
-       private boolean isModified() {
-               return fc != null;
-       }
-
-       private void clearReadState() {
-               position = -1;
-               JcrUtils.closeQuietly(binary);
-               binary = null;
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java
deleted file mode 100644 (file)
index 7c9711b..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-package org.argeo.jcr.fs;
-
-import static javax.jcr.Property.JCR_CREATED;
-import static javax.jcr.Property.JCR_LAST_MODIFIED;
-
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.JcrUtils;
-
-public class JcrBasicfileAttributes implements NodeFileAttributes {
-       private final Node node;
-
-       private final static FileTime EPOCH = FileTime.fromMillis(0);
-
-       public JcrBasicfileAttributes(Node node) {
-               if (node == null)
-                       throw new JcrFsException("Node underlying the attributes cannot be null");
-               this.node = node;
-       }
-
-       @Override
-       public FileTime lastModifiedTime() {
-               try {
-                       if (node.hasProperty(JCR_LAST_MODIFIED)) {
-                               Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
-                               return FileTime.from(instant);
-                       } else if (node.hasProperty(JCR_CREATED)) {
-                               Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
-                               return FileTime.from(instant);
-                       }
-//                     if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-//                             Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant();
-//                             return FileTime.from(instant);
-//                     }
-                       return EPOCH;
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get last modified time", e);
-               }
-       }
-
-       @Override
-       public FileTime lastAccessTime() {
-               return lastModifiedTime();
-       }
-
-       @Override
-       public FileTime creationTime() {
-               try {
-                       if (node.hasProperty(JCR_CREATED)) {
-                               Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
-                               return FileTime.from(instant);
-                       } else if (node.hasProperty(JCR_LAST_MODIFIED)) {
-                               Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant();
-                               return FileTime.from(instant);
-                       }
-//                     if (node.isNodeType(NodeType.MIX_CREATED)) {
-//                             Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant();
-//                             return FileTime.from(instant);
-//                     }
-                       return EPOCH;
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get creation time", e);
-               }
-       }
-
-       @Override
-       public boolean isRegularFile() {
-               try {
-                       return node.isNodeType(NodeType.NT_FILE);
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot check if regular file", e);
-               }
-       }
-
-       @Override
-       public boolean isDirectory() {
-               try {
-                       if (node.isNodeType(NodeType.NT_FOLDER))
-                               return true;
-                       // all other non file nodes
-                       return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE));
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot check if directory", e);
-               }
-       }
-
-       @Override
-       public boolean isSymbolicLink() {
-               try {
-                       return node.isNodeType(NodeType.NT_LINKED_FILE);
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot check if linked file", e);
-               }
-       }
-
-       @Override
-       public boolean isOther() {
-               return !(isDirectory() || isRegularFile() || isSymbolicLink());
-       }
-
-       @Override
-       public long size() {
-               if (isRegularFile()) {
-                       Binary binary = null;
-                       try {
-                               binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary();
-                               return binary.getSize();
-                       } catch (RepositoryException e) {
-                               throw new JcrFsException("Cannot check size", e);
-                       } finally {
-                               JcrUtils.closeQuietly(binary);
-                       }
-               }
-               return -1;
-       }
-
-       @Override
-       public Object fileKey() {
-               try {
-                       return node.getIdentifier();
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get identifier", e);
-               }
-       }
-
-       @Override
-       public Node getNode() {
-               return node;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java
deleted file mode 100644 (file)
index 4b32981..0000000
+++ /dev/null
@@ -1,252 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.FileSystem;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.WatchService;
-import java.nio.file.attribute.UserPrincipalLookupService;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.acr.fs.AbstractFsStore;
-import org.argeo.api.acr.fs.AbstractFsSystem;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrFileSystem extends AbstractFsSystem<WorkspaceFileStore> {
-       private final JcrFileSystemProvider provider;
-
-       private final Repository repository;
-       private Session session;
-       private WorkspaceFileStore baseFileStore;
-
-       private Map<String, WorkspaceFileStore> mounts = new TreeMap<>();
-
-       private String userHomePath = null;
-
-       @Deprecated
-       public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException {
-               super();
-               this.provider = provider;
-               baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
-               this.session = session;
-//             Node userHome = provider.getUserHome(session);
-//             if (userHome != null)
-//                     try {
-//                             userHomePath = userHome.getPath();
-//                     } catch (RepositoryException e) {
-//                             throw new IOException("Cannot retrieve user home path", e);
-//                     }
-               this.repository = null;
-       }
-
-       public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException {
-               this(provider, repository, null);
-       }
-
-       public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials)
-                       throws IOException {
-               super();
-               this.provider = provider;
-               this.repository = repository;
-               try {
-                       this.session = credentials == null ? repository.login() : repository.login(credentials);
-                       baseFileStore = new WorkspaceFileStore(null, session.getWorkspace());
-                       workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) {
-                               if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
-                                       continue workspaces;// do not mount base
-                               if (workspaceName.equals("security")) {
-                                       continue workspaces;// do not mount security workspace
-                                       // TODO make it configurable
-                               }
-                               Session mountSession = credentials == null ? repository.login(workspaceName)
-                                               : repository.login(credentials, workspaceName);
-                               String mountPath = JcrPath.separator + workspaceName;
-                               mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace()));
-                       }
-               } catch (RepositoryException e) {
-                       throw new IOException("Cannot initialise file system", e);
-               }
-
-               Node userHome = provider.getUserHome(repository);
-               if (userHome != null)
-                       try {
-                               userHomePath = toFsPath(userHome);
-                       } catch (RepositoryException e) {
-                               throw new IOException("Cannot retrieve user home path", e);
-                       } finally {
-                               JcrUtils.logoutQuietly(Jcr.session(userHome));
-                       }
-       }
-
-       public String toFsPath(Node node) throws RepositoryException {
-               return getFileStore(node).toFsPath(node);
-       }
-
-       /** Whether this node should be skipped in directory listings */
-       public boolean skipNode(Node node) throws RepositoryException {
-               if (node.isNodeType(NodeType.NT_HIERARCHY_NODE))
-                       return false;
-               return true;
-       }
-
-       public String getUserHomePath() {
-               return userHomePath;
-       }
-
-       public WorkspaceFileStore getFileStore(String path) {
-               WorkspaceFileStore res = baseFileStore;
-               for (String mountPath : mounts.keySet()) {
-                       if (path.equals(mountPath))
-                               return mounts.get(mountPath);
-                       if (path.startsWith(mountPath + JcrPath.separator)) {
-                               res = mounts.get(mountPath);
-                               // we keep the last one
-                       }
-               }
-               assert res != null;
-               return res;
-       }
-
-       public WorkspaceFileStore getFileStore(Node node) throws RepositoryException {
-               String workspaceName = node.getSession().getWorkspace().getName();
-               if (workspaceName.equals(baseFileStore.getWorkspace().getName()))
-                       return baseFileStore;
-               for (String mountPath : mounts.keySet()) {
-                       WorkspaceFileStore fileStore = mounts.get(mountPath);
-                       if (workspaceName.equals(fileStore.getWorkspace().getName()))
-                               return fileStore;
-               }
-               throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName);
-       }
-
-       public Iterator<JcrPath> listDirectMounts(Path base) {
-               String baseStr = base.toString();
-               Set<JcrPath> res = new HashSet<>();
-               mounts: for (String mountPath : mounts.keySet()) {
-                       if (mountPath.equals(baseStr))
-                               continue mounts;
-                       if (mountPath.startsWith(baseStr)) {
-                               JcrPath path = new JcrPath(this, mountPath);
-                               Path relPath = base.relativize(path);
-                               if (relPath.getNameCount() == 1)
-                                       res.add(path);
-                       }
-               }
-               return res.iterator();
-       }
-
-       public WorkspaceFileStore getBaseFileStore() {
-               return baseFileStore;
-       }
-
-       @Override
-       public FileSystemProvider provider() {
-               return provider;
-       }
-
-       @Override
-       public void close() throws IOException {
-               JcrUtils.logoutQuietly(session);
-               for (String mountPath : mounts.keySet()) {
-                       WorkspaceFileStore fileStore = mounts.get(mountPath);
-                       try {
-                               fileStore.close();
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                       }
-               }
-       }
-
-       @Override
-       public boolean isOpen() {
-               return session.isLive();
-       }
-
-       @Override
-       public boolean isReadOnly() {
-               return false;
-       }
-
-       @Override
-       public String getSeparator() {
-               return JcrPath.separator;
-       }
-
-       @Override
-       public Iterable<Path> getRootDirectories() {
-               Set<Path> single = new HashSet<>();
-               single.add(new JcrPath(this, JcrPath.separator));
-               return single;
-       }
-
-       @Override
-       public Iterable<FileStore> getFileStores() {
-               List<FileStore> stores = new ArrayList<>();
-               stores.add(baseFileStore);
-               stores.addAll(mounts.values());
-               return stores;
-       }
-
-       @Override
-       public Set<String> supportedFileAttributeViews() {
-               try {
-                       String[] prefixes = session.getNamespacePrefixes();
-                       Set<String> res = new HashSet<>();
-                       for (String prefix : prefixes)
-                               res.add(prefix);
-                       res.add("basic");
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot get supported file attributes views", e);
-               }
-       }
-
-       @Override
-       public Path getPath(String first, String... more) {
-               StringBuilder sb = new StringBuilder(first);
-               // TODO Make it more robust
-               for (String part : more)
-                       sb.append('/').append(part);
-               return new JcrPath(this, sb.toString());
-       }
-
-       @Override
-       public PathMatcher getPathMatcher(String syntaxAndPattern) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public UserPrincipalLookupService getUserPrincipalLookupService() {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public WatchService newWatchService() throws IOException {
-               throw new UnsupportedOperationException();
-       }
-
-//     public Session getSession() {
-//             return session;
-//     }
-
-       public Repository getRepository() {
-               return repository;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java
deleted file mode 100644 (file)
index 74d9a19..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.file.AccessMode;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryNotEmptyException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.FileStore;
-import java.nio.file.LinkOption;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.nodetype.PropertyDefinition;
-
-import org.argeo.jcr.JcrUtils;
-
-/** Operations on a {@link JcrFileSystem}. */
-public abstract class JcrFileSystemProvider extends FileSystemProvider {
-
-       @Override
-       public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
-                       throws IOException {
-               Node node = toNode(path);
-               try {
-                       if (node == null) {
-                               Node parent = toNode(path.getParent());
-                               if (parent == null)
-                                       throw new IOException("No parent directory for " + path);
-                               if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
-                                               || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
-                                       throw new IOException(path + " parent is a file");
-
-                               String fileName = path.getFileName().toString();
-                               fileName = Text.escapeIllegalJcrChars(fileName);
-                               node = parent.addNode(fileName, NodeType.NT_FILE);
-                               node.addMixin(NodeType.MIX_CREATED);
-//                             node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                       }
-                       if (!node.isNodeType(NodeType.NT_FILE))
-                               throw new UnsupportedOperationException(node + " must be a file");
-                       return new BinaryChannel(node, path);
-               } catch (RepositoryException e) {
-                       discardChanges(node);
-                       throw new IOException("Cannot read file", e);
-               }
-       }
-
-       @Override
-       public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
-               try {
-                       Node base = toNode(dir);
-                       if (base == null)
-                               throw new IOException(dir + " is not a JCR node");
-                       JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem();
-                       return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter);
-               } catch (RepositoryException e) {
-                       throw new IOException("Cannot list directory", e);
-               }
-       }
-
-       @Override
-       public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
-               Node node = toNode(dir);
-               try {
-                       if (node == null) {
-                               Node parent = toNode(dir.getParent());
-                               if (parent == null)
-                                       throw new IOException("Parent of " + dir + " does not exist");
-                               Session session = parent.getSession();
-                               synchronized (session) {
-                                       if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)
-                                                       || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE))
-                                               throw new IOException(dir + " parent is a file");
-                                       String fileName = dir.getFileName().toString();
-                                       fileName = Text.escapeIllegalJcrChars(fileName);
-                                       node = parent.addNode(fileName, NodeType.NT_FOLDER);
-                                       node.addMixin(NodeType.MIX_CREATED);
-                                       node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                                       save(session);
-                               }
-                       } else {
-                               // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
-                               // throw new FileExistsException(dir + " exists and is not a directory");
-                       }
-               } catch (RepositoryException e) {
-                       discardChanges(node);
-                       throw new IOException("Cannot create directory " + dir, e);
-               }
-       }
-
-       @Override
-       public void delete(Path path) throws IOException {
-               Node node = toNode(path);
-               try {
-                       if (node == null)
-                               throw new NoSuchFileException(path + " does not exist");
-                       Session session = node.getSession();
-                       synchronized (session) {
-                               session.refresh(false);
-                               if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
-                                       node.remove();
-                               else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) {
-                                       if (node.hasNodes())// TODO check only files
-                                               throw new DirectoryNotEmptyException(path.toString());
-                                       node.remove();
-                               }
-                               save(session);
-                       }
-               } catch (RepositoryException e) {
-                       discardChanges(node);
-                       throw new IOException("Cannot delete " + path, e);
-               }
-
-       }
-
-       @Override
-       public void copy(Path source, Path target, CopyOption... options) throws IOException {
-               Node sourceNode = toNode(source);
-               Node targetNode = toNode(target);
-               try {
-                       Session targetSession = targetNode.getSession();
-                       synchronized (targetSession) {
-                               JcrUtils.copy(sourceNode, targetNode);
-                               save(targetSession);
-                       }
-               } catch (RepositoryException e) {
-                       discardChanges(sourceNode);
-                       discardChanges(targetNode);
-                       throw new IOException("Cannot copy from " + source + " to " + target, e);
-               }
-       }
-
-       @Override
-       public void move(Path source, Path target, CopyOption... options) throws IOException {
-               JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem();
-               WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString());
-               WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString());
-               try {
-                       if (sourceStore.equals(targetStore)) {
-                               sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()),
-                                               targetStore.toJcrPath(target.toString()));
-                       } else {
-                               // TODO implement it
-                               throw new UnsupportedOperationException("Can only move paths within the same workspace.");
-                       }
-               } catch (RepositoryException e) {
-                       throw new IOException("Cannot move from " + source + " to " + target, e);
-               }
-
-//             Node sourceNode = toNode(source);
-//             try {
-//                     Session session = sourceNode.getSession();
-//                     synchronized (session) {
-//                             session.move(sourceNode.getPath(), target.toString());
-//                             save(session);
-//                     }
-//             } catch (RepositoryException e) {
-//                     discardChanges(sourceNode);
-//                     throw new IOException("Cannot move from " + source + " to " + target, e);
-//             }
-       }
-
-       @Override
-       public boolean isSameFile(Path path, Path path2) throws IOException {
-               if (path.getFileSystem() != path2.getFileSystem())
-                       return false;
-               boolean equ = path.equals(path2);
-               if (equ)
-                       return true;
-               else {
-                       try {
-                               Node node = toNode(path);
-                               Node node2 = toNode(path2);
-                               return node.isSame(node2);
-                       } catch (RepositoryException e) {
-                               throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e);
-                       }
-               }
-
-       }
-
-       @Override
-       public boolean isHidden(Path path) throws IOException {
-               return path.getFileName().toString().charAt(0) == '.';
-       }
-
-       @Override
-       public FileStore getFileStore(Path path) throws IOException {
-               JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem();
-               return fileSystem.getFileStore(path.toString());
-       }
-
-       @Override
-       public void checkAccess(Path path, AccessMode... modes) throws IOException {
-               Node node = toNode(path);
-               if (node == null)
-                       throw new NoSuchFileException(path + " does not exist");
-               // TODO check access via JCR api
-       }
-
-       @Override
-       public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings("unchecked")
-       @Override
-       public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
-                       throws IOException {
-               // TODO check if assignable
-               Node node = toNode(path);
-               if (node == null) {
-                       throw new IOException("JCR node not found for " + path);
-               }
-               return (A) new JcrBasicfileAttributes(node);
-       }
-
-       @Override
-       public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
-               try {
-                       Node node = toNode(path);
-                       String pattern = attributes.replace(',', '|');
-                       Map<String, Object> res = new HashMap<String, Object>();
-                       PropertyIterator it = node.getProperties(pattern);
-                       props: while (it.hasNext()) {
-                               Property prop = it.nextProperty();
-                               PropertyDefinition pd = prop.getDefinition();
-                               if (pd.isMultiple())
-                                       continue props;
-                               int requiredType = pd.getRequiredType();
-                               switch (requiredType) {
-                               case PropertyType.LONG:
-                                       res.put(prop.getName(), prop.getLong());
-                                       break;
-                               case PropertyType.DOUBLE:
-                                       res.put(prop.getName(), prop.getDouble());
-                                       break;
-                               case PropertyType.BOOLEAN:
-                                       res.put(prop.getName(), prop.getBoolean());
-                                       break;
-                               case PropertyType.DATE:
-                                       res.put(prop.getName(), prop.getDate());
-                                       break;
-                               case PropertyType.BINARY:
-                                       byte[] arr = JcrUtils.getBinaryAsBytes(prop);
-                                       res.put(prop.getName(), arr);
-                                       break;
-                               default:
-                                       res.put(prop.getName(), prop.getString());
-                               }
-                       }
-                       return res;
-               } catch (RepositoryException e) {
-                       throw new IOException("Cannot read attributes of " + path, e);
-               }
-       }
-
-       @Override
-       public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
-               Node node = toNode(path);
-               try {
-                       Session session = node.getSession();
-                       synchronized (session) {
-                               if (value instanceof byte[]) {
-                                       JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value);
-                               } else if (value instanceof Calendar) {
-                                       node.setProperty(attribute, (Calendar) value);
-                               } else {
-                                       node.setProperty(attribute, value.toString());
-                               }
-                               save(session);
-                       }
-               } catch (RepositoryException e) {
-                       discardChanges(node);
-                       throw new IOException("Cannot set attribute " + attribute + " on " + path, e);
-               }
-       }
-
-       protected Node toNode(Path path) {
-               try {
-                       return ((JcrPath) path).getNode();
-               } catch (RepositoryException e) {
-                       throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e);
-               }
-       }
-
-       /** Discard changes in the underlying session */
-       protected void discardChanges(Node node) {
-               if (node == null)
-                       return;
-               try {
-                       // discard changes
-                       node.getSession().refresh(false);
-               } catch (RepositoryException e) {
-                       e.printStackTrace();
-                       // TODO log out session?
-                       // TODO use Commons logging?
-               }
-       }
-
-       /** Make sure save is robust. */
-       protected void save(Session session) throws RepositoryException {
-               session.refresh(true);
-               session.save();
-               session.notifyAll();
-       }
-
-       /**
-        * To be overriden in order to support the ~ path, with an implementation
-        * specific concept of user home.
-        * 
-        * @return null by default
-        */
-       public Node getUserHome(Repository session) {
-               return null;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java
deleted file mode 100644 (file)
index f214fdc..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.jcr.fs;
-
-
-/** Exception related to the JCR FS */
-public class JcrFsException extends RuntimeException {
-       private static final long serialVersionUID = -7973896038244922980L;
-
-       public JcrFsException(String message, Throwable e) {
-               super(message, e);
-       }
-
-       public JcrFsException(String message) {
-               super(message);
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java
deleted file mode 100644 (file)
index 7318b70..0000000
+++ /dev/null
@@ -1,384 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.nio.file.Path;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.acr.fs.AbstractFsPath;
-
-/** A {@link Path} which contains a reference to a JCR {@link Node}. */
-public class JcrPath extends AbstractFsPath<JcrFileSystem, WorkspaceFileStore> {
-       final static String separator = "/";
-       final static char separatorChar = '/';
-
-//     private final JcrFileSystem fs;
-//     /** null for non absolute paths */
-//     private final WorkspaceFileStore fileStore;
-//     private final String[] path;// null means root
-//     private final boolean absolute;
-//
-//     // optim
-//     private final int hashCode;
-
-       public JcrPath(JcrFileSystem filesSystem, String path) {
-               super(filesSystem, path);
-//             this.fs = filesSystem;
-//             if (path == null)
-//                     throw new JcrFsException("Path cannot be null");
-//             if (path.equals(separator)) {// root
-//                     this.path = null;
-//                     this.absolute = true;
-//                     this.hashCode = 0;
-//                     this.fileStore = fs.getBaseFileStore();
-//                     return;
-//             } else if (path.equals("")) {// empty path
-//                     this.path = new String[] { "" };
-//                     this.absolute = false;
-//                     this.fileStore = null;
-//                     this.hashCode = "".hashCode();
-//                     return;
-//             }
-//
-//             if (path.equals("~")) {// home
-//                     path = filesSystem.getUserHomePath();
-//                     if (path == null)
-//                             throw new JcrFsException("No home directory available");
-//             }
-//
-//             this.absolute = path.charAt(0) == separatorChar ? true : false;
-//
-//             this.fileStore = absolute ? fs.getFileStore(path) : null;
-//
-//             String trimmedPath = path.substring(absolute ? 1 : 0,
-//                             path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length());
-//             this.path = trimmedPath.split(separator);
-//             for (int i = 0; i < this.path.length; i++) {
-//                     this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]);
-//             }
-//             this.hashCode = this.path[this.path.length - 1].hashCode();
-//             assert !(absolute && fileStore == null);
-       }
-
-       public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException {
-               this(filesSystem, filesSystem.getFileStore(node).toFsPath(node));
-       }
-
-       /** Internal optimisation */
-       private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) {
-               super(filesSystem, fileStore, path, absolute);
-//             this.fs = filesSystem;
-//             this.path = path;
-//             this.absolute = path == null ? true : absolute;
-//             if (this.absolute && fileStore == null)
-//                     throw new IllegalArgumentException("Absolute path requires a file store");
-//             if (!this.absolute && fileStore != null)
-//                     throw new IllegalArgumentException("A file store should not be provided for a relative path");
-//             this.fileStore = fileStore;
-//             this.hashCode = path == null ? 0 : path[path.length - 1].hashCode();
-//             assert !(absolute && fileStore == null);
-       }
-
-       protected String cleanUpSegment(String segment) {
-               return Text.unescapeIllegalJcrChars(segment);
-       }
-
-       @Override
-       protected JcrPath newInstance(String path) {
-               return new JcrPath(getFileSystem(), path);
-       }
-
-       @Override
-       protected JcrPath newInstance(String[] segments, boolean absolute) {
-               return new JcrPath(getFileSystem(), getFileStore(), segments, absolute);
-
-       }
-
-//     @Override
-//     public FileSystem getFileSystem() {
-//             return fs;
-//     }
-//
-//     @Override
-//     public boolean isAbsolute() {
-//             return absolute;
-//     }
-//
-//     @Override
-//     public Path getRoot() {
-//             if (path == null)
-//                     return this;
-//             return new JcrPath(fs, separator);
-//     }
-//
-//     @Override
-//     public String toString() {
-//             return toFsPath(path);
-//     }
-//
-//     private String toFsPath(String[] path) {
-//             if (path == null)
-//                     return "/";
-//             StringBuilder sb = new StringBuilder();
-//             if (isAbsolute())
-//                     sb.append('/');
-//             for (int i = 0; i < path.length; i++) {
-//                     if (i != 0)
-//                             sb.append('/');
-//                     sb.append(path[i]);
-//             }
-//             return sb.toString();
-//     }
-
-//     @Deprecated
-//     private String toJcrPath() {
-//             return toJcrPath(path);
-//     }
-//
-//     @Deprecated
-//     private String toJcrPath(String[] path) {
-//             if (path == null)
-//                     return "/";
-//             StringBuilder sb = new StringBuilder();
-//             if (isAbsolute())
-//                     sb.append('/');
-//             for (int i = 0; i < path.length; i++) {
-//                     if (i != 0)
-//                             sb.append('/');
-//                     sb.append(Text.escapeIllegalJcrChars(path[i]));
-//             }
-//             return sb.toString();
-//     }
-
-//     @Override
-//     public Path getFileName() {
-//             if (path == null)
-//                     return null;
-//             return new JcrPath(fs, path[path.length - 1]);
-//     }
-//
-//     @Override
-//     public Path getParent() {
-//             if (path == null)
-//                     return null;
-//             if (path.length == 1)// root
-//                     return new JcrPath(fs, separator);
-//             String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1);
-//             if (!absolute)
-//                     return new JcrPath(fs, null, parentPath, absolute);
-//             else
-//                     return new JcrPath(fs, toFsPath(parentPath));
-//     }
-//
-//     @Override
-//     public int getNameCount() {
-//             if (path == null)
-//                     return 0;
-//             return path.length;
-//     }
-//
-//     @Override
-//     public Path getName(int index) {
-//             if (path == null)
-//                     return null;
-//             return new JcrPath(fs, path[index]);
-//     }
-//
-//     @Override
-//     public Path subpath(int beginIndex, int endIndex) {
-//             if (path == null)
-//                     return null;
-//             String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex);
-//             return new JcrPath(fs, null, parentPath, false);
-//     }
-//
-//     @Override
-//     public boolean startsWith(Path other) {
-//             return toString().startsWith(other.toString());
-//     }
-//
-//     @Override
-//     public boolean startsWith(String other) {
-//             return toString().startsWith(other);
-//     }
-//
-//     @Override
-//     public boolean endsWith(Path other) {
-//             return toString().endsWith(other.toString());
-//     }
-//
-//     @Override
-//     public boolean endsWith(String other) {
-//             return toString().endsWith(other);
-//     }
-
-//     @Override
-//     public Path normalize() {
-//             // always normalized
-//             return this;
-//     }
-
-//     @Override
-//     public Path resolve(Path other) {
-//             JcrPath otherPath = (JcrPath) other;
-//             if (otherPath.isAbsolute())
-//                     return other;
-//             String[] newPath;
-//             if (path == null) {
-//                     newPath = new String[otherPath.path.length];
-//                     System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length);
-//             } else {
-//                     newPath = new String[path.length + otherPath.path.length];
-//                     System.arraycopy(path, 0, newPath, 0, path.length);
-//                     System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length);
-//             }
-//             if (!absolute)
-//                     return new JcrPath(fs, null, newPath, absolute);
-//             else {
-//                     return new JcrPath(fs, toFsPath(newPath));
-//             }
-//     }
-//
-//     @Override
-//     public final Path resolve(String other) {
-//             return resolve(getFileSystem().getPath(other));
-//     }
-//
-//     @Override
-//     public final Path resolveSibling(Path other) {
-//             if (other == null)
-//                     throw new NullPointerException();
-//             Path parent = getParent();
-//             return (parent == null) ? other : parent.resolve(other);
-//     }
-//
-//     @Override
-//     public final Path resolveSibling(String other) {
-//             return resolveSibling(getFileSystem().getPath(other));
-//     }
-//
-//     @Override
-//     public final Iterator<Path> iterator() {
-//             return new Iterator<Path>() {
-//                     private int i = 0;
-//
-//                     @Override
-//                     public boolean hasNext() {
-//                             return (i < getNameCount());
-//                     }
-//
-//                     @Override
-//                     public Path next() {
-//                             if (i < getNameCount()) {
-//                                     Path result = getName(i);
-//                                     i++;
-//                                     return result;
-//                             } else {
-//                                     throw new NoSuchElementException();
-//                             }
-//                     }
-//
-//                     @Override
-//                     public void remove() {
-//                             throw new UnsupportedOperationException();
-//                     }
-//             };
-//     }
-//
-//     @Override
-//     public Path relativize(Path other) {
-//             if (equals(other))
-//                     return new JcrPath(fs, "");
-//             if (other.startsWith(this)) {
-//                     String p1 = toString();
-//                     String p2 = other.toString();
-//                     String relative = p2.substring(p1.length(), p2.length());
-//                     if (relative.charAt(0) == '/')
-//                             relative = relative.substring(1);
-//                     return new JcrPath(fs, relative);
-//             }
-//             throw new IllegalArgumentException(other + " cannot be relativized against " + this);
-//     }
-
-//     @Override
-//     public URI toUri() {
-//             try {
-//                     return new URI(fs.provider().getScheme(), toString(), null);
-//             } catch (URISyntaxException e) {
-//                     throw new JcrFsException("Cannot create URI for " + toString(), e);
-//             }
-//     }
-//
-//     @Override
-//     public Path toAbsolutePath() {
-//             if (isAbsolute())
-//                     return this;
-//             return new JcrPath(fs, fileStore, path, true);
-//     }
-//
-//     @Override
-//     public Path toRealPath(LinkOption... options) throws IOException {
-//             return this;
-//     }
-//
-//     @Override
-//     public File toFile() {
-//             throw new UnsupportedOperationException();
-//     }
-
-       public Node getNode() throws RepositoryException {
-               if (!isAbsolute())// TODO default dir
-                       throw new JcrFsException("Cannot get a JCR node from a relative path");
-               assert getFileStore() != null;
-               return getFileStore().toNode(getSegments());
-//             String pathStr = toJcrPath();
-//             Session session = fs.getSession();
-//             // TODO synchronize on the session ?
-//             if (!session.itemExists(pathStr))
-//                     return null;
-//             return session.getNode(pathStr);
-       }
-//
-//     @Override
-//     public boolean equals(Object obj) {
-//             if (!(obj instanceof JcrPath))
-//                     return false;
-//             JcrPath other = (JcrPath) obj;
-//
-//             if (path == null) {// root
-//                     if (other.path == null)// root
-//                             return true;
-//                     else
-//                             return false;
-//             } else {
-//                     if (other.path == null)// root
-//                             return false;
-//             }
-//             // non root
-//             if (path.length != other.path.length)
-//                     return false;
-//             for (int i = 0; i < path.length; i++) {
-//                     if (!path[i].equals(other.path[i]))
-//                             return false;
-//             }
-//             return true;
-//     }
-
-//     @Override
-//     public int hashCode() {
-//             return hashCode;
-//     }
-
-//     @Override
-//     protected Object clone() throws CloneNotSupportedException {
-//             return new JcrPath(fs, toString());
-//     }
-
-//     @Override
-//     protected void finalize() throws Throwable {
-//             Arrays.fill(path, null);
-//     }
-
-       
-       
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java
deleted file mode 100644 (file)
index eda07a5..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Path;
-import java.util.Iterator;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-public class NodeDirectoryStream implements DirectoryStream<Path> {
-       private final JcrFileSystem fs;
-       private final NodeIterator nodeIterator;
-       private final Iterator<JcrPath> additionalPaths;
-       private final Filter<? super Path> filter;
-
-       public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator<JcrPath> additionalPaths,
-                       Filter<? super Path> filter) {
-               this.fs = fs;
-               this.nodeIterator = nodeIterator;
-               this.additionalPaths = additionalPaths;
-               this.filter = filter;
-       }
-
-       @Override
-       public void close() throws IOException {
-       }
-
-       @Override
-       public Iterator<Path> iterator() {
-               return new Iterator<Path>() {
-                       private JcrPath next = null;
-
-                       @Override
-                       public synchronized boolean hasNext() {
-                               if (next != null)
-                                       return true;
-                               nodes: while (nodeIterator.hasNext()) {
-                                       try {
-                                               Node node = nodeIterator.nextNode();
-                                               String nodeName = node.getName();
-                                               if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:"))
-                                                       continue nodes;
-                                               if (fs.skipNode(node))
-                                                       continue nodes;
-                                               next = new JcrPath(fs, node);
-                                               if (filter != null) {
-                                                       if (filter.accept(next))
-                                                               break nodes;
-                                               } else
-                                                       break nodes;
-                                       } catch (Exception e) {
-                                               throw new JcrFsException("Could not get next path", e);
-                                       }
-                               }
-
-                               if (next == null) {
-                                       if (additionalPaths.hasNext())
-                                               next = additionalPaths.next();
-                               }
-
-                               return next != null;
-                       }
-
-                       @Override
-                       public synchronized Path next() {
-                               if (!hasNext())// make sure has next has been called
-                                       return null;
-                               JcrPath res = next;
-                               next = null;
-                               return res;
-                       }
-
-               };
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java
deleted file mode 100644 (file)
index 8054d52..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.nio.file.attribute.BasicFileAttributes;
-
-import javax.jcr.Node;
-
-public interface NodeFileAttributes extends BasicFileAttributes {
-       public Node getNode();
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java
deleted file mode 100644 (file)
index 4643c8c..0000000
+++ /dev/null
@@ -1,877 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.fs;
-
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Properties;
-
-/**
- * <b>Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons</b>
- * This Class provides some text related utilities
- */
-class Text {
-
-       /**
-        * Hidden constructor.
-        */
-       private Text() {
-       }
-
-       /**
-        * used for the md5
-        */
-       public static final char[] hexTable = "0123456789abcdef".toCharArray();
-
-       /**
-        * Calculate an MD5 hash of the string given.
-        *
-        * @param data
-        *            the data to encode
-        * @param enc
-        *            the character encoding to use
-        * @return a hex encoded string of the md5 digested input
-        */
-       public static String md5(String data, String enc) throws UnsupportedEncodingException {
-               try {
-                       return digest("MD5", data.getBytes(enc));
-               } catch (NoSuchAlgorithmException e) {
-                       throw new InternalError("MD5 digest not available???");
-               }
-       }
-
-       /**
-        * Calculate an MD5 hash of the string given using 'utf-8' encoding.
-        *
-        * @param data
-        *            the data to encode
-        * @return a hex encoded string of the md5 digested input
-        */
-       public static String md5(String data) {
-               try {
-                       return md5(data, "utf-8");
-               } catch (UnsupportedEncodingException e) {
-                       throw new InternalError("UTF8 digest not available???");
-               }
-       }
-
-       /**
-        * Digest the plain string using the given algorithm.
-        *
-        * @param algorithm
-        *            The alogrithm for the digest. This algorithm must be supported
-        *            by the MessageDigest class.
-        * @param data
-        *            The plain text String to be digested.
-        * @param enc
-        *            The character encoding to use
-        * @return The digested plain text String represented as Hex digits.
-        * @throws java.security.NoSuchAlgorithmException
-        *             if the desired algorithm is not supported by the
-        *             MessageDigest class.
-        * @throws java.io.UnsupportedEncodingException
-        *             if the encoding is not supported
-        */
-       public static String digest(String algorithm, String data, String enc)
-                       throws NoSuchAlgorithmException, UnsupportedEncodingException {
-
-               return digest(algorithm, data.getBytes(enc));
-       }
-
-       /**
-        * Digest the plain string using the given algorithm.
-        *
-        * @param algorithm
-        *            The algorithm for the digest. This algorithm must be supported
-        *            by the MessageDigest class.
-        * @param data
-        *            the data to digest with the given algorithm
-        * @return The digested plain text String represented as Hex digits.
-        * @throws java.security.NoSuchAlgorithmException
-        *             if the desired algorithm is not supported by the
-        *             MessageDigest class.
-        */
-       public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException {
-
-               MessageDigest md = MessageDigest.getInstance(algorithm);
-               byte[] digest = md.digest(data);
-               StringBuilder res = new StringBuilder(digest.length * 2);
-               for (byte b : digest) {
-                       res.append(hexTable[(b >> 4) & 15]);
-                       res.append(hexTable[b & 15]);
-               }
-               return res.toString();
-       }
-
-       /**
-        * returns an array of strings decomposed of the original string, split at
-        * every occurrence of 'ch'. if 2 'ch' follow each other with no
-        * intermediate characters, empty "" entries are avoided.
-        *
-        * @param str
-        *            the string to decompose
-        * @param ch
-        *            the character to use a split pattern
-        * @return an array of strings
-        */
-       public static String[] explode(String str, int ch) {
-               return explode(str, ch, false);
-       }
-
-       /**
-        * returns an array of strings decomposed of the original string, split at
-        * every occurrence of 'ch'.
-        *
-        * @param str
-        *            the string to decompose
-        * @param ch
-        *            the character to use a split pattern
-        * @param respectEmpty
-        *            if <code>true</code>, empty elements are generated
-        * @return an array of strings
-        */
-       public static String[] explode(String str, int ch, boolean respectEmpty) {
-               if (str == null || str.length() == 0) {
-                       return new String[0];
-               }
-
-               ArrayList<String> strings = new ArrayList<String>();
-               int pos;
-               int lastpos = 0;
-
-               // add snipples
-               while ((pos = str.indexOf(ch, lastpos)) >= 0) {
-                       if (pos - lastpos > 0 || respectEmpty) {
-                               strings.add(str.substring(lastpos, pos));
-                       }
-                       lastpos = pos + 1;
-               }
-               // add rest
-               if (lastpos < str.length()) {
-                       strings.add(str.substring(lastpos));
-               } else if (respectEmpty && lastpos == str.length()) {
-                       strings.add("");
-               }
-
-               // return string array
-               return strings.toArray(new String[strings.size()]);
-       }
-
-       /**
-        * Concatenates all strings in the string array using the specified
-        * delimiter.
-        * 
-        * @param arr
-        * @param delim
-        * @return the concatenated string
-        */
-       public static String implode(String[] arr, String delim) {
-               StringBuilder buf = new StringBuilder();
-               for (int i = 0; i < arr.length; i++) {
-                       if (i > 0) {
-                               buf.append(delim);
-                       }
-                       buf.append(arr[i]);
-               }
-               return buf.toString();
-       }
-
-       /**
-        * Replaces all occurrences of <code>oldString</code> in <code>text</code>
-        * with <code>newString</code>.
-        *
-        * @param text
-        * @param oldString
-        *            old substring to be replaced with <code>newString</code>
-        * @param newString
-        *            new substring to replace occurrences of <code>oldString</code>
-        * @return a string
-        */
-       public static String replace(String text, String oldString, String newString) {
-               if (text == null || oldString == null || newString == null) {
-                       throw new IllegalArgumentException("null argument");
-               }
-               int pos = text.indexOf(oldString);
-               if (pos == -1) {
-                       return text;
-               }
-               int lastPos = 0;
-               StringBuilder sb = new StringBuilder(text.length());
-               while (pos != -1) {
-                       sb.append(text.substring(lastPos, pos));
-                       sb.append(newString);
-                       lastPos = pos + oldString.length();
-                       pos = text.indexOf(oldString, lastPos);
-               }
-               if (lastPos < text.length()) {
-                       sb.append(text.substring(lastPos));
-               }
-               return sb.toString();
-       }
-
-       /**
-        * Replaces XML characters in the given string that might need escaping as
-        * XML text or attribute
-        *
-        * @param text
-        *            text to be escaped
-        * @return a string
-        */
-       public static String encodeIllegalXMLCharacters(String text) {
-               return encodeMarkupCharacters(text, false);
-       }
-
-       /**
-        * Replaces HTML characters in the given string that might need escaping as
-        * HTML text or attribute
-        *
-        * @param text
-        *            text to be escaped
-        * @return a string
-        */
-       public static String encodeIllegalHTMLCharacters(String text) {
-               return encodeMarkupCharacters(text, true);
-       }
-
-       private static String encodeMarkupCharacters(String text, boolean isHtml) {
-               if (text == null) {
-                       throw new IllegalArgumentException("null argument");
-               }
-               StringBuilder buf = null;
-               int length = text.length();
-               int pos = 0;
-               for (int i = 0; i < length; i++) {
-                       int ch = text.charAt(i);
-                       switch (ch) {
-                       case '<':
-                       case '>':
-                       case '&':
-                       case '"':
-                       case '\'':
-                               if (buf == null) {
-                                       buf = new StringBuilder();
-                               }
-                               if (i > 0) {
-                                       buf.append(text.substring(pos, i));
-                               }
-                               pos = i + 1;
-                               break;
-                       default:
-                               continue;
-                       }
-                       if (ch == '<') {
-                               buf.append("&lt;");
-                       } else if (ch == '>') {
-                               buf.append("&gt;");
-                       } else if (ch == '&') {
-                               buf.append("&amp;");
-                       } else if (ch == '"') {
-                               buf.append("&quot;");
-                       } else if (ch == '\'') {
-                               buf.append(isHtml ? "&#39;" : "&apos;");
-                       }
-               }
-               if (buf == null) {
-                       return text;
-               } else {
-                       if (pos < length) {
-                               buf.append(text.substring(pos));
-                       }
-                       return buf.toString();
-               }
-       }
-
-       /**
-        * The list of characters that are not encoded by the <code>escape()</code>
-        * and <code>unescape()</code> METHODS. They contains the characters as
-        * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
-        * <p>
-        * 
-        * <pre>
-        * unreserved  = alphanum | mark
-        * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
-        * </pre>
-        */
-       public static BitSet URISave;
-
-       /**
-        * Same as {@link #URISave} but also contains the '/'
-        */
-       public static BitSet URISaveEx;
-
-       static {
-               URISave = new BitSet(256);
-               int i;
-               for (i = 'a'; i <= 'z'; i++) {
-                       URISave.set(i);
-               }
-               for (i = 'A'; i <= 'Z'; i++) {
-                       URISave.set(i);
-               }
-               for (i = '0'; i <= '9'; i++) {
-                       URISave.set(i);
-               }
-               URISave.set('-');
-               URISave.set('_');
-               URISave.set('.');
-               URISave.set('!');
-               URISave.set('~');
-               URISave.set('*');
-               URISave.set('\'');
-               URISave.set('(');
-               URISave.set(')');
-
-               URISaveEx = (BitSet) URISave.clone();
-               URISaveEx.set('/');
-       }
-
-       /**
-        * Does an URL encoding of the <code>string</code> using the
-        * <code>escape</code> character. The characters that don't need encoding
-        * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
-        * RFC 2396, but without the escape character.
-        *
-        * @param string
-        *            the string to encode.
-        * @param escape
-        *            the escape character.
-        * @return the escaped string
-        * @throws NullPointerException
-        *             if <code>string</code> is <code>null</code>.
-        */
-       public static String escape(String string, char escape) {
-               return escape(string, escape, false);
-       }
-
-       /**
-        * Does an URL encoding of the <code>string</code> using the
-        * <code>escape</code> character. The characters that don't need encoding
-        * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax'
-        * RFC 2396, but without the escape character. If <code>isPath</code> is
-        * <code>true</code>, additionally the slash '/' is ignored, too.
-        *
-        * @param string
-        *            the string to encode.
-        * @param escape
-        *            the escape character.
-        * @param isPath
-        *            if <code>true</code>, the string is treated as path
-        * @return the escaped string
-        * @throws NullPointerException
-        *             if <code>string</code> is <code>null</code>.
-        */
-       public static String escape(String string, char escape, boolean isPath) {
-               try {
-                       BitSet validChars = isPath ? URISaveEx : URISave;
-                       byte[] bytes = string.getBytes("utf-8");
-                       StringBuilder out = new StringBuilder(bytes.length);
-                       for (byte aByte : bytes) {
-                               int c = aByte & 0xff;
-                               if (validChars.get(c) && c != escape) {
-                                       out.append((char) c);
-                               } else {
-                                       out.append(escape);
-                                       out.append(hexTable[(c >> 4) & 0x0f]);
-                                       out.append(hexTable[(c) & 0x0f]);
-                               }
-                       }
-                       return out.toString();
-               } catch (UnsupportedEncodingException e) {
-                       throw new InternalError(e.toString());
-               }
-       }
-
-       /**
-        * Does a URL encoding of the <code>string</code>. The characters that don't
-        * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
-        * generic syntax' RFC 2396.
-        *
-        * @param string
-        *            the string to encode
-        * @return the escaped string
-        * @throws NullPointerException
-        *             if <code>string</code> is <code>null</code>.
-        */
-       public static String escape(String string) {
-               return escape(string, '%');
-       }
-
-       /**
-        * Does a URL encoding of the <code>path</code>. The characters that don't
-        * need encoding are those defined 'unreserved' in section 2.3 of the 'URI
-        * generic syntax' RFC 2396. In contrast to the {@link #escape(String)}
-        * method, not the entire path string is escaped, but every individual part
-        * (i.e. the slashes are not escaped).
-        *
-        * @param path
-        *            the path to encode
-        * @return the escaped path
-        * @throws NullPointerException
-        *             if <code>path</code> is <code>null</code>.
-        */
-       public static String escapePath(String path) {
-               return escape(path, '%', true);
-       }
-
-       /**
-        * Does a URL decoding of the <code>string</code> using the
-        * <code>escape</code> character. Please note that in opposite to the
-        * {@link java.net.URLDecoder} it does not transform the + into spaces.
-        *
-        * @param string
-        *            the string to decode
-        * @param escape
-        *            the escape character
-        * @return the decoded string
-        * @throws NullPointerException
-        *             if <code>string</code> is <code>null</code>.
-        * @throws IllegalArgumentException
-        *             if the 2 characters following the escape character do not
-        *             represent a hex-number or if not enough characters follow an
-        *             escape character
-        */
-       public static String unescape(String string, char escape) {
-               try {
-                       byte[] utf8 = string.getBytes("utf-8");
-
-                       // Check whether escape occurs at invalid position
-                       if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape)
-                                       || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) {
-                               throw new IllegalArgumentException("Premature end of escape sequence at end of input");
-                       }
-
-                       ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length);
-                       for (int k = 0; k < utf8.length; k++) {
-                               byte b = utf8[k];
-                               if (b == escape) {
-                                       out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k]));
-                               } else {
-                                       out.write(b);
-                               }
-                       }
-
-                       return new String(out.toByteArray(), "utf-8");
-               } catch (UnsupportedEncodingException e) {
-                       throw new InternalError(e.toString());
-               }
-       }
-
-       /**
-        * Does a URL decoding of the <code>string</code>. Please note that in
-        * opposite to the {@link java.net.URLDecoder} it does not transform the +
-        * into spaces.
-        *
-        * @param string
-        *            the string to decode
-        * @return the decoded string
-        * @throws NullPointerException
-        *             if <code>string</code> is <code>null</code>.
-        * @throws ArrayIndexOutOfBoundsException
-        *             if not enough character follow an escape character
-        * @throws IllegalArgumentException
-        *             if the 2 characters following the escape character do not
-        *             represent a hex-number.
-        */
-       public static String unescape(String string) {
-               return unescape(string, '%');
-       }
-
-       /**
-        * Escapes all illegal JCR name characters of a string. The encoding is
-        * loosely modeled after URI encoding, but only encodes the characters it
-        * absolutely needs to in order to make the resulting string a valid JCR
-        * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding.
-        * <p>
-        * QName EBNF:<br>
-        * <xmp> simplename ::= onecharsimplename | twocharsimplename |
-        * threeormorecharname onecharsimplename ::= (* Any Unicode character
-        * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *)
-        * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' |
-        * onecharsimplename onecharsimplename threeormorecharname ::= nonspace
-        * string nonspace string ::= char | string char char ::= nonspace | ' '
-        * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*',
-        * '|' or any whitespace character *) </xmp>
-        *
-        * @param name
-        *            the name to escape
-        * @return the escaped name
-        */
-       public static String escapeIllegalJcrChars(String name) {
-               return escapeIllegalChars(name, "%/:[]*|\t\r\n");
-       }
-
-       /**
-        * Escapes all illegal JCR 1.0 name characters of a string. Use
-        * {@link #unescapeIllegalJcrChars(String)} for decoding.
-        * <p>
-        * QName EBNF:<br>
-        * <xmp> simplename ::= onecharsimplename | twocharsimplename |
-        * threeormorecharname onecharsimplename ::= (* Any Unicode character
-        * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
-        * character *) twocharsimplename ::= '.' onecharsimplename |
-        * onecharsimplename '.' | onecharsimplename onecharsimplename
-        * threeormorecharname ::= nonspace string nonspace string ::= char | string
-        * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character
-        * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace
-        * character *) </xmp>
-        *
-        * @since Apache Jackrabbit 2.3.2 and 2.2.10
-        * @see <a href=
-        *      "https://issues.apache.org/jira/browse/JCR-3128">JCR-3128</a>
-        * @param name
-        *            the name to escape
-        * @return the escaped name
-        */
-       public static String escapeIllegalJcr10Chars(String name) {
-               return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n");
-       }
-
-       private static String escapeIllegalChars(String name, String illegal) {
-               StringBuilder buffer = new StringBuilder(name.length() * 2);
-               for (int i = 0; i < name.length(); i++) {
-                       char ch = name.charAt(i);
-                       if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3)
-                                       || (ch == ' ' && (i == 0 || i == name.length() - 1))) {
-                               buffer.append('%');
-                               buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16)));
-                               buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16)));
-                       } else {
-                               buffer.append(ch);
-                       }
-               }
-               return buffer.toString();
-       }
-
-       /**
-        * Escapes illegal XPath search characters at the end of a string.
-        * <p>
-        * Example:<br>
-        * A search string like 'test?' will run into a ParseException documented in
-        * http://issues.apache.org/jira/browse/JCR-1248
-        *
-        * @param s
-        *            the string to encode
-        * @return the escaped string
-        */
-       public static String escapeIllegalXpathSearchChars(String s) {
-               StringBuilder sb = new StringBuilder();
-               sb.append(s.substring(0, (s.length() - 1)));
-               char c = s.charAt(s.length() - 1);
-               // NOTE: keep this in sync with _ESCAPED_CHAR below!
-               if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') {
-                       sb.append('\\');
-               }
-               sb.append(c);
-               return sb.toString();
-       }
-
-       /**
-        * Unescapes previously escaped jcr chars.
-        * <p>
-        * Please note, that this does not exactly the same as the url related
-        * {@link #unescape(String)}, since it handles the byte-encoding
-        * differently.
-        *
-        * @param name
-        *            the name to unescape
-        * @return the unescaped name
-        */
-       public static String unescapeIllegalJcrChars(String name) {
-               StringBuilder buffer = new StringBuilder(name.length());
-               int i = name.indexOf('%');
-               while (i > -1 && i + 2 < name.length()) {
-                       buffer.append(name.toCharArray(), 0, i);
-                       int a = Character.digit(name.charAt(i + 1), 16);
-                       int b = Character.digit(name.charAt(i + 2), 16);
-                       if (a > -1 && b > -1) {
-                               buffer.append((char) (a * 16 + b));
-                               name = name.substring(i + 3);
-                       } else {
-                               buffer.append('%');
-                               name = name.substring(i + 1);
-                       }
-                       i = name.indexOf('%');
-               }
-               buffer.append(name);
-               return buffer.toString();
-       }
-
-       /**
-        * Returns the name part of the path. If the given path is already a name
-        * (i.e. contains no slashes) it is returned.
-        *
-        * @param path
-        *            the path
-        * @return the name part or <code>null</code> if <code>path</code> is
-        *         <code>null</code>.
-        */
-       public static String getName(String path) {
-               return getName(path, '/');
-       }
-
-       /**
-        * Returns the name part of the path, delimited by the given
-        * <code>delim</code>. If the given path is already a name (i.e. contains no
-        * <code>delim</code> characters) it is returned.
-        *
-        * @param path
-        *            the path
-        * @param delim
-        *            the delimiter
-        * @return the name part or <code>null</code> if <code>path</code> is
-        *         <code>null</code>.
-        */
-       public static String getName(String path, char delim) {
-               return path == null ? null : path.substring(path.lastIndexOf(delim) + 1);
-       }
-
-       /**
-        * Same as {@link #getName(String)} but adding the possibility to pass paths
-        * that end with a trailing '/'
-        *
-        * @see #getName(String)
-        */
-       public static String getName(String path, boolean ignoreTrailingSlash) {
-               if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) {
-                       path = path.substring(0, path.length() - 1);
-               }
-               return getName(path);
-       }
-
-       /**
-        * Returns the namespace prefix of the given <code>qname</code>. If the
-        * prefix is missing, an empty string is returned. Please note, that this
-        * method does not validate the name or prefix.
-        * </p>
-        * the qname has the format: qname := [prefix ':'] local;
-        *
-        * @param qname
-        *            a qualified name
-        * @return the prefix of the name or "".
-        *
-        * @see #getLocalName(String)
-        *
-        * @throws NullPointerException
-        *             if <code>qname</code> is <code>null</code>
-        */
-       public static String getNamespacePrefix(String qname) {
-               int pos = qname.indexOf(':');
-               return pos >= 0 ? qname.substring(0, pos) : "";
-       }
-
-       /**
-        * Returns the local name of the given <code>qname</code>. Please note, that
-        * this method does not validate the name.
-        * </p>
-        * the qname has the format: qname := [prefix ':'] local;
-        *
-        * @param qname
-        *            a qualified name
-        * @return the localname
-        *
-        * @see #getNamespacePrefix(String)
-        *
-        * @throws NullPointerException
-        *             if <code>qname</code> is <code>null</code>
-        */
-       public static String getLocalName(String qname) {
-               int pos = qname.indexOf(':');
-               return pos >= 0 ? qname.substring(pos + 1) : qname;
-       }
-
-       /**
-        * Determines, if two paths denote hierarchical siblins.
-        *
-        * @param p1
-        *            first path
-        * @param p2
-        *            second path
-        * @return true if on same level, false otherwise
-        */
-       public static boolean isSibling(String p1, String p2) {
-               int pos1 = p1.lastIndexOf('/');
-               int pos2 = p2.lastIndexOf('/');
-               return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1));
-       }
-
-       /**
-        * Determines if the <code>descendant</code> path is hierarchical a
-        * descendant of <code>path</code>.
-        *
-        * @param path
-        *            the current path
-        * @param descendant
-        *            the potential descendant
-        * @return <code>true</code> if the <code>descendant</code> is a descendant;
-        *         <code>false</code> otherwise.
-        */
-       public static boolean isDescendant(String path, String descendant) {
-               String pattern = path.endsWith("/") ? path : path + "/";
-               return !pattern.equals(descendant) && descendant.startsWith(pattern);
-       }
-
-       /**
-        * Determines if the <code>descendant</code> path is hierarchical a
-        * descendant of <code>path</code> or equal to it.
-        *
-        * @param path
-        *            the path to check
-        * @param descendant
-        *            the potential descendant
-        * @return <code>true</code> if the <code>descendant</code> is a descendant
-        *         or equal; <code>false</code> otherwise.
-        */
-       public static boolean isDescendantOrEqual(String path, String descendant) {
-               if (path.equals(descendant)) {
-                       return true;
-               } else {
-                       String pattern = path.endsWith("/") ? path : path + "/";
-                       return descendant.startsWith(pattern);
-               }
-       }
-
-       /**
-        * Returns the n<sup>th</sup> relative parent of the path, where n=level.
-        * <p>
-        * Example:<br>
-        * <code>
-        * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar"
-        * </code>
-        *
-        * @param path
-        *            the path of the page
-        * @param level
-        *            the level of the parent
-        */
-       public static String getRelativeParent(String path, int level) {
-               int idx = path.length();
-               while (level > 0) {
-                       idx = path.lastIndexOf('/', idx - 1);
-                       if (idx < 0) {
-                               return "";
-                       }
-                       level--;
-               }
-               return (idx == 0) ? "/" : path.substring(0, idx);
-       }
-
-       /**
-        * Same as {@link #getRelativeParent(String, int)} but adding the
-        * possibility to pass paths that end with a trailing '/'
-        *
-        * @see #getRelativeParent(String, int)
-        */
-       public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) {
-               if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) {
-                       path = path.substring(0, path.length() - 1);
-               }
-               return getRelativeParent(path, level);
-       }
-
-       /**
-        * Returns the n<sup>th</sup> absolute parent of the path, where n=level.
-        * <p>
-        * Example:<br>
-        * <code>
-        * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar"
-        * </code>
-        *
-        * @param path
-        *            the path of the page
-        * @param level
-        *            the level of the parent
-        */
-       public static String getAbsoluteParent(String path, int level) {
-               int idx = 0;
-               int len = path.length();
-               while (level >= 0 && idx < len) {
-                       idx = path.indexOf('/', idx + 1);
-                       if (idx < 0) {
-                               idx = len;
-                       }
-                       level--;
-               }
-               return level >= 0 ? "" : path.substring(0, idx);
-       }
-
-       /**
-        * Performs variable replacement on the given string value. Each
-        * <code>${...}</code> sequence within the given value is replaced with the
-        * value of the named parser variable. If a variable is not found in the
-        * properties an IllegalArgumentException is thrown unless
-        * <code>ignoreMissing</code> is <code>true</code>. In the later case, the
-        * missing variable is replaced by the empty string.
-        *
-        * @param value
-        *            the original value
-        * @param ignoreMissing
-        *            if <code>true</code>, missing variables are replaced by the
-        *            empty string.
-        * @return value after variable replacements
-        * @throws IllegalArgumentException
-        *             if the replacement of a referenced variable is not found
-        */
-       public static String replaceVariables(Properties variables, String value, boolean ignoreMissing)
-                       throws IllegalArgumentException {
-               StringBuilder result = new StringBuilder();
-
-               // Value:
-               // +--+-+--------+-+-----------------+
-               // | |p|--> |q|--> |
-               // +--+-+--------+-+-----------------+
-               int p = 0, q = value.indexOf("${"); // Find first ${
-               while (q != -1) {
-                       result.append(value.substring(p, q)); // Text before ${
-                       p = q;
-                       q = value.indexOf("}", q + 2); // Find }
-                       if (q != -1) {
-                               String variable = value.substring(p + 2, q);
-                               String replacement = variables.getProperty(variable);
-                               if (replacement == null) {
-                                       if (ignoreMissing) {
-                                               replacement = "";
-                                       } else {
-                                               throw new IllegalArgumentException("Replacement not found for ${" + variable + "}.");
-                                       }
-                               }
-                               result.append(replacement);
-                               p = q + 1;
-                               q = value.indexOf("${", p); // Find next ${
-                       }
-               }
-               result.append(value.substring(p, value.length())); // Trailing text
-
-               return result.toString();
-       }
-
-       private static byte decodeDigit(byte b) {
-               if (b >= 0x30 && b <= 0x39) {
-                       return (byte) (b - 0x30);
-               } else if (b >= 0x41 && b <= 0x46) {
-                       return (byte) (b - 0x37);
-               } else if (b >= 0x61 && b <= 0x66) {
-                       return (byte) (b - 0x57);
-               } else {
-                       throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java
deleted file mode 100644 (file)
index ce4205a..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.IOException;
-import java.nio.file.FileStore;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileStoreAttributeView;
-import java.util.Arrays;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Workspace;
-
-import org.argeo.api.acr.fs.AbstractFsStore;
-import org.argeo.jcr.JcrUtils;
-
-/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
-public class WorkspaceFileStore extends AbstractFsStore {
-       private final String mountPath;
-       private final Workspace workspace;
-       private final String workspaceName;
-       private final int mountDepth;
-
-       public WorkspaceFileStore(String mountPath, Workspace workspace) {
-               if ("/".equals(mountPath) || "".equals(mountPath))
-                       throw new IllegalArgumentException(
-                                       "Mount path '" + mountPath + "' is unsupported, use null for the base file store");
-               if (mountPath != null && !mountPath.startsWith(JcrPath.separator))
-                       throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
-               if (mountPath != null && mountPath.endsWith(JcrPath.separator))
-                       throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /");
-               this.mountPath = mountPath;
-               if (mountPath == null)
-                       mountDepth = 0;
-               else {
-                       mountDepth = mountPath.split(JcrPath.separator).length - 1;
-               }
-               this.workspace = workspace;
-               this.workspaceName = workspace.getName();
-       }
-
-       public void close() {
-               JcrUtils.logoutQuietly(workspace.getSession());
-       }
-
-       @Override
-       public String name() {
-               return workspace.getName();
-       }
-
-       @Override
-       public String type() {
-               return "workspace";
-       }
-
-       @Override
-       public boolean isReadOnly() {
-               return false;
-       }
-
-       @Override
-       public long getTotalSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public long getUsableSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public long getUnallocatedSpace() throws IOException {
-               return 0;
-       }
-
-       @Override
-       public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
-               return false;
-       }
-
-       @Override
-       public boolean supportsFileAttributeView(String name) {
-               return false;
-       }
-
-       @Override
-       public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
-               return null;
-       }
-
-       @Override
-       public Object getAttribute(String attribute) throws IOException {
-               return workspace.getSession().getRepository().getDescriptor(attribute);
-       }
-
-       public Workspace getWorkspace() {
-               return workspace;
-       }
-
-       public String toFsPath(Node node) throws RepositoryException {
-               String nodeWorkspaceName = node.getSession().getWorkspace().getName();
-               if (!nodeWorkspaceName.equals(workspace.getName()))
-                       throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName
-                                       + "' in file store '" + workspace.getName() + "'");
-               return mountPath == null ? node.getPath() : mountPath + node.getPath();
-       }
-
-       public boolean isBase() {
-               return mountPath == null;
-       }
-
-       Node toNode(String[] fullPath) throws RepositoryException {
-               String jcrPath = toJcrPath(fullPath);
-               Session session = workspace.getSession();
-               if (!session.itemExists(jcrPath))
-                       return null;
-               Node node = session.getNode(jcrPath);
-               return node;
-       }
-
-       String toJcrPath(String fsPath) {
-               if (fsPath.length() == 1)
-                       return toJcrPath((String[]) null);// root
-               String[] arr = fsPath.substring(1).split("/");
-//             if (arr.length == 0 || (arr.length == 1 && arr[0].equals("")))
-//                     return toJcrPath((String[]) null);// root
-//             else
-               return toJcrPath(arr);
-       }
-
-       private String toJcrPath(String[] path) {
-               if (path == null)
-                       return "/";
-               if (path.length < mountDepth)
-                       throw new IllegalArgumentException(
-                                       "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
-
-               if (!isBase()) {
-                       // check mount compatibility
-                       StringBuilder mount = new StringBuilder();
-                       mount.append('/');
-                       for (int i = 0; i < mountDepth; i++) {
-                               if (i != 0)
-                                       mount.append('/');
-                               mount.append(Text.escapeIllegalJcrChars(path[i]));
-                       }
-                       if (!mountPath.equals(mount.toString()))
-                               throw new IllegalArgumentException(
-                                               "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath);
-               }
-
-               StringBuilder sb = new StringBuilder();
-               sb.append('/');
-               for (int i = mountDepth; i < path.length; i++) {
-                       if (i != mountDepth)
-                               sb.append('/');
-                       sb.append(Text.escapeIllegalJcrChars(path[i]));
-               }
-               return sb.toString();
-       }
-
-       public String getMountPath() {
-               return mountPath;
-       }
-
-       public String getWorkspaceName() {
-               return workspaceName;
-       }
-
-       public int getMountDepth() {
-               return mountDepth;
-       }
-
-       @Override
-       public int hashCode() {
-               return workspaceName.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof WorkspaceFileStore))
-                       return false;
-               WorkspaceFileStore other = (WorkspaceFileStore) obj;
-               return workspaceName.equals(other.workspaceName);
-       }
-
-       @Override
-       public String toString() {
-               return "WorkspaceFileStore " + workspaceName;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java
deleted file mode 100644 (file)
index 0cdfdaf..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Java NIO file system implementation based on plain JCR. */
-package org.argeo.jcr.fs;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd
deleted file mode 100644 (file)
index 3eb0e7a..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// JCR EXTENSIONS
-//
-<jcrx = "http://www.argeo.org/ns/jcrx">
-
-[jcrx:xmlvalue]
-- *
-+ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext
-
-[jcrx:xmltext]
- - jcr:xmlcharacters (STRING) mandatory
-
-[jcrx:csum]
-mixin
- - jcrx:sum (STRING) *
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java
deleted file mode 100644 (file)
index 1837749..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic JCR utilities. */
-package org.argeo.jcr;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java
deleted file mode 100644 (file)
index 0177636..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-
-/** Base class for URL based proxys. */
-public abstract class AbstractUrlProxy implements ResourceProxy {
-       private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class);
-
-       private Repository jcrRepository;
-       private Session jcrAdminSession;
-       private String proxyWorkspace = "proxy";
-
-       protected abstract Node retrieve(Session session, String path);
-
-       void init() {
-               try {
-                       jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace);
-                       beforeInitSessionSave(jcrAdminSession);
-                       if (jcrAdminSession.hasPendingChanges())
-                               jcrAdminSession.save();
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(jcrAdminSession);
-                       throw new JcrException("Cannot initialize URL proxy", e);
-               }
-       }
-
-       /**
-        * Called before the (admin) session is saved at the end of the initialization.
-        * Does nothing by default, to be overridden.
-        */
-       protected void beforeInitSessionSave(Session session) throws RepositoryException {
-       }
-
-       void destroy() {
-               JcrUtils.logoutQuietly(jcrAdminSession);
-       }
-
-       /**
-        * Called before the (admin) session is logged out when resources are released.
-        * Does nothing by default, to be overridden.
-        */
-       protected void beforeDestroySessionLogout() throws RepositoryException {
-       }
-
-       public Node proxy(String path) {
-               // we open a JCR session with client credentials in order not to use the
-               // admin session in multiple thread or make it a bottleneck.
-               Node nodeAdmin = null;
-               Node nodeClient = null;
-               Session clientSession = null;
-               try {
-                       clientSession = jcrRepository.login(proxyWorkspace);
-                       if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) {
-                               nodeAdmin = retrieveAndSave(path);
-                               if (nodeAdmin != null)
-                                       nodeClient = clientSession.getNode(path);
-                       } else
-                               nodeClient = clientSession.getNode(path);
-                       return nodeClient;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot proxy " + path, e);
-               } finally {
-                       if (nodeClient == null)
-                               JcrUtils.logoutQuietly(clientSession);
-               }
-       }
-
-       protected synchronized Node retrieveAndSave(String path) {
-               try {
-                       Node node = retrieve(jcrAdminSession, path);
-                       if (node == null)
-                               return null;
-                       jcrAdminSession.save();
-                       return node;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(jcrAdminSession);
-                       throw new JcrException("Cannot retrieve and save " + path, e);
-               } finally {
-                       notifyAll();
-               }
-       }
-
-       /** Session is not saved */
-       protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException {
-               Node node = null;
-               if (session.itemExists(path)) {
-                       // throw new ArgeoJcrException("Node " + path + " already exists");
-               }
-               try (InputStream in = new URL(remoteUrl).openStream()) {
-                       // URL u = new URL(remoteUrl);
-                       // in = u.openStream();
-                       node = importFile(session, path, in);
-               } catch (IOException e) {
-                       if (log.isDebugEnabled()) {
-                               log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage());
-                               // log.trace("Cannot read because of ", e);
-                       }
-                       JcrUtils.discardQuietly(session);
-                       // } finally {
-                       // IOUtils.closeQuietly(in);
-               }
-               return node;
-       }
-
-       protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException {
-               Binary binary = null;
-               try {
-                       Node content = null;
-                       Node node = null;
-                       if (!session.itemExists(path)) {
-                               node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false);
-                               content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
-                       } else {
-                               node = session.getNode(path);
-                               content = node.getNode(Node.JCR_CONTENT);
-                       }
-                       binary = session.getValueFactory().createBinary(in);
-                       content.setProperty(Property.JCR_DATA, binary);
-                       JcrUtils.updateLastModifiedAndParents(node, null, true);
-                       return node;
-               } finally {
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       /** Whether the file should be updated. */
-       protected Boolean shouldUpdate(Session clientSession, String nodePath) {
-               return false;
-       }
-
-       public void setJcrRepository(Repository jcrRepository) {
-               this.jcrRepository = jcrRepository;
-       }
-
-       public void setProxyWorkspace(String localWorkspace) {
-               this.proxyWorkspace = localWorkspace;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java
deleted file mode 100644 (file)
index 84eea1f..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.jcr.proxy;
-
-import javax.jcr.Node;
-
-/** A proxy which nows how to resolve and synchronize relative URLs */
-public interface ResourceProxy {
-       /**
-        * Proxy the file referenced by this relative path in the underlying
-        * repository. A new session is created by each call, so the underlying
-        * session of the returned node must be closed by the caller.
-        * 
-        * @return the proxied Node, <code>null</code> if the resource was not found
-        *         (e.g. HTTP 404)
-        */
-       public Node proxy(String relativePath);
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java
deleted file mode 100644 (file)
index a8e00df..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.Bin;
-import org.argeo.jcr.JcrUtils;
-
-/** Wraps a proxy via HTTP */
-public class ResourceProxyServlet extends HttpServlet {
-       private static final long serialVersionUID = -8886549549223155801L;
-
-       private final static CmsLog log = CmsLog
-                       .getLog(ResourceProxyServlet.class);
-
-       private ResourceProxy proxy;
-
-       private String contentTypeCharset = "UTF-8";
-
-       @Override
-       protected void doGet(HttpServletRequest request,
-                       HttpServletResponse response) throws ServletException, IOException {
-               String path = request.getPathInfo();
-
-               if (log.isTraceEnabled()) {
-                       log.trace("path=" + path);
-                       log.trace("UserPrincipal = " + request.getUserPrincipal().getName());
-                       log.trace("SessionID = " + request.getSession(false).getId());
-                       log.trace("ContextPath = " + request.getContextPath());
-                       log.trace("ServletPath = " + request.getServletPath());
-                       log.trace("PathInfo = " + request.getPathInfo());
-                       log.trace("Method = " + request.getMethod());
-                       log.trace("User-Agent = " + request.getHeader("User-Agent"));
-               }
-
-               Node node = null;
-               try {
-                       node = proxy.proxy(path);
-                       if (node == null)
-                               response.sendError(404);
-                       else
-                               processResponse(node, response);
-               } finally {
-                       if (node != null)
-                               try {
-                                       JcrUtils.logoutQuietly(node.getSession());
-                               } catch (RepositoryException e) {
-                                       // silent
-                               }
-               }
-
-       }
-
-       /** Retrieve the content of the node. */
-       protected void processResponse(Node node, HttpServletResponse response) {
-//             Binary binary = null;
-//             InputStream in = null;
-               try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT)
-                               .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) {
-                       String fileName = node.getName();
-                       String ext = FilenameUtils.getExtension(fileName);
-
-                       // TODO use a more generic / standard approach
-                       // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml
-                       String contentType;
-                       if ("xml".equals(ext))
-                               contentType = "text/xml;charset=" + contentTypeCharset;
-                       else if ("jar".equals(ext))
-                               contentType = "application/java-archive";
-                       else if ("zip".equals(ext))
-                               contentType = "application/zip";
-                       else if ("gz".equals(ext))
-                               contentType = "application/x-gzip";
-                       else if ("bz2".equals(ext))
-                               contentType = "application/x-bzip2";
-                       else if ("tar".equals(ext))
-                               contentType = "application/x-tar";
-                       else if ("rpm".equals(ext))
-                               contentType = "application/x-redhat-package-manager";
-                       else
-                               contentType = "application/octet-stream";
-                       contentType = contentType + ";name=\"" + fileName + "\"";
-                       response.setHeader("Content-Disposition", "attachment; filename=\""
-                                       + fileName + "\"");
-                       response.setHeader("Expires", "0");
-                       response.setHeader("Cache-Control", "no-cache, must-revalidate");
-                       response.setHeader("Pragma", "no-cache");
-
-                       response.setContentType(contentType);
-
-                       IOUtils.copy(in, response.getOutputStream());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot download " + node, e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot download " + node, e);
-               }
-       }
-
-       public void setProxy(ResourceProxy resourceProxy) {
-               this.proxy = resourceProxy;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java
deleted file mode 100644 (file)
index a578c45..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Components to build proxys based on JCR. */
-package org.argeo.jcr.proxy;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java
deleted file mode 100644 (file)
index 2adb6a9..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-package org.argeo.jcr.xml;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.jcr.Jcr;
-
-/** Utilities around JCR and XML. */
-public class JcrXmlUtils {
-       /**
-        * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with
-        * <code>false</code>.
-        */
-       public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException {
-               toXmlElements(writer, node, null, false, false, false);
-       }
-
-       /**
-        * Write JCR properties as XML elements in a tree structure whose elements are
-        * named by node primary type.
-        * 
-        * @param writer               the writer to use
-        * @param node                 the subtree
-        * @param depth                maximal depth, or if <code>null</code> the whole
-        *                             subtree. It must be positive, with depth 0
-        *                             describing just the node without its children.
-        * @param withMetadata         whether to write the primary type and mixins as
-        *                             elements
-        * @param withPrefix           whether to keep the namespace prefixes
-        * @param propertiesAsElements whether single properties should be written as
-        *                             elements rather than attributes. If
-        *                             <code>false</code>, multiple properties will be
-        *                             skipped.
-        */
-       public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix,
-                       boolean propertiesAsElements) throws RepositoryException, IOException {
-               if (depth != null && depth < 0)
-                       throw new IllegalArgumentException("Depth " + depth + " is negative.");
-
-               if (node.getName().equals(Jcr.JCR_XMLTEXT)) {
-                       writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString());
-                       return;
-               }
-
-               if (!propertiesAsElements) {
-                       Map<String, String> attrs = new TreeMap<>();
-                       PropertyIterator pit = node.getProperties();
-                       properties: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               if (!p.isMultiple()) {
-                                       String pName = p.getName();
-                                       if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
-                                                       || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
-                                                       || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
-                                               continue properties;
-                                       attrs.put(withPrefix(p.getName(), withPrefix), p.getString());
-                               }
-                       }
-                       if (withMetadata && node.hasProperty(Property.JCR_UUID))
-                               attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
-                       attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName());
-                       writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes());
-               } else {
-                       if (withMetadata && node.hasProperty(Property.JCR_UUID)) {
-                               writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id",
-                                               "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString());
-                       } else {
-                               writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
-                       }
-                       // name
-                       writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name");
-                       writer.append(node.getName());
-                       writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name");
-               }
-
-               // mixins
-               if (withMetadata) {
-                       for (NodeType mixin : node.getMixinNodeTypes()) {
-                               writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
-                               writer.append(mixin.getName());
-                               writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes");
-                       }
-               }
-
-               // properties as elements
-               if (propertiesAsElements) {
-                       PropertyIterator pit = node.getProperties();
-                       properties: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               if (p.isMultiple()) {
-                                       for (Value value : p.getValues()) {
-                                               writeStart(writer, withPrefix(p.getName(), withPrefix));
-                                               writer.write(value.getString());
-                                               writeEnd(writer, withPrefix(p.getName(), withPrefix));
-                                       }
-                               } else {
-                                       Value value = p.getValue();
-                                       String pName = p.getName();
-                                       if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID)
-                                                       || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY)
-                                                       || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY)))
-                                               continue properties;
-                                       writeStart(writer, withPrefix(p.getName(), withPrefix));
-                                       writer.write(value.getString());
-                                       writeEnd(writer, withPrefix(p.getName(), withPrefix));
-                               }
-                       }
-               }
-
-               // children
-               if (node.hasNodes()) {
-                       if (depth == null || depth > 0) {
-                               NodeIterator nit = node.getNodes();
-                               while (nit.hasNext()) {
-                                       toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix,
-                                                       propertiesAsElements);
-                               }
-                       }
-                       writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix));
-               }
-       }
-
-       private static String withPrefix(String str, boolean withPrefix) {
-               if (withPrefix)
-                       return str;
-               int index = str.indexOf(':');
-               if (index < 0)
-                       return str;
-               return str.substring(index + 1);
-       }
-
-       private static void writeStart(Writer writer, String tagName) throws IOException {
-               writer.append('<');
-               writer.append(tagName);
-               writer.append('>');
-       }
-
-       private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException {
-               writer.append('<');
-               writer.append(tagName);
-               writer.append(' ');
-               writer.append(attr);
-               writer.append("=\"");
-               writer.append(value);
-               writer.append("\">");
-       }
-
-       private static void writeStart(Writer writer, String tagName, Map<String, String> attrs, boolean hasChildren)
-                       throws IOException {
-               writer.append('<');
-               writer.append(tagName);
-               for (String attr : attrs.keySet()) {
-                       writer.append(' ');
-                       writer.append(attr);
-                       writer.append("=\"");
-                       writer.append(attrs.get(attr));
-                       writer.append('\"');
-               }
-               if (hasChildren)
-                       writer.append('>');
-               else
-                       writer.append("/>");
-       }
-
-       private static void writeEnd(Writer writer, String tagName) throws IOException {
-               writer.append("</");
-               writer.append(tagName);
-               writer.append('>');
-       }
-
-       /** Singleton. */
-       private JcrXmlUtils() {
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl
deleted file mode 100644 (file)
index 813d065..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
-  <xsl:output method="xml" indent="yes"/>
-  <xsl:template match="/|comment()|processing-instruction()">
-    <xsl:copy>
-      <xsl:apply-templates/>
-    </xsl:copy>
-  </xsl:template>
-  <xsl:template match="*">
-    <xsl:element name="{local-name()}">
-      <xsl:apply-templates select="@*|node()"/>
-    </xsl:element>
-  </xsl:template>
-  <xsl:template match="@*">
-    <xsl:attribute name="{local-name()}">
-      <xsl:value-of select="."/>
-    </xsl:attribute>
-  </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java
deleted file mode 100644 (file)
index 3c8f296..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.argeo.maintenance;
-
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.util.naming.Distinguished;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Make sure roles and access rights are properly configured. */
-public abstract class AbstractMaintenanceService {
-       private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class);
-
-       private Repository repository;
-//     private UserAdminService userAdminService;
-       private UserAdmin userAdmin;
-       private WorkTransaction userTransaction;
-
-       public void init() {
-               makeSureRolesExists(getRequiredRoles());
-               configureStandardRoles();
-
-               Set<String> workspaceNames = getWorkspaceNames();
-               if (workspaceNames == null || workspaceNames.isEmpty()) {
-                       configureJcr(repository, null);
-               } else {
-                       for (String workspaceName : workspaceNames)
-                               configureJcr(repository, workspaceName);
-               }
-       }
-
-       /** Configures a workspace. */
-       protected void configureJcr(Repository repository, String workspaceName) {
-               Session adminSession;
-               try {
-                       adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
-               } catch (RuntimeException e1) {
-                       if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
-                               Session defaultAdminSession = CmsJcrUtils.openDataAdminSession(repository, null);
-                               try {
-                                       defaultAdminSession.getWorkspace().createWorkspace(workspaceName);
-                                       log.info("Created JCR workspace " + workspaceName);
-                               } catch (RepositoryException e) {
-                                       throw new IllegalStateException("Cannot create workspace " + workspaceName, e);
-                               } finally {
-                                       Jcr.logout(defaultAdminSession);
-                               }
-                               adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName);
-                       } else
-                               throw e1;
-               }
-               try {
-                       if (prepareJcrTree(adminSession)) {
-                               configurePrivileges(adminSession);
-                       }
-               } catch (RepositoryException | IOException e) {
-                       throw new IllegalStateException("Cannot initialise JCR data layer.", e);
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-       /** To be overridden. */
-       protected Set<String> getWorkspaceNames() {
-               return null;
-       }
-
-       /**
-        * To be overridden in order to programmatically set relationships between
-        * roles. Does nothing by default.
-        */
-       protected void configureStandardRoles() {
-       }
-
-       /**
-        * Creates the base JCR tree structure expected for this app if necessary.
-        * 
-        * Expects a clean session ({@link Session#hasPendingChanges()} should return
-        * false) and saves it once the changes have been done. Thus the session can be
-        * rolled back if an exception occurs.
-        * 
-        * @return true if something as been updated
-        */
-       public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
-               return false;
-       }
-
-       /**
-        * Adds app specific default privileges.
-        * 
-        * Expects a clean session ({@link Session#hasPendingChanges()} should return
-        * false} and saves it once the changes have been done. Thus the session can be
-        * rolled back if an exception occurs.
-        * 
-        * Warning: no check is done and corresponding privileges are always added, so
-        * only call this when necessary
-        */
-       public void configurePrivileges(Session session) throws RepositoryException {
-       }
-
-       /** The system roles that must be available in the system. */
-       protected Set<String> getRequiredRoles() {
-               return new HashSet<>();
-       }
-
-       public void destroy() {
-
-       }
-
-       /*
-        * UTILITIES
-        */
-
-       /** Create these roles as group if they don't exist. */
-       protected void makeSureRolesExists(EnumSet<? extends Distinguished> enumSet) {
-               makeSureRolesExists(Distinguished.enumToDns(enumSet));
-       }
-
-       /** Create these roles as group if they don't exist. */
-       protected void makeSureRolesExists(Set<String> requiredRoles) {
-               if (requiredRoles == null)
-                       return;
-               if (getUserAdmin() == null) {
-                       log.warn("No user admin service available, cannot make sure that role exists");
-                       return;
-               }
-               for (String role : requiredRoles) {
-                       Role systemRole = getUserAdmin().getRole(role);
-                       if (systemRole == null) {
-                               try {
-                                       getUserTransaction().begin();
-                                       getUserAdmin().createRole(role, Role.GROUP);
-                                       getUserTransaction().commit();
-                                       log.info("Created role " + role);
-                               } catch (Exception e) {
-                                       try {
-                                               getUserTransaction().rollback();
-                                       } catch (Exception e1) {
-                                               // silent
-                                       }
-                                       throw new IllegalStateException("Cannot create role " + role, e);
-                               }
-                       }
-               }
-       }
-
-       /** Add a user or group to a group. */
-       protected void addToGroup(String groupToAddDn, String groupDn) {
-               if (groupToAddDn.contentEquals(groupDn)) {
-                       if (log.isTraceEnabled())
-                               log.trace("Ignore adding group " + groupDn + " to itself");
-                       return;
-               }
-
-               if (getUserAdmin() == null) {
-                       log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn);
-                       return;
-               }
-               Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn);
-               if (groupToAdd == null)
-                       throw new IllegalArgumentException("Group " + groupToAddDn + " not found");
-               Group group = (Group) getUserAdmin().getRole(groupDn);
-               if (group == null)
-                       throw new IllegalArgumentException("Group " + groupDn + " not found");
-               try {
-                       getUserTransaction().begin();
-                       if (group.addMember(groupToAdd))
-                               log.info("Added " + groupToAddDn + " to " + group);
-                       getUserTransaction().commit();
-               } catch (Exception e) {
-                       try {
-                               getUserTransaction().rollback();
-                       } catch (Exception e1) {
-                               // silent
-                       }
-                       throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn);
-               }
-       }
-
-       /*
-        * DEPENDENCY INJECTION
-        */
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-//     public void setUserAdminService(UserAdminService userAdminService) {
-//             this.userAdminService = userAdminService;
-//     }
-
-       protected WorkTransaction getUserTransaction() {
-               return userTransaction;
-       }
-
-       protected UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-       public void setUserAdmin(UserAdmin userAdmin) {
-               this.userAdmin = userAdmin;
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java
deleted file mode 100644 (file)
index ebb8c53..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.argeo.maintenance;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Register one or many roles via a user admin service. Does nothing if the role
- * is already registered.
- */
-public class SimpleRoleRegistration implements Runnable {
-       private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class);
-
-       private String role;
-       private List<String> roles = new ArrayList<String>();
-       private UserAdmin userAdmin;
-       private WorkTransaction userTransaction;
-
-       @Override
-       public void run() {
-               try {
-                       userTransaction.begin();
-                       if (role != null && !roleExists(role))
-                               newRole(toDn(role));
-
-                       for (String r : roles)
-                               if (!roleExists(r))
-                                       newRole(toDn(r));
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Cannot rollback", e1);
-                       }
-                       throw new IllegalArgumentException("Cannot add roles", e);
-               }
-       }
-
-       private boolean roleExists(String role) {
-               return userAdmin.getRole(toDn(role).toString()) != null;
-       }
-
-       protected void newRole(LdapName r) {
-               userAdmin.createRole(r.toString(), Role.GROUP);
-               log.info("Added role " + r + " required by application.");
-       }
-
-       public void register(UserAdmin userAdminService, Map<?, ?> properties) {
-               this.userAdmin = userAdminService;
-               run();
-       }
-
-       protected LdapName toDn(String name) {
-               try {
-                       return new LdapName("cn=" + name + ",ou=roles,ou=node");
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Badly formatted role name " + name, e);
-               }
-       }
-
-       public void setRole(String role) {
-               this.role = role;
-       }
-
-       public void setRoles(List<String> roles) {
-               this.roles = roles;
-       }
-
-       public void setUserAdmin(UserAdmin userAdminService) {
-               this.userAdmin = userAdminService;
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java
deleted file mode 100644 (file)
index ef83c1f..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/** XML handler serialising a JCR system view. */
-public class BackupContentHandler extends DefaultHandler {
-       final static int MAX_DEPTH = 1024;
-       final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
-       final static String SV_PREFIX = "sv";
-       // elements
-       final static String NODE = "node";
-       final static String PROPERTY = "property";
-       final static String VALUE = "value";
-       // attributes
-       final static String NAME = "name";
-       final static String MULTIPLE = "multiple";
-       final static String TYPE = "type";
-
-       // values
-       final static String BINARY = "Binary";
-       final static String JCR_CONTENT = "jcr:content";
-
-       private Writer out;
-       private Session session;
-       private Set<String> contentPaths = new TreeSet<>();
-
-       boolean prettyPrint = true;
-
-       private final String parentPath;
-
-//     private boolean inSystem = false;
-
-       public BackupContentHandler(Writer out, Node node) {
-               super();
-               this.out = out;
-               this.session = Jcr.getSession(node);
-               parentPath = Jcr.getParentPath(node);
-       }
-
-       private int currentDepth = -1;
-       private String[] currentPath = new String[MAX_DEPTH];
-
-       private boolean currentPropertyIsMultiple = false;
-       private String currentEncoded = null;
-       private Base64.Encoder base64encore = Base64.getEncoder();
-
-       @Override
-       public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
-               boolean isNode;
-               boolean isProperty;
-               switch (localName) {
-               case NODE:
-                       isNode = true;
-                       isProperty = false;
-                       break;
-               case PROPERTY:
-                       isNode = false;
-                       isProperty = true;
-                       break;
-               default:
-                       isNode = false;
-                       isProperty = false;
-               }
-
-               if (isNode) {
-                       String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
-                       currentDepth = currentDepth + 1;
-//                     if (currentDepth >= 0)
-                       currentPath[currentDepth] = nodeName;
-//                     System.out.println(getCurrentPath() + " , depth=" + currentDepth);
-//                     if ("jcr:system".equals(nodeName)) {
-//                             inSystem = true;
-//                     }
-               }
-//             if (inSystem)
-//                     return;
-
-               if (SV_NAMESPACE_URI.equals(uri))
-                       try {
-                               if (prettyPrint) {
-                                       if (isNode) {
-                                               out.write(spaces());
-                                               out.write("<!-- ");
-                                               out.write(getCurrentJcrPath());
-                                               out.write(" -->\n");
-                                               out.write(spaces());
-                                       } else if (isProperty)
-                                               out.write(spaces());
-                                       else if (currentPropertyIsMultiple)
-                                               out.write(spaces());
-                               }
-
-                               out.write("<");
-                               out.write(SV_PREFIX + ":" + localName);
-                               if (isProperty)
-                                       currentPropertyIsMultiple = false; // always reset
-                               for (int i = 0; i < attributes.getLength(); i++) {
-                                       String ns = attributes.getURI(i);
-                                       if (SV_NAMESPACE_URI.equals(ns)) {
-                                               String attrName = attributes.getLocalName(i);
-                                               String attrValue = attributes.getValue(i);
-                                               out.write(" ");
-                                               out.write(SV_PREFIX + ":" + attrName);
-                                               out.write("=");
-                                               out.write("\"");
-                                               out.write(attrValue);
-                                               out.write("\"");
-                                               if (isProperty) {
-                                                       if (MULTIPLE.equals(attrName))
-                                                               currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
-                                                       else if (TYPE.equals(attrName)) {
-                                                               if (BINARY.equals(attrValue)) {
-                                                                       if (JCR_CONTENT.equals(getCurrentName())) {
-                                                                               contentPaths.add(getCurrentJcrPath());
-                                                                       } else {
-                                                                               Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName)
-                                                                                               .getBinary();
-                                                                               try (InputStream in = binary.getStream()) {
-                                                                                       currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
-                                                                               } finally {
-
-                                                                               }
-                                                                       }
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                               if (isNode && currentDepth == 0) {
-                                       // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
-                                       out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\"");
-                               }
-                               out.write(">");
-
-                               if (prettyPrint)
-                                       if (isNode)
-                                               out.write("\n");
-                                       else if (isProperty && currentPropertyIsMultiple)
-                                               out.write("\n");
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       } catch (RepositoryException e) {
-                               throw new JcrException(e);
-                       }
-       }
-
-       @Override
-       public void endElement(String uri, String localName, String qName) throws SAXException {
-               boolean isNode = localName.equals(NODE);
-               boolean isValue = localName.equals(VALUE);
-               if (prettyPrint)
-                       if (!isValue)
-                               try {
-                                       if (isNode || currentPropertyIsMultiple)
-                                               out.write(spaces());
-                               } catch (IOException e1) {
-                                       throw new RuntimeException(e1);
-                               }
-               if (isNode) {
-//                     System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
-//                     if (currentDepth > 0)
-                       currentPath[currentDepth] = null;
-                       currentDepth = currentDepth - 1;
-//                     if (inSystem) {
-//                             // System.out.println("Skip " + getCurrentPath()+" ,
-//                             // currentDepth="+currentDepth);
-//                             if (currentDepth == 0) {
-//                                     inSystem = false;
-//                                     return;
-//                             }
-//                     }
-               }
-//             if (inSystem)
-//                     return;
-               if (SV_NAMESPACE_URI.equals(uri))
-                       try {
-                               if (isValue && currentEncoded != null) {
-                                       out.write(currentEncoded);
-                               }
-                               currentEncoded = null;
-                               out.write("</");
-                               out.write(SV_PREFIX + ":" + localName);
-                               out.write(">");
-                               if (prettyPrint)
-                                       if (!isValue)
-                                               out.write("\n");
-                                       else {
-                                               if (currentPropertyIsMultiple)
-                                                       out.write("\n");
-                                       }
-                               if (currentDepth == 0)
-                                       out.flush();
-                       } catch (IOException e) {
-                               throw new RuntimeException(e);
-                       }
-
-       }
-
-       private char[] spaces() {
-               char[] arr = new char[currentDepth];
-               Arrays.fill(arr, ' ');
-               return arr;
-       }
-
-       @Override
-       public void characters(char[] ch, int start, int length) throws SAXException {
-//             if (inSystem)
-//                     return;
-               try {
-                       out.write(ch, start, length);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       protected String getCurrentName() {
-               assert currentDepth >= 0;
-//             if (currentDepth == 0)
-//                     return "jcr:root";
-               return currentPath[currentDepth];
-       }
-
-       protected String getCurrentJcrPath() {
-//             if (currentDepth == 0)
-//                     return "/";
-               StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath);
-               for (int i = 0; i <= currentDepth; i++) {
-//                     if (i != 0)
-                       sb.append('/');
-                       sb.append(currentPath[i]);
-               }
-               return sb.toString();
-       }
-
-       public Set<String> getContentPaths() {
-               return contentPaths;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java
deleted file mode 100644 (file)
index 00d4be8..0000000
+++ /dev/null
@@ -1,448 +0,0 @@
-package org.argeo.maintenance.backup;
-
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.net.URI;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipOutputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-/**
- * Performs a backup of the data based only on programmatic interfaces. Useful
- * for migration or live backup. Physical backups of the underlying file
- * systems, databases, LDAP servers, etc. should be performed for disaster
- * recovery.
- */
-public class LogicalBackup implements Runnable {
-       private final static CmsLog log = CmsLog.getLog(LogicalBackup.class);
-
-       public final static String WORKSPACES_BASE = "workspaces/";
-       public final static String FILES_BASE = "files/";
-       public final static String OSGI_BASE = "share/osgi/";
-
-       public final static String JCR_SYSTEM = "jcr:system";
-       public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage";
-
-       private final Repository repository;
-       private String defaultWorkspace;
-       private final BundleContext bundleContext;
-
-       private final ZipOutputStream zout;
-       private final Path basePath;
-
-       private ExecutorService executorService;
-
-       private boolean performSoftwareBackup = false;
-
-       private Map<String, String> checksums = new TreeMap<>();
-
-       private int threadCount = 5;
-
-       private boolean backupFailed = false;
-
-       public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
-               this.repository = repository;
-               this.zout = null;
-               this.basePath = basePath;
-               this.bundleContext = bundleContext;
-       }
-
-       @Override
-       public void run() {
-               try {
-                       log.info("Start logical backup to " + basePath);
-                       perform();
-               } catch (Exception e) {
-                       log.error("Unexpected exception when performing logical backup", e);
-                       throw new IllegalStateException("Logical backup failed", e);
-               }
-
-       }
-
-       public void perform() throws RepositoryException, IOException {
-               if (executorService != null && !executorService.isTerminated())
-                       throw new IllegalStateException("Another backup is running");
-               executorService = Executors.newFixedThreadPool(threadCount);
-               long begin = System.currentTimeMillis();
-               // software backup
-               if (bundleContext != null && performSoftwareBackup)
-                       executorService.submit(() -> performSoftwareBackup(bundleContext));
-
-               // data backup
-               Session defaultSession = login(null);
-               defaultWorkspace = defaultSession.getWorkspace().getName();
-               try {
-                       String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
-                       workspaces: for (String workspaceName : workspaceNames) {
-                               if ("security".equals(workspaceName))
-                                       continue workspaces;
-                               performDataBackup(workspaceName);
-                       }
-               } finally {
-                       JcrUtils.logoutQuietly(defaultSession);
-                       executorService.shutdown();
-                       try {
-                               executorService.awaitTermination(24, TimeUnit.HOURS);
-                       } catch (InterruptedException e) {
-                               // silent
-                               throw new IllegalStateException("Backup was interrupted before completion", e);
-                       }
-               }
-               // versions
-               executorService = Executors.newFixedThreadPool(threadCount);
-               try {
-                       performVersionsBackup();
-               } finally {
-                       executorService.shutdown();
-                       try {
-                               executorService.awaitTermination(24, TimeUnit.HOURS);
-                       } catch (InterruptedException e) {
-                               // silent
-                               throw new IllegalStateException("Backup was interrupted before completion", e);
-                       }
-               }
-               long duration = System.currentTimeMillis() - begin;
-               if (isBackupFailed())
-                       log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s");
-               else
-                       log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s");
-       }
-
-       protected void performDataBackup(String workspaceName) throws RepositoryException, IOException {
-               Session session = login(workspaceName);
-               try {
-                       nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) {
-                               if (isBackupFailed())
-                                       return;
-                               Node nodeToExport = nit.nextNode();
-                               if (JCR_SYSTEM.equals(nodeToExport.getName()))
-                                       continue nodes;
-                               String nodePath = nodeToExport.getPath();
-                               Future<Set<String>> contentPathsFuture = executorService
-                                               .submit(() -> performNodeBackup(workspaceName, nodePath));
-                               executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture));
-                       }
-               } finally {
-                       Jcr.logout(session);
-               }
-       }
-
-       protected void performVersionsBackup() throws RepositoryException, IOException {
-               Session session = login(defaultWorkspace);
-               Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH);
-               try {
-                       for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) {
-                               Node nodeToExport = nit.nextNode();
-                               String nodePath = nodeToExport.getPath();
-                               if (isBackupFailed())
-                                       return;
-                               Future<Set<String>> contentPathsFuture = executorService
-                                               .submit(() -> performNodeBackup(defaultWorkspace, nodePath));
-                               executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture));
-                       }
-               } finally {
-                       Jcr.logout(session);
-               }
-
-       }
-
-       protected Set<String> performNodeBackup(String workspaceName, String nodePath) {
-               Session session = login(workspaceName);
-               try {
-                       Node nodeToExport = session.getNode(nodePath);
-//                     String nodeName = nodeToExport.getName();
-//             if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:"))
-//                     continue nodes;
-//             // TODO make it more robust / configurable
-//             if (nodeName.equals("user"))
-//                     continue nodes;
-                       String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml";
-                       OutputStream xmlOut = openOutputStream(relativePath);
-                       BackupContentHandler contentHandler;
-                       try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
-                               contentHandler = new BackupContentHandler(writer, nodeToExport);
-                               session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false);
-                               if (log.isDebugEnabled())
-                                       log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath);
-                       }
-
-                       // Files
-                       Set<String> contentPaths = contentHandler.getContentPaths();
-                       return contentPaths;
-               } catch (Exception e) {
-                       markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e);
-                       throw new ThreadDeath();
-               } finally {
-                       Jcr.logout(session);
-               }
-       }
-
-       protected void performFilesBackup(String workspaceName, Future<Set<String>> contentPathsFuture) {
-               Set<String> contentPaths;
-               try {
-                       contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS);
-               } catch (InterruptedException | ExecutionException | TimeoutException e1) {
-                       markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1);
-                       return;
-               }
-               if (contentPaths == null || contentPaths.size() == 0)
-                       return;
-               Session session = login(workspaceName);
-               try {
-                       String workspacesFilesBasePath = FILES_BASE + workspaceName;
-                       for (String path : contentPaths) {
-                               if (isBackupFailed())
-                                       return;
-                               Node contentNode = session.getNode(path);
-                               Binary binary = null;
-                               try {
-                                       binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
-                                       String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath();
-
-                                       // checksum
-                                       boolean skip = false;
-                                       String checksum = null;
-                                       if (session instanceof JackrabbitSession) {
-                                               JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue();
-//                                     ReferenceBinary referenceBinary = (ReferenceBinary) binary;
-                                               checksum = value.getContentIdentity();
-                                       }
-                                       if (checksum != null) {
-                                               if (!checksums.containsKey(checksum)) {
-                                                       checksums.put(checksum, fileRelativePath);
-                                               } else {
-                                                       skip = true;
-                                                       String sourcePath = checksums.get(checksum);
-                                                       if (log.isTraceEnabled())
-                                                               log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum);
-                                                       createLink(sourcePath, fileRelativePath);
-                                                       try (Writer writerSum = new OutputStreamWriter(
-                                                                       openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) {
-                                                               writerSum.write(checksum);
-                                                       }
-                                               }
-                                       }
-
-                                       // copy file
-                                       if (!skip)
-                                               try (InputStream in = binary.getStream();
-                                                               OutputStream out = openOutputStream(fileRelativePath)) {
-                                                       IOUtils.copy(in, out);
-                                                       if (log.isTraceEnabled())
-                                                               log.trace("Workspace " + workspaceName + ": file content exported to "
-                                                                               + fileRelativePath);
-                                               }
-                               } finally {
-                                       JcrUtils.closeQuietly(binary);
-                               }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath);
-               } catch (Exception e) {
-                       markBackupFailed("Cannot backup files from " + workspaceName + ":", e);
-               } finally {
-                       Jcr.logout(session);
-               }
-       }
-
-       protected OutputStream openOutputStream(String relativePath) throws IOException {
-               if (zout != null) {
-                       ZipEntry entry = new ZipEntry(relativePath);
-                       zout.putNextEntry(entry);
-                       return zout;
-               } else if (basePath != null) {
-                       Path targetPath = basePath.resolve(Paths.get(relativePath));
-                       Files.createDirectories(targetPath.getParent());
-                       return Files.newOutputStream(targetPath);
-               } else {
-                       throw new UnsupportedOperationException();
-               }
-       }
-
-       protected void createLink(String source, String target) throws IOException {
-               if (zout != null) {
-                       // TODO implement for zip
-                       throw new UnsupportedOperationException();
-               } else if (basePath != null) {
-                       Path sourcePath = basePath.resolve(Paths.get(source));
-                       Path targetPath = basePath.resolve(Paths.get(target));
-                       Path relativeSource = targetPath.getParent().relativize(sourcePath);
-                       Files.createDirectories(targetPath.getParent());
-                       Files.createSymbolicLink(targetPath, relativeSource);
-               } else {
-                       throw new UnsupportedOperationException();
-               }
-       }
-
-       protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
-               if (zout != null) {
-                       zout.closeEntry();
-               } else if (basePath != null) {
-                       out.close();
-               } else {
-                       throw new UnsupportedOperationException();
-               }
-       }
-
-       protected Session login(String workspaceName) {
-               if (bundleContext != null) {// local
-                       return CmsJcrUtils.openDataAdminSession(repository, workspaceName);
-               } else {// remote
-                       try {
-                               return repository.login(workspaceName);
-                       } catch (RepositoryException e) {
-                               throw new JcrException(e);
-                       }
-               }
-       }
-
-       public final static void main(String[] args) throws Exception {
-               if (args.length == 0) {
-                       printUsage("No argument");
-                       System.exit(1);
-               }
-               URI uri = new URI(args[0]);
-               Repository repository = createRemoteRepository(uri);
-               Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir"));
-               if (!Files.exists(basePath))
-                       Files.createDirectories(basePath);
-               LogicalBackup backup = new LogicalBackup(null, repository, basePath);
-               backup.run();
-       }
-
-       private static void printUsage(String errorMessage) {
-               if (errorMessage != null)
-                       System.err.println(errorMessage);
-               System.out.println("Usage: LogicalBackup <remote URL> [<target directory>]");
-
-       }
-
-       protected static Repository createRemoteRepository(URI uri) throws RepositoryException {
-               RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory();
-               Map<String, String> params = new HashMap<String, String>();
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString());
-               // TODO make it configurable
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE);
-               return repositoryFactory.getRepository(params);
-       }
-
-       public void performSoftwareBackup(BundleContext bundleContext) {
-               String bootBasePath = OSGI_BASE + "boot";
-               Bundle[] bundles = bundleContext.getBundles();
-               for (Bundle bundle : bundles) {
-                       String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar";
-                       Dictionary<String, String> headers = bundle.getHeaders();
-                       Manifest manifest = new Manifest();
-                       Enumeration<String> headerKeys = headers.keys();
-                       while (headerKeys.hasMoreElements()) {
-                               String headerKey = headerKeys.nextElement();
-                               String headerValue = headers.get(headerKey);
-                               manifest.getMainAttributes().putValue(headerKey, headerValue);
-                       }
-                       try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
-                               Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
-                               resources: while (resourcePaths.hasMoreElements()) {
-                                       URL entryUrl = resourcePaths.nextElement();
-                                       String entryPath = entryUrl.getPath();
-                                       if (entryPath.equals(""))
-                                               continue resources;
-                                       if (entryPath.endsWith("/"))
-                                               continue resources;
-                                       String entryName = entryPath.substring(1);// remove first '/'
-                                       if (entryUrl.getPath().equals("/META-INF/"))
-                                               continue resources;
-                                       if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
-                                               continue resources;
-                                       // dev
-                                       if (entryUrl.getPath().startsWith("/target"))
-                                               continue resources;
-                                       if (entryUrl.getPath().startsWith("/src"))
-                                               continue resources;
-                                       if (entryUrl.getPath().startsWith("/ext"))
-                                               continue resources;
-
-                                       if (entryName.startsWith("bin/")) {// dev
-                                               entryName = entryName.substring("bin/".length());
-                                       }
-
-                                       ZipEntry entry = new ZipEntry(entryName);
-                                       try (InputStream in = entryUrl.openStream()) {
-                                               try {
-                                                       jarOut.putNextEntry(entry);
-                                               } catch (ZipException e) {// duplicate
-                                                       continue resources;
-                                               }
-                                               IOUtils.copy(in, jarOut);
-                                               jarOut.closeEntry();
-//                                             log.info(entryUrl);
-                                       } catch (FileNotFoundException e) {
-                                               log.warn(entryUrl + ": " + e.getMessage());
-                                       }
-                               }
-                       } catch (IOException e1) {
-                               throw new RuntimeException("Cannot export bundle " + bundle, e1);
-                       }
-               }
-               if (log.isDebugEnabled())
-                       log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath);
-
-       }
-
-       protected synchronized void markBackupFailed(Object message, Exception e) {
-               log.error(message, e);
-               backupFailed = true;
-               notifyAll();
-               if (executorService != null)
-                       executorService.shutdownNow();
-       }
-
-       protected boolean isBackupFailed() {
-               return backupFailed;
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java
deleted file mode 100644 (file)
index 122c967..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.maintenance.backup;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.osgi.framework.BundleContext;
-
-/** Restores a backup in the format defined by {@link LogicalBackup}. */
-public class LogicalRestore implements Runnable {
-       private final static CmsLog log = CmsLog.getLog(LogicalRestore.class);
-
-       private final Repository repository;
-       private final BundleContext bundleContext;
-       private final Path basePath;
-
-       public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
-               this.repository = repository;
-               this.basePath = basePath;
-               this.bundleContext = bundleContext;
-       }
-
-       @Override
-       public void run() {
-               Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
-               try {
-                       // import jcr:system first
-//                     Session defaultSession = NodeUtils.openDataAdminSession(repository, null);
-//                     try (DirectoryStream<Path> xmls = Files.newDirectoryStream(
-//                                     workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH),
-//                                     "*.xml")) {
-//                             for (Path xml : xmls) {
-//                                     try (InputStream in = Files.newInputStream(xml)) {
-//                                             defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in,
-//                                                             ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-//                                             if (log.isDebugEnabled())
-//                                                     log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":");
-//                                     }
-//                             }
-//                     } finally {
-//                             Jcr.logout(defaultSession);
-//                     }
-
-                       // non-system content
-                       try (DirectoryStream<Path> workspaceDirs = Files.newDirectoryStream(workspaces)) {
-                               for (Path workspacePath : workspaceDirs) {
-                                       String workspaceName = workspacePath.getFileName().toString();
-                                       Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
-                                       try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspacePath, "*.xml")) {
-                                               xmls: for (Path xml : xmls) {
-                                                       if (xml.getFileName().toString().startsWith("rep:"))
-                                                               continue xmls;
-                                                       try (InputStream in = Files.newInputStream(xml)) {
-                                                               session.getWorkspace().importXML("/", in,
-                                                                               ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
-                                                               if (log.isDebugEnabled())
-                                                                       log.debug("Restored " + xml + " to workspace " + workspaceName);
-                                                       }
-                                               }
-                                       } finally {
-                                               Jcr.logout(session);
-                                       }
-                               }
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot restore backup from " + basePath, e);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot restore backup from " + basePath, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java
deleted file mode 100644 (file)
index a61e19b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo Node backup utilities. */
-package org.argeo.maintenance.backup;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java
deleted file mode 100644 (file)
index ef40ab3..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.maintenance.internal;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.jcr.Repository;
-
-import org.argeo.maintenance.backup.LogicalBackup;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-public class Activator implements BundleActivator {
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-               // Start backup
-               Repository repository = context.getService(context.getServiceReference(Repository.class));
-               Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
-               LogicalBackup backup = new LogicalBackup(context, repository, basePath);
-               backup.run();
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java
deleted file mode 100644 (file)
index 1ce974c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Utilities for the maintenance of an Argeo Node. */
-package org.argeo.maintenance;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java
deleted file mode 100644 (file)
index bffe531..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.core.security.authorization.acl.ACLProvider;
-
-/** Argeo specific access control provider */
-public class ArgeoAccessControlProvider extends ACLProvider {
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       @Override
-       public void init(Session systemSession, Map configuration) throws RepositoryException {
-               if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS))
-                       configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true");
-               if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS))
-                       configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true");
-               super.init(systemSession, configuration);
-       }
-
-       @Override
-       public boolean canAccessRoot(Set<Principal> principals) throws RepositoryException {
-               return super.canAccessRoot(principals);
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java
deleted file mode 100644 (file)
index 7464078..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.security.jackrabbit;
-
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.security.Privilege;
-
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.security.DefaultAccessManager;
-import org.apache.jackrabbit.spi.Path;
-
-/**
- * Intermediary class in order to have a consistent naming in config files. Does
- * nothing for the time being, but may in the future.
- */
-public class ArgeoAccessManager extends DefaultAccessManager {
-
-       @Override
-       public boolean canRead(Path itemPath, ItemId itemId)
-                       throws RepositoryException {
-               return super.canRead(itemPath, itemId);
-       }
-
-       @Override
-       public Privilege[] getPrivileges(String absPath)
-                       throws PathNotFoundException, RepositoryException {
-               return super.getPrivileges(absPath);
-       }
-
-       @Override
-       public boolean hasPrivileges(String absPath, Privilege[] privileges)
-                       throws PathNotFoundException, RepositoryException {
-               return super.hasPrivileges(absPath, privileges);
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java
deleted file mode 100644 (file)
index d679c45..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.security.jackrabbit;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.jackrabbit.core.security.authentication.AuthContext;
-
-/** Wraps a regular {@link LoginContext}, using the proper class loader. */
-class ArgeoAuthContext implements AuthContext {
-       private LoginContext lc;
-
-       public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) {
-               try {
-                       lc = new LoginContext(appName, subject, callbackHandler);
-               } catch (LoginException e) {
-                       throw new IllegalStateException("Cannot configure Jackrabbit login context", e);
-               }
-       }
-
-       @Override
-       public void login() throws LoginException {
-               lc.login();
-       }
-
-       @Override
-       public Subject getSubject() {
-               return lc.getSubject();
-       }
-
-       @Override
-       public void logout() throws LoginException {
-               lc.logout();
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
deleted file mode 100644 (file)
index 36ee547..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-package org.argeo.security.jackrabbit;
-
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.Set;
-
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.DefaultSecurityManager;
-import org.apache.jackrabbit.core.security.AMContext;
-import org.apache.jackrabbit.core.security.AccessManager;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.SystemPrincipal;
-import org.apache.jackrabbit.core.security.authentication.AuthContext;
-import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl;
-import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.DataAdminPrincipal;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.AnonymousPrincipal;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Customises Jackrabbit security. */
-public class ArgeoSecurityManager extends DefaultSecurityManager {
-       private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class);
-
-       private BundleContext cmsBundleContext = null;
-
-       public ArgeoSecurityManager() {
-               if (FrameworkUtil.getBundle(CmsSession.class) != null) {
-                       cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext();
-               }
-       }
-
-       public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName)
-                       throws RepositoryException {
-               checkInitialized();
-
-               CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(),
-                               adminId, anonymousId);
-               String appName = "Jackrabbit";
-               return new ArgeoAuthContext(appName, subject, cbHandler);
-       }
-
-       @Override
-       public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException {
-               synchronized (getSystemSession()) {
-                       return super.getAccessManager(session, amContext);
-               }
-       }
-
-       @Override
-       public UserManager getUserManager(Session session) throws RepositoryException {
-               synchronized (getSystemSession()) {
-                       return super.getUserManager(session);
-               }
-       }
-
-       @Override
-       protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
-               return super.createDefaultPrincipalProvider(moduleConfig);
-       }
-
-       /** Called once when the session is created */
-       @Override
-       public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
-               boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty();
-               boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
-               boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty();
-               Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
-               boolean isRegularUser = !userPrincipal.isEmpty();
-               CmsSession cmsSession = null;
-               if (cmsBundleContext != null) {
-                       cmsSession = CmsOsgiUtils.getCmsSession(cmsBundleContext, subject);
-                       if (log.isTraceEnabled())
-                               log.trace("Opening JCR session for CMS session " + cmsSession);
-               }
-
-               if (isAnonymous) {
-                       if (isDataAdmin || isJackrabbitSystem || isRegularUser)
-                               throw new IllegalStateException("Inconsistent " + subject);
-                       else
-                               return CmsConstants.ROLE_ANONYMOUS;
-               } else if (isRegularUser) {// must be before DataAdmin
-                       if (isAnonymous || isJackrabbitSystem)
-                               throw new IllegalStateException("Inconsistent " + subject);
-                       else {
-                               if (userPrincipal.size() > 1) {
-                                       StringBuilder buf = new StringBuilder();
-                                       for (X500Principal principal : userPrincipal)
-                                               buf.append(' ').append('\"').append(principal).append('\"');
-                                       throw new RuntimeException("Multiple user principals:" + buf);
-                               }
-                               return userPrincipal.iterator().next().getName();
-                       }
-               } else if (isDataAdmin) {
-                       if (isAnonymous || isJackrabbitSystem || isRegularUser)
-                               throw new IllegalStateException("Inconsistent " + subject);
-                       else {
-                               assert !subject.getPrincipals(AdminPrincipal.class).isEmpty();
-                               return CmsConstants.ROLE_DATA_ADMIN;
-                       }
-               } else if (isJackrabbitSystem) {
-                       if (isAnonymous || isDataAdmin || isRegularUser)
-                               throw new IllegalStateException("Inconsistent " + subject);
-                       else
-                               return super.getUserID(subject, workspaceName);
-               } else {
-                       throw new IllegalStateException("Unrecognized subject type: " + subject);
-               }
-       }
-
-       @Override
-       protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() {
-               WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager();
-               ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam);
-               if (log.isTraceEnabled())
-                       log.trace("Created workspace access manager");
-               return workspaceAccessManager;
-       }
-
-       private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager {
-               private final WorkspaceAccessManager wam;
-
-               public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) {
-                       super();
-                       this.wam = wam;
-               }
-
-               public void init(Session systemSession) throws RepositoryException {
-                       wam.init(systemSession);
-                       Repository repository = systemSession.getRepository();
-                       if (log.isTraceEnabled())
-                               log.trace("Initialised workspace access manager on repository " + repository
-                                               + ", systemSession workspace: " + systemSession.getWorkspace().getName());
-               }
-
-               public void close() throws RepositoryException {
-               }
-
-               public boolean grants(Set<Principal> principals, String workspaceName) throws RepositoryException {
-                       // TODO: implements finer access to workspaces
-                       if (log.isTraceEnabled())
-                               log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'");
-                       return true;
-                       // return wam.grants(principals, workspaceName);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java
deleted file mode 100644 (file)
index 0f63957..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-package org.argeo.security.jackrabbit;
-
-import java.util.Map;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.jackrabbit.core.security.AnonymousPrincipal;
-import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.argeo.api.cms.DataAdminPrincipal;
-
-/** JAAS login module used when initiating a new Jackrabbit session. */
-public class SystemJackrabbitLoginModule implements LoginModule {
-       private Subject subject;
-
-       @Override
-       public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
-                       Map<String, ?> options) {
-               this.subject = subject;
-       }
-
-       @Override
-       public boolean login() throws LoginException {
-               return true;
-       }
-
-       @Override
-       public boolean commit() throws LoginException {
-               Set<org.argeo.api.cms.AnonymousPrincipal> anonPrincipal = subject
-                               .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class);
-               if (!anonPrincipal.isEmpty()) {
-                       subject.getPrincipals().add(new AnonymousPrincipal());
-                       return true;
-               }
-
-               Set<DataAdminPrincipal> initPrincipal = subject.getPrincipals(DataAdminPrincipal.class);
-               if (!initPrincipal.isEmpty()) {
-                       subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID));
-                       return true;
-               }
-
-               Set<X500Principal> userPrincipal = subject.getPrincipals(X500Principal.class);
-               if (userPrincipal.isEmpty())
-                       throw new LoginException("Subject must be pre-authenticated");
-               if (userPrincipal.size() > 1)
-                       throw new LoginException("Multiple user principals " + userPrincipal);
-
-               return true;
-       }
-
-       @Override
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       @Override
-       public boolean logout() throws LoginException {
-               subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class));
-               subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class));
-               return true;
-       }
-}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java
deleted file mode 100644 (file)
index 8529cc2..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Integration of Jackrabbit with Argeo security model. */
-package org.argeo.security.jackrabbit;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/.classpath b/jcr/org.argeo.cms.ui/.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/jcr/org.argeo.cms.ui/.project b/jcr/org.argeo.cms.ui/.project
deleted file mode 100644 (file)
index e52eb8e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ui</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/jcr/org.argeo.cms.ui/META-INF/.gitignore b/jcr/org.argeo.cms.ui/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/jcr/org.argeo.cms.ui/bnd.bnd b/jcr/org.argeo.cms.ui/bnd.bnd
deleted file mode 100644 (file)
index c3c609c..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-Bundle-Activator: org.argeo.cms.ui.internal.Activator
-Bundle-ActivationPolicy: lazy
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.jface.window,\
-org.eclipse.core.commands,\
-javax.jcr.security,\
-org.eclipse.rap.fileupload;version="[2.1,4)",\
-org.eclipse.rap.rwt;version="[2.1,4)",\
-org.eclipse.rap.rwt.application;version="[2.1,4)",\
-org.eclipse.rap.rwt.client;version="[2.1,4)",\
-org.eclipse.rap.rwt.client.service;version="[2.1,4)",\
-org.eclipse.rap.rwt.service;version="[2.1,4)",\
-org.eclipse.rap.rwt.widgets;version="[2.1,4)",\
-org.osgi.*;version=0.0.0,\
-javax.servlet.*;version="[3,5)",\
-*
-
-## TODO: in order to enable single sourcing, we have introduced dummy RAP packages 
-# in the RCP specific bundle org.argeo.eclipse.ui.rap.
-# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, 
-# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x
-# We enable large RAP version range as a workaround that must be fixed 
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/build.properties b/jcr/org.argeo.cms.ui/build.properties
deleted file mode 100644 (file)
index c6baffa..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               icons/
diff --git a/jcr/org.argeo.cms.ui/icons/loading.gif b/jcr/org.argeo.cms.ui/icons/loading.gif
deleted file mode 100644 (file)
index 3288d10..0000000
Binary files a/jcr/org.argeo.cms.ui/icons/loading.gif and /dev/null differ
diff --git a/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png
deleted file mode 100644 (file)
index 0396506..0000000
Binary files a/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png and /dev/null differ
diff --git a/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png
deleted file mode 100644 (file)
index 8e3abb5..0000000
Binary files a/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png and /dev/null differ
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java
deleted file mode 100644 (file)
index 872142b..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.util.EventObject;
-
-/** Notify of the edition lifecycle */
-public class CmsEditionEvent extends EventObject {
-       private static final long serialVersionUID = 950914736016693110L;
-
-       public final static Integer START_EDITING = 0;
-       public final static Integer STOP_EDITING = 1;
-
-       private final Integer type;
-
-       public CmsEditionEvent(Object source, Integer type) {
-               super(source);
-               this.type = type;
-       }
-
-       public Integer getType() {
-               return type;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java
deleted file mode 100644 (file)
index 9df61dc..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.cms.ui;
-
-import org.argeo.api.cms.Cms2DSize;
-
-/** Commons constants */
-@Deprecated
-public interface CmsUiConstants {
-       // DATAKEYS
-//     public final static String STYLE = EclipseUiConstants.CSS_CLASS;
-//     public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT;
-       @Deprecated
-       /* RWT.CUSTOM_ITEM_HEIGHT */
-       public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight";
-
-       // EVENT DETAILS
-       @Deprecated
-       /* RWT.HYPERLINK */
-       public final static int HYPERLINK = 1 << 26;
-
-       // STANDARD RESOURCES
-       public final static String LOADING_IMAGE = "icons/loading.gif";
-
-       public final static String NO_IMAGE = "icons/noPic-square-640px.png";
-       public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320);
-       public final static Float NO_IMAGE_RATIO = 1f;
-       // MISCEALLENEOUS
-       String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm";
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java
deleted file mode 100644 (file)
index ec76321..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.cms.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.MvcProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Stateless factory building an SWT user interface given a JCR context. */
-@FunctionalInterface
-public interface CmsUiProvider extends MvcProvider<Composite, Node, Control> {
-       /**
-        * Initialises a user interface.
-        * 
-        * @param parent  the parent composite
-        * @param context a context node (holding the JCR underlying session), or null
-        */
-       Control createUi(Composite parent, Node context) throws RepositoryException;
-
-       @Override
-       default Control createUiPart(Composite parent, Node context) {
-               try {
-                       return createUi(parent, context);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot create UI for context " + context, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java
deleted file mode 100644 (file)
index 5d77c15..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.cms.ui;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-/** CmsUiProvider notified of initialisation with a system session. */
-public interface LifeCycleUiProvider extends CmsUiProvider {
-       public void init(Session adminSession) throws RepositoryException;
-
-       public void destroy();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java
deleted file mode 100644 (file)
index 4ce4688..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java
deleted file mode 100644 (file)
index 32b031b..0000000
+++ /dev/null
@@ -1,730 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java
deleted file mode 100644 (file)
index 9e931ba..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java
deleted file mode 100644 (file)
index 9927104..0000000
+++ /dev/null
@@ -1,913 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java
deleted file mode 100644 (file)
index 76e3f11..0000000
+++ /dev/null
@@ -1,522 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java
deleted file mode 100644 (file)
index cf0e5d3..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java
deleted file mode 100644 (file)
index 954cc03..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java
deleted file mode 100644 (file)
index 490d3a3..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java
deleted file mode 100644 (file)
index 0f557d4..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java
deleted file mode 100644 (file)
index 4140465..0000000
+++ /dev/null
@@ -1,323 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java
deleted file mode 100644 (file)
index 7fa00d9..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.argeo.cms.ui.eclipse.forms.editor;
-
-import org.argeo.cms.ui.eclipse.forms.FormToolkit;
-import org.eclipse.core.runtime.ListenerList;
-import org.eclipse.jface.dialogs.IPageChangeProvider;
-import org.eclipse.jface.dialogs.IPageChangedListener;
-import org.eclipse.jface.dialogs.PageChangedEvent;
-import org.eclipse.jface.util.SafeRunnable;
-
-/**
- * This class forms a base of multi-page form editors that typically use one or
- * more pages with forms and one page for raw source of the editor input.
- * <p>
- * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does
- * not cause the page control to be created. Page control is created when an
- * attempt is made to select the page in question. This allows editors with
- * several tabs and complex pages to open quickly.
- * <p>
- * Subclasses should extend this class and implement <code>addPages</code>
- * method. One of the two <code>addPage</code> methods should be called to
- * contribute pages to the editor. One adds complete (standalone) editors as
- * nested tabs. These editors will be created right away and will be hooked so
- * that key bindings, selection service etc. is compatible with the one for the
- * standalone case. The other method adds classes that implement
- * <code>IFormPage</code> interface. These pages will be created lazily and
- * they will share the common key binding and selection service. Since 3.1,
- * FormEditor is a page change provider. It allows listeners to attach to it and
- * get notified when pages are changed. This new API in JFace allows dynamic
- * help to update on page changes.
- * 
- * @since 1.0
- */
-// RAP [if] As RAP is still using workbench 3.4, the implementation of
-// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
-// with the adoption of workbench > 3.5
-//public abstract class FormEditor extends MultiPageEditorPart  {
-public abstract class FormEditor  implements
-        IPageChangeProvider {
-       private FormToolkit formToolkit;
-       
-       
-public FormToolkit getToolkit() {
-               return formToolkit;
-       }
-
-public void editorDirtyStateChanged() {
-       
-}
-
-public FormPage getActivePageInstance() {
-       return null;
-}
-
-       // RAP [if] As RAP is still using workbench 3.4, the implementation of
-// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
-// with the adoption of workbench > 3.5
-       private ListenerList pageListeners = new ListenerList();
-       
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
-     */
-    public void addPageChangedListener(IPageChangedListener listener) {
-        pageListeners.add(listener);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
-     */
-    public void removePageChangedListener(IPageChangedListener listener) {
-        pageListeners.remove(listener);
-    }
-    
-       private void firePageChanged(final PageChangedEvent event) {
-        Object[] listeners = pageListeners.getListeners();
-        for (int i = 0; i < listeners.length; ++i) {
-            final IPageChangedListener l = (IPageChangedListener) listeners[i];
-            SafeRunnable.run(new SafeRunnable() {
-                public void run() {
-                    l.pageChanged(event);
-                }
-            });
-        }
-    }
-// RAPEND [if]
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java
deleted file mode 100644 (file)
index 1511cf3..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-package org.argeo.cms.ui.eclipse.forms.editor;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.cms.ui.eclipse.forms.ManagedForm;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.swt.custom.BusyIndicator;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-/**
- * A base class that all pages that should be added to FormEditor must subclass.
- * Form page has an instance of PageForm that extends managed form. Subclasses
- * should override method 'createFormContent(ManagedForm)' to fill the form with
- * content. Note that page itself can be loaded lazily (on first open).
- * Consequently, the call to create the form content can come after the editor
- * has been opened for a while (in fact, it is possible to open and close the
- * editor and never create the form because no attempt has been made to show the
- * page).
- * 
- * @since 1.0
- */
-public class FormPage implements IFormPage {
-       private FormEditor editor;
-       private PageForm mform;
-       private int index;
-       private String id;
-       
-       private String partName;
-       
-       
-       
-       public void setPartName(String partName) {
-               this.partName = partName;
-       }
-       private static class PageForm extends ManagedForm {
-               public PageForm(FormPage page, ScrolledComposite form) {
-                       super(page.getEditor().getToolkit(), form);
-                       setContainer(page);
-               }
-               
-               public FormPage getPage() {
-                       return (FormPage)getContainer();
-               }
-               public void dirtyStateChanged() {
-                       getPage().getEditor().editorDirtyStateChanged();
-               }
-               public void staleStateChanged() {
-                       if (getPage().isActive())
-                               refresh();
-               }
-       }
-       /**
-        * A constructor that creates the page and initializes it with the editor.
-        * 
-        * @param editor
-        *            the parent editor
-        * @param id
-        *            the unique identifier
-        * @param title
-        *            the page title
-        */
-       public FormPage(FormEditor editor, String id, String title) {
-               this(id, title);
-               initialize(editor);
-       }
-       /**
-        * The constructor. The parent editor need to be passed in the
-        * <code>initialize</code> method if this constructor is used.
-        * 
-        * @param id
-        *            a unique page identifier
-        * @param title
-        *            a user-friendly page title
-        */
-       public FormPage(String id, String title) {
-               this.id = id;
-               setPartName(title);
-       }
-       /**
-        * Initializes the form page.
-        * 
-        * @see IEditorPart#init
-        */
-//     public void init(IEditorSite site, IEditorInput input) {
-//             setSite(site);
-//             setInput(input);
-//     }
-       /**
-        * Primes the form page with the parent editor instance.
-        * 
-        * @param editor
-        *            the parent editor
-        */
-       public void initialize(FormEditor editor) {
-               this.editor = editor;
-       }
-       /**
-        * Returns the parent editor.
-        * 
-        * @return parent editor instance
-        */
-       public FormEditor getEditor() {
-               return editor;
-       }
-       /**
-        * Returns the managed form owned by this page.
-        * 
-        * @return the managed form
-        */
-       public IManagedForm getManagedForm() {
-               return mform;
-       }
-       /**
-        * Implements the required method by refreshing the form when set active.
-        * Subclasses must call super when overriding this method.
-        */
-       public void setActive(boolean active) {
-               if (active) {
-                       // We are switching to this page - refresh it
-                       // if needed.
-                       if (mform != null)
-                               mform.refresh();
-               }
-       }
-       /**
-        * Tests if the page is active by asking the parent editor if this page is
-        * the currently active page.
-        * 
-        * @return <code>true</code> if the page is currently active,
-        *         <code>false</code> otherwise.
-        */
-       public boolean isActive() {
-               return this.equals(editor.getActivePageInstance());
-       }
-       /**
-        * Creates the part control by creating the managed form using the parent
-        * editor's toolkit. Subclasses should override
-        * <code>createFormContent(IManagedForm)</code> to populate the form with
-        * content.
-        * 
-        * @param parent
-        *            the page parent composite
-        */
-       public void createPartControl(Composite parent) {
-               ScrolledComposite form = editor.getToolkit().createScrolledForm(parent);
-               mform = new PageForm(this, form);
-               BusyIndicator.showWhile(parent.getDisplay(), new Runnable() {
-                       public void run() {
-                               createFormContent(mform);
-                       }
-               });
-       }
-       /**
-        * Subclasses should override this method to create content in the form
-        * hosted in this page.
-        * 
-        * @param managedForm
-        *            the form hosted in this page.
-        */
-       protected void createFormContent(IManagedForm managedForm) {
-       }
-       /**
-        * Returns the form page control.
-        * 
-        * @return managed form's control
-        */
-       public Control getPartControl() {
-               return mform != null ? mform.getForm() : null;
-       }
-       /**
-        * Disposes the managed form.
-        */
-       public void dispose() {
-               if (mform != null)
-                       mform.dispose();
-       }
-       /**
-        * Returns the unique identifier that can be used to reference this page.
-        * 
-        * @return the unique page identifier
-        */
-       public String getId() {
-               return id;
-       }
-       /**
-        * Returns <code>null</code>- form page has no title image. Subclasses
-        * may override.
-        * 
-        * @return <code>null</code>
-        */
-       public Image getTitleImage() {
-               return null;
-       }
-       /**
-        * Sets the focus by delegating to the managed form.
-        */
-       public void setFocus() {
-               if (mform != null)
-                       mform.setFocus();
-       }
-       /**
-        * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
-        */
-       public void doSave(IProgressMonitor monitor) {
-               if (mform != null)
-                       mform.commit(true);
-       }
-       /**
-        * @see org.eclipse.ui.ISaveablePart#doSaveAs()
-        */
-       public void doSaveAs() {
-       }
-       /**
-        * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
-        */
-       public boolean isSaveAsAllowed() {
-               return false;
-       }
-       /**
-        * Implemented by testing if the managed form is dirty.
-        * 
-        * @return <code>true</code> if the managed form is dirty,
-        *         <code>false</code> otherwise.
-        * 
-        * @see org.eclipse.ui.ISaveablePart#isDirty()
-        */
-       public boolean isDirty() {
-               return mform != null ? mform.isDirty() : false;
-       }
-       /**
-        * Preserves the page index.
-        * 
-        * @param index
-        *            the assigned page index
-        */
-       public void setIndex(int index) {
-               this.index = index;
-       }
-       /**
-        * Returns the saved page index.
-        * 
-        * @return the page index
-        */
-       public int getIndex() {
-               return index;
-       }
-       /**
-        * Form pages are not editors.
-        * 
-        * @return <code>false</code>
-        */
-       public boolean isEditor() {
-               return false;
-       }
-       /**
-        * Attempts to select and reveal the given object by passing the request to
-        * the managed form.
-        * 
-        * @param object
-        *            the object to select and reveal in the page if possible.
-        * @return <code>true</code> if the page has been successfully selected
-        *         and revealed by one of the managed form parts, <code>false</code>
-        *         otherwise.
-        */
-       public boolean selectReveal(Object object) {
-               if (mform != null)
-                       return mform.setInput(object);
-               return false;
-       }
-       /**
-        * By default, editor will be allowed to flip the page.
-        * @return <code>true</code>
-        */
-       public boolean canLeaveThePage() {
-               return true;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java
deleted file mode 100644 (file)
index eb08cb5..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java
deleted file mode 100644 (file)
index e74de5e..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable String that displays a browsable link when read-only */
-public class EditableLink extends EditablePropertyString implements
-               EditablePart {
-       private static final long serialVersionUID = 5055000749992803591L;
-
-       private String type;
-       private String message;
-       private boolean readOnly;
-
-       public EditableLink(Composite parent, int style, Node node,
-                       String propertyName, String type, String message)
-                       throws RepositoryException {
-               super(parent, style, node, propertyName, message);
-               this.message = message;
-               this.type = type;
-
-               readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
-               if (node.hasProperty(propertyName)) {
-                       this.setStyle(FormStyle.propertyText.style());
-                       this.setText(node.getProperty(propertyName).getString());
-               } else {
-                       this.setStyle(FormStyle.propertyMessage.style());
-                       this.setText("");
-               }
-       }
-
-       public void setText(String text) {
-               Control child = getControl();
-               if (child instanceof Label) {
-                       Label lbl = (Label) child;
-                       if (EclipseUiUtils.isEmpty(text))
-                               lbl.setText(message);
-                       else if (readOnly)
-                               setLinkValue(lbl, text);
-                       else
-                               // if canEdit() we put only the value with no link
-                               // to avoid glitches of the edition life cycle
-                               lbl.setText(text);
-               } else if (child instanceof Text) {
-                       Text txt = (Text) child;
-                       if (EclipseUiUtils.isEmpty(text)) {
-                               txt.setText("");
-                               txt.setMessage(message);
-                       } else
-                               txt.setText(text);
-               }
-       }
-
-       private void setLinkValue(Label lbl, String text) {
-               if (FormStyle.email.style().equals(type))
-                       lbl.setText(FormUtils.getMailLink(text));
-               else if (FormStyle.phone.style().equals(type))
-                       lbl.setText(FormUtils.getPhoneLink(text));
-               else if (FormStyle.website.style().equals(type))
-                       lbl.setText(FormUtils.getUrlLink(text));
-               else if (FormStyle.facebook.style().equals(type)
-                               || FormStyle.instagram.style().equals(type)
-                               || FormStyle.linkedIn.style().equals(type)
-                               || FormStyle.twitter.style().equals(type))
-                       lbl.setText(FormUtils.getUrlLink(text));
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java
deleted file mode 100644 (file)
index fd3f48e..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.events.TraverseEvent;
-import org.eclipse.swt.events.TraverseListener;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Display, add or remove values from a list in a CMS context */
-public class EditableMultiStringProperty extends StyledControl implements EditablePart {
-       private static final long serialVersionUID = -7044614381252178595L;
-
-       private String propertyName;
-       private String message;
-       // TODO implement the ability to provide a list of possible values
-//     private String[] possibleValues;
-       private boolean canEdit;
-       private SelectionListener removeValueSL;
-       private List<String> values;
-
-       // TODO manage within the CSS
-       private int rowSpacing = 5;
-       private int rowMarging = 0;
-       private int oneValueMargingRight = 5;
-       private int btnWidth = 16;
-       private int btnHeight = 16;
-       private int btnHorizontalIndent = 3;
-
-       public EditableMultiStringProperty(Composite parent, int style, Node node, String propertyName, List<String> values,
-                       String[] possibleValues, String addValueMsg, SelectionListener removeValueSelectionListener)
-                       throws RepositoryException {
-               super(parent, style, node, true);
-
-               this.propertyName = propertyName;
-               this.values = values;
-//             this.possibleValues = new String[] { "Un", "Deux", "Trois" };
-               this.message = addValueMsg;
-               this.canEdit = removeValueSelectionListener != null;
-               this.removeValueSL = removeValueSelectionListener;
-       }
-
-       public List<String> getValues() {
-               return values;
-       }
-
-       public void setValues(List<String> values) {
-               this.values = values;
-       }
-
-       // Row layout items do not need explicit layout data
-       protected void setControlLayoutData(Control control) {
-       }
-
-       /** To be overridden */
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       @Override
-       public Control getControl() {
-               return super.getControl();
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               Composite row = new Composite(box, SWT.NO_FOCUS);
-               row.setLayoutData(EclipseUiUtils.fillAll());
-
-               RowLayout rl = new RowLayout(SWT.HORIZONTAL);
-               rl.wrap = true;
-               rl.spacing = rowSpacing;
-               rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging;
-               row.setLayout(rl);
-
-               if (values != null) {
-                       for (final String value : values) {
-                               if (canEdit)
-                                       createRemovableValue(row, SWT.SINGLE, value);
-                               else
-                                       createValueLabel(row, SWT.SINGLE, value);
-                       }
-               }
-
-               if (!canEdit)
-                       return row;
-               else if (isEditing())
-                       return createText(row, style);
-               else
-                       return createLabel(row, style);
-       }
-
-       /**
-        * Override to provide specific layout for the existing values, typically adding
-        * a pound (#) char for tags or anchor info for browsable links. We assume the
-        * parent composite already has a layout and it is the caller responsibility to
-        * apply corresponding layout data
-        */
-       protected Label createValueLabel(Composite parent, int style, String value) {
-               Label label = new Label(parent, style);
-               label.setText("#" + value);
-               CmsSwtUtils.markup(label);
-               CmsSwtUtils.style(label, FormStyle.propertyText.style());
-               return label;
-       }
-
-       private Composite createRemovableValue(Composite parent, int style, String value) {
-               Composite valCmp = new Composite(parent, SWT.NO_FOCUS);
-               GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
-               gl.marginRight = oneValueMargingRight;
-               valCmp.setLayout(gl);
-
-               createValueLabel(valCmp, SWT.WRAP, value);
-
-               Button deleteBtn = new Button(valCmp, SWT.FLAT);
-               deleteBtn.setData(FormConstants.LINKED_VALUE, value);
-               deleteBtn.addSelectionListener(removeValueSL);
-               CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX);
-               GridData gd = new GridData();
-               gd.heightHint = btnHeight;
-               gd.widthHint = btnWidth;
-               gd.horizontalIndent = btnHorizontalIndent;
-               deleteBtn.setLayoutData(gd);
-
-               return valCmp;
-       }
-
-       protected Text createText(Composite box, String style) {
-               final Text text = new Text(box, getStyle());
-               // The "add new value" text is not meant to change, so we can set it on
-               // creation
-               text.setMessage(message);
-               CmsSwtUtils.style(text, style);
-               text.setFocus();
-
-               text.addTraverseListener(new TraverseListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void keyTraversed(TraverseEvent e) {
-                               if (e.keyCode == SWT.CR) {
-                                       addValue(text);
-                                       e.doit = false;
-                               }
-                       }
-               });
-
-               // The OK button does not work with the focusOut listener
-               // because focus out is called before the OK button is pressed
-
-               // // we must call layout() now so that the row data can compute the
-               // height
-               // // of the other controls.
-               // text.getParent().layout();
-               // int height = text.getSize().y;
-               //
-               // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
-               // okBtn.setText("OK");
-               // RowData rd = new RowData(SWT.DEFAULT, height - 2);
-               // okBtn.setLayoutData(rd);
-               //
-               // okBtn.addSelectionListener(new SelectionAdapter() {
-               // private static final long serialVersionUID = 2780819012423622369L;
-               //
-               // @Override
-               // public void widgetSelected(SelectionEvent e) {
-               // addValue(text);
-               // }
-               // });
-
-               return text;
-       }
-
-       /** Performs the real addition, overwrite to make further sanity checks */
-       protected void addValue(Text text) {
-               String value = text.getText();
-               String errMsg = null;
-
-               if (EclipseUiUtils.isEmpty(value))
-                       return;
-
-               if (values.contains(value))
-                       errMsg = "Dupplicated value: " + value + ", please correct and try again";
-               if (errMsg != null)
-                       MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg);
-               else {
-                       values.add(value);
-                       Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value);
-                       newCmp.moveAbove(text);
-                       text.setText("");
-                       newCmp.getParent().layout();
-               }
-       }
-
-       protected Label createLabel(Composite box, String style) {
-               if (canEdit) {
-                       Label lbl = new Label(box, getStyle());
-                       lbl.setText(message);
-                       CmsSwtUtils.style(lbl, style);
-                       CmsSwtUtils.markup(lbl);
-                       if (mouseListener != null)
-                               lbl.addMouseListener(mouseListener);
-                       return lbl;
-               }
-               return null;
-       }
-
-       protected void clear(boolean deep) {
-               Control child = getControl();
-               if (deep)
-                       super.clear(deep);
-               else {
-                       child.getParent().dispose();
-               }
-       }
-
-       public void setText(String text) {
-               Control child = getControl();
-               if (child instanceof Label) {
-                       Label lbl = (Label) child;
-                       if (canEdit)
-                               lbl.setText(text);
-                       else
-                               lbl.setText("");
-               } else if (child instanceof Text) {
-                       Text txt = (Text) child;
-                       txt.setText(text);
-               }
-       }
-
-       public synchronized void startEditing() {
-               CmsSwtUtils.style(getControl(), FormStyle.propertyText.style());
-//             getControl().setData(STYLE, FormStyle.propertyText.style());
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style());
-//             getControl().setData(STYLE, FormStyle.propertyMessage.style());
-               super.stopEditing();
-       }
-
-       public String getPropertyName() {
-               return propertyName;
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java
deleted file mode 100644 (file)
index 8591a92..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.text.DateFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.DateTime;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** CMS form part to display and edit a date */
-public class EditablePropertyDate extends StyledControl implements EditablePart {
-       private static final long serialVersionUID = 2500215515778162468L;
-
-       // Context
-       private String propertyName;
-       private String message;
-       private DateFormat dateFormat;
-
-       // UI Objects
-       private Text dateTxt;
-       private Button openCalBtn;
-
-       // TODO manage within the CSS
-       private int fieldBtnSpacing = 5;
-
-       /**
-        * 
-        * @param parent
-        * @param style
-        * @param node
-        * @param propertyName
-        * @param message
-        * @param dateFormat   provide a {@link DateFormat} as contract to be able to
-        *                     read/write dates as strings
-        * @throws RepositoryException
-        */
-       public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message,
-                       DateFormat dateFormat) throws RepositoryException {
-               super(parent, style, node, false);
-
-               this.propertyName = propertyName;
-               this.message = message;
-               this.dateFormat = dateFormat;
-
-               if (node.hasProperty(propertyName)) {
-                       this.setStyle(FormStyle.propertyText.style());
-                       this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime()));
-               } else {
-                       this.setStyle(FormStyle.propertyMessage.style());
-                       this.setText(message);
-               }
-       }
-
-       public void setText(String text) {
-               Control child = getControl();
-               if (child instanceof Label) {
-                       Label lbl = (Label) child;
-                       if (EclipseUiUtils.isEmpty(text))
-                               lbl.setText(message);
-                       else
-                               lbl.setText(text);
-               } else if (child instanceof Text) {
-                       Text txt = (Text) child;
-                       if (EclipseUiUtils.isEmpty(text)) {
-                               txt.setText("");
-                       } else
-                               txt.setText(text);
-               }
-       }
-
-       public synchronized void startEditing() {
-               // if (dateTxt != null && !dateTxt.isDisposed())
-               CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-//             getControl().setData(STYLE, FormStyle.propertyText.style());
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               if (EclipseUiUtils.isEmpty(dateTxt.getText()))
-                       CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
-//                     getControl().setData(STYLE, FormStyle.propertyMessage.style());
-               else
-                       CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-//             getControl().setData(STYLE, FormStyle.propertyText.style());
-               super.stopEditing();
-       }
-
-       public String getPropertyName() {
-               return propertyName;
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               if (isEditing()) {
-                       return createCustomEditableControl(box, style);
-               } else
-                       return createLabel(box, style);
-       }
-
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = new Label(box, getStyle() | SWT.WRAP);
-               lbl.setLayoutData(CmsSwtUtils.fillWidth());
-               CmsSwtUtils.style(lbl, style);
-               CmsSwtUtils.markup(lbl);
-               if (mouseListener != null)
-                       lbl.addMouseListener(mouseListener);
-               return lbl;
-       }
-
-       private Control createCustomEditableControl(Composite box, String style) {
-               box.setLayoutData(CmsSwtUtils.fillWidth());
-               Composite dateComposite = new Composite(box, SWT.NONE);
-               GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
-               gl.horizontalSpacing = fieldBtnSpacing;
-               dateComposite.setLayout(gl);
-               dateTxt = new Text(dateComposite, SWT.BORDER);
-               CmsSwtUtils.style(dateTxt, style);
-               dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT));
-               dateTxt.setToolTipText(
-                               "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar");
-               openCalBtn = new Button(dateComposite, SWT.FLAT);
-               CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX);
-               GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
-               gd.heightHint = 17;
-               openCalBtn.setLayoutData(gd);
-               // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN);
-
-               openCalBtn.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void widgetSelected(SelectionEvent event) {
-                               CalendarPopup popup = new CalendarPopup(dateTxt);
-                               popup.open();
-                       }
-               });
-
-               // dateTxt.addFocusListener(new FocusListener() {
-               // private static final long serialVersionUID = 1L;
-               //
-               // @Override
-               // public void focusLost(FocusEvent event) {
-               // String newVal = dateTxt.getText();
-               // // Enable reset of the field
-               // if (FormUtils.notNull(newVal))
-               // calendar = null;
-               // else {
-               // try {
-               // Calendar newCal = parseDate(newVal);
-               // // DateText.this.setText(newCal);
-               // calendar = newCal;
-               // } catch (ParseException pe) {
-               // // Silent. Manage error popup?
-               // if (calendar != null)
-               // EditablePropertyDate.this.setText(calendar);
-               // }
-               // }
-               // }
-               //
-               // @Override
-               // public void focusGained(FocusEvent event) {
-               // }
-               // });
-               return dateTxt;
-       }
-
-       protected void clear(boolean deep) {
-               Control child = getControl();
-               if (deep || child instanceof Label)
-                       super.clear(deep);
-               else {
-                       child.getParent().dispose();
-               }
-       }
-
-       /** Enable setting a custom tooltip on the underlying text */
-       @Deprecated
-       public void setToolTipText(String toolTipText) {
-               dateTxt.setToolTipText(toolTipText);
-       }
-
-       @Deprecated
-       /** Enable setting a custom message on the underlying text */
-       public void setMessage(String message) {
-               dateTxt.setMessage(message);
-       }
-
-       @Deprecated
-       public void setText(Calendar cal) {
-               String newValueStr = "";
-               if (cal != null)
-                       newValueStr = dateFormat.format(cal.getTime());
-               if (!newValueStr.equals(dateTxt.getText()))
-                       dateTxt.setText(newValueStr);
-       }
-
-       // UTILITIES TO MANAGE THE CALENDAR POPUP
-       // TODO manage the popup shell in a cleaner way
-       private class CalendarPopup extends Shell {
-               private static final long serialVersionUID = 1L;
-               private DateTime dateTimeCtl;
-
-               public CalendarPopup(Control source) {
-                       super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-                       populate();
-                       // Add border and shadow style
-                       CmsSwtUtils.markup(CalendarPopup.this);
-                       CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style());
-                       pack();
-                       layout();
-                       setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3));
-
-                       addShellListener(new ShellAdapter() {
-                               private static final long serialVersionUID = 5178980294808435833L;
-
-                               @Override
-                               public void shellDeactivated(ShellEvent e) {
-                                       close();
-                                       dispose();
-                               }
-                       });
-                       open();
-               }
-
-               private void setProperty() {
-                       // Direct set does not seems to work. investigate
-                       // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(),
-                       // dateTimeCtl.getDay(), 12, 0);
-                       Calendar cal = new GregorianCalendar();
-                       cal.set(Calendar.YEAR, dateTimeCtl.getYear());
-                       cal.set(Calendar.MONTH, dateTimeCtl.getMonth());
-                       cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay());
-                       String dateStr = dateFormat.format(cal.getTime());
-                       dateTxt.setText(dateStr);
-               }
-
-               protected void populate() {
-                       setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-                       dateTimeCtl = new DateTime(this, SWT.CALENDAR);
-                       dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll());
-
-                       Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText());
-
-                       if (calendar != null)
-                               dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
-                                               calendar.get(Calendar.DAY_OF_MONTH));
-
-                       dateTimeCtl.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = -8414377364434281112L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       setProperty();
-                               }
-                       });
-
-                       dateTimeCtl.addMouseListener(new MouseListener() {
-                               private static final long serialVersionUID = 1L;
-
-                               @Override
-                               public void mouseUp(MouseEvent e) {
-                               }
-
-                               @Override
-                               public void mouseDown(MouseEvent e) {
-                               }
-
-                               @Override
-                               public void mouseDoubleClick(MouseEvent e) {
-                                       setProperty();
-                                       close();
-                                       dispose();
-                               }
-                       });
-               }
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java
deleted file mode 100644 (file)
index 0920093..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import static org.argeo.cms.ui.forms.FormStyle.propertyMessage;
-import static org.argeo.cms.ui.forms.FormStyle.propertyText;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.widgets.EditableText;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable String in a CMS context */
-public class EditablePropertyString extends EditableText implements EditablePart {
-       private static final long serialVersionUID = 5055000749992803591L;
-
-       private String propertyName;
-       private String message;
-
-       // encode the '&' character in rap
-       private final static String AMPERSAND = "&#38;";
-       private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)";
-
-       public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message)
-                       throws RepositoryException {
-               super(parent, style, node, true);
-               //setUseTextAsLabel(true);
-               this.propertyName = propertyName;
-               this.message = message;
-
-               if (node.hasProperty(propertyName)) {
-                       this.setStyle(propertyText.style());
-                       this.setText(node.getProperty(propertyName).getString());
-               } else {
-                       this.setStyle(propertyMessage.style());
-                       this.setText(message + "  ");
-               }
-       }
-
-       public void setText(String text) {
-               Control child = getControl();
-               if (child instanceof Label) {
-                       Label lbl = (Label) child;
-                       if (EclipseUiUtils.isEmpty(text))
-                               lbl.setText(message + "  ");
-                       else
-                               // TODO enhance this
-                               lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND));
-               } else if (child instanceof Text) {
-                       Text txt = (Text) child;
-                       if (EclipseUiUtils.isEmpty(text)) {
-                               txt.setText("");
-                               txt.setMessage(message + " ");
-                       } else
-                               txt.setText(text.replaceAll("<br/>", "\n"));
-               }
-       }
-
-       public synchronized void startEditing() {
-               CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               if (EclipseUiUtils.isEmpty(((Text) getControl()).getText()))
-                       CmsSwtUtils.style(getControl(), FormStyle.propertyMessage);
-               else
-                       CmsSwtUtils.style(getControl(), FormStyle.propertyText);
-               super.stopEditing();
-       }
-
-       public String getPropertyName() {
-               return propertyName;
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java
deleted file mode 100644 (file)
index fe9f7e7..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-/** Constants used in the various CMS Forms */
-public interface FormConstants {
-       // DATAKEYS
-       public final static String LINKED_VALUE = "LinkedValue";
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java
deleted file mode 100644 (file)
index f3a56f7..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.util.Observable;
-import java.util.Observer;
-
-import javax.jcr.Node;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-
-/** Add life cycle management abilities to an editable form page */
-public class FormEditorHeader implements SelectionListener, Observer {
-       private static final long serialVersionUID = 7392898696542484282L;
-
-       // private final Node context;
-       private final CmsEditable cmsEditable;
-       private Button publishBtn;
-
-       // Should we provide here the ability to switch from read only to edition
-       // mode?
-       // private Button editBtn;
-       // private boolean readOnly;
-
-       // TODO add information about the current node status, typically if it is
-       // dirty or not
-
-       private Composite parent;
-       private Composite display;
-       private Object layoutData;
-
-       public FormEditorHeader(Composite parent, int style, Node context,
-                       CmsEditable cmsEditable) {
-               this.cmsEditable = cmsEditable;
-               this.parent = parent;
-               // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
-               // this.context = context;
-               if (this.cmsEditable instanceof Observable)
-                       ((Observable) this.cmsEditable).addObserver(this);
-               refresh();
-       }
-
-       public void setLayoutData(Object layoutData) {
-               this.layoutData = layoutData;
-               if (display != null && !display.isDisposed())
-                       display.setLayoutData(layoutData);
-       }
-
-       protected void refresh() {
-               if (display != null && !display.isDisposed())
-                       display.dispose();
-
-               display = new Composite(parent, SWT.NONE);
-               display.setLayoutData(layoutData);
-
-               CmsSwtUtils.style(display, FormStyle.header.style());
-               display.setBackgroundMode(SWT.INHERIT_FORCE);
-
-               display.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               publishBtn = createSimpleBtn(display, getPublishButtonLabel());
-               display.moveAbove(null);
-               parent.layout();
-       }
-
-       private Button createSimpleBtn(Composite parent, String label) {
-               Button button = new Button(parent, SWT.FLAT | SWT.PUSH);
-               button.setText(label);
-               CmsSwtUtils.style(button, FormStyle.header.style());
-               button.addSelectionListener(this);
-               return button;
-       }
-
-       private String getPublishButtonLabel() {
-               // Rather check if the current node differs from what has been
-               // previously committed
-               // For the time being, we always reach here, the underlying CmsEditable
-               // is always editing.
-               if (cmsEditable.isEditing())
-                       return " Publish ";
-               else
-                       return " Edit ";
-       }
-
-       @Override
-       public void widgetSelected(SelectionEvent e) {
-               if (e.getSource() == publishBtn) {
-                       // For the time being, the underlying CmsEditable
-                       // is always editing when we reach this point
-                       if (cmsEditable.isEditing()) {
-                               // we always leave the node in a check outed state
-                               cmsEditable.stopEditing();
-                               cmsEditable.startEditing();
-                       } else {
-                               cmsEditable.startEditing();
-                       }
-               }
-       }
-
-       @Override
-       public void widgetDefaultSelected(SelectionEvent e) {
-       }
-
-       @Override
-       public void update(Observable o, Object arg) {
-               if (o == cmsEditable) {
-                       refresh();
-               }
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java
deleted file mode 100644 (file)
index cc732d4..0000000
+++ /dev/null
@@ -1,608 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.viewers.AbstractPageViewer;
-import org.argeo.cms.ui.viewers.EditablePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.cms.ui.widgets.StyledControl;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadEvent;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.service.ServerPushSession;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FormAttachment;
-import org.eclipse.swt.layout.FormData;
-import org.eclipse.swt.layout.FormLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Manage life cycle of a form page that is linked to a given node */
-public class FormPageViewer extends AbstractPageViewer {
-       private final static CmsLog log = CmsLog.getLog(FormPageViewer.class);
-       private static final long serialVersionUID = 5277789504209413500L;
-
-       private final Section mainSection;
-
-       // TODO manage within the CSS
-       private Integer labelColWidth = null;
-       private int rowLayoutHSpacing = 8;
-
-       // Context cached in the viewer
-       // The reference to translate from text to calendar and reverse
-       private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
-       private CmsImageManager<Control, Node> imageManager;
-       private FileUploadListener fileUploadListener;
-
-       public FormPageViewer(Section mainSection, int style, CmsEditable cmsEditable) throws RepositoryException {
-               super(mainSection, style, cmsEditable);
-               this.mainSection = mainSection;
-
-               if (getCmsEditable().canEdit()) {
-                       fileUploadListener = new FUL();
-               }
-       }
-
-       @Override
-       protected void prepare(EditablePart part, Object caretPosition) {
-               if (part instanceof Img) {
-                       ((Img) part).setFileUploadListener(fileUploadListener);
-               }
-       }
-
-       /** To be overridden.Save the edited part. */
-       protected void save(EditablePart part) throws RepositoryException {
-               Node node = null;
-               if (part instanceof EditableMultiStringProperty) {
-                       EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
-                       // SWT : View
-                       List<String> values = ept.getValues();
-                       // JCR : Model
-                       node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       if (values.isEmpty()) {
-                               if (node.hasProperty(propName))
-                                       node.getProperty(propName).remove();
-                       } else {
-                               node.setProperty(propName, values.toArray(new String[0]));
-                       }
-                       // => Viewer : Controller
-               } else if (part instanceof EditablePropertyString) {
-                       EditablePropertyString ept = (EditablePropertyString) part;
-                       // SWT : View
-                       String txt = ((Text) ept.getControl()).getText();
-                       // JCR : Model
-                       node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       if (EclipseUiUtils.isEmpty(txt)) {
-                               if (node.hasProperty(propName))
-                                       node.getProperty(propName).remove();
-                       } else {
-                               setPropertySilently(node, propName, txt);
-                               // node.setProperty(propName, txt);
-                       }
-                       // node.getSession().save();
-                       // => Viewer : Controller
-               } else if (part instanceof EditablePropertyDate) {
-                       EditablePropertyDate ept = (EditablePropertyDate) part;
-                       Calendar cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText());
-                       node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       if (cal == null) {
-                               if (node.hasProperty(propName))
-                                       node.getProperty(propName).remove();
-                       } else {
-                               node.setProperty(propName, cal);
-                       }
-                       // node.getSession().save();
-                       // => Viewer : Controller
-               }
-               // TODO: make this configurable, sometimes we do not want to save the
-               // current session at this stage
-               if (node != null && node.getSession().hasPendingChanges()) {
-                       JcrUtils.updateLastModified(node, true);
-                       node.getSession().save();
-               }
-       }
-
-       @Override
-       protected void updateContent(EditablePart part) throws RepositoryException {
-               if (part instanceof EditableMultiStringProperty) {
-                       EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
-                       // SWT : View
-                       Node node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       List<String> valStrings = new ArrayList<String>();
-                       if (node.hasProperty(propName)) {
-                               Value[] values = node.getProperty(propName).getValues();
-                               for (Value val : values)
-                                       valStrings.add(val.getString());
-                       }
-                       ept.setValues(valStrings);
-               } else if (part instanceof EditablePropertyString) {
-                       // || part instanceof EditableLink
-                       EditablePropertyString ept = (EditablePropertyString) part;
-                       // JCR : Model
-                       Node node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       if (node.hasProperty(propName)) {
-                               String value = node.getProperty(propName).getString();
-                               ept.setText(value);
-                       } else
-                               ept.setText("");
-                       // => Viewer : Controller
-               } else if (part instanceof EditablePropertyDate) {
-                       EditablePropertyDate ept = (EditablePropertyDate) part;
-                       // JCR : Model
-                       Node node = ept.getNode();
-                       String propName = ept.getPropertyName();
-                       if (node.hasProperty(propName))
-                               ept.setText(dateFormat.format(node.getProperty(propName).getDate().getTime()));
-                       else
-                               ept.setText("");
-               } else if (part instanceof SectionPart) {
-                       SectionPart sectionPart = (SectionPart) part;
-                       Node partNode = sectionPart.getNode();
-                       // use control AFTER setting style, since it may have been reset
-                       if (part instanceof EditableImage) {
-                               EditableImage editableImage = (EditableImage) part;
-                               imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize());
-                       }
-               }
-       }
-
-       // FILE UPLOAD LISTENER
-       protected class FUL implements FileUploadListener {
-
-               public FUL() {
-               }
-
-               public void uploadProgress(FileUploadEvent event) {
-                       // TODO Monitor upload progress
-               }
-
-               public void uploadFailed(FileUploadEvent event) {
-                       throw new IllegalStateException("Upload failed " + event, event.getException());
-               }
-
-               public void uploadFinished(FileUploadEvent event) {
-                       for (FileDetails file : event.getFileDetails()) {
-                               if (log.isDebugEnabled())
-                                       log.debug("Received: " + file.getFileName());
-                       }
-                       mainSection.getDisplay().syncExec(new Runnable() {
-                               @Override
-                               public void run() {
-                                       saveEdit();
-                               }
-                       });
-                       FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
-                       uploadHandler.dispose();
-               }
-       }
-
-       // FOCUS OUT LISTENER
-       protected FocusListener createFocusListener() {
-               return new FocusOutListener();
-       }
-
-       private class FocusOutListener implements FocusListener {
-               private static final long serialVersionUID = -6069205786732354186L;
-
-               @Override
-               public void focusLost(FocusEvent event) {
-                       saveEdit();
-               }
-
-               @Override
-               public void focusGained(FocusEvent event) {
-                       // does nothing;
-               }
-       }
-
-       // MOUSE LISTENER
-       @Override
-       protected MouseListener createMouseListener() {
-               return new ML();
-       }
-
-       private class ML extends MouseAdapter {
-               private static final long serialVersionUID = 8526890859876770905L;
-
-               @Override
-               public void mouseDoubleClick(MouseEvent e) {
-                       if (e.button == 1) {
-                               Control source = (Control) e.getSource();
-                               if (getCmsEditable().canEdit()) {
-                                       if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
-                                               if (source == mainSection)
-                                                       return;
-                                               EditablePart part = findDataParent(source);
-                                               upload(part);
-                                       } else {
-                                               getCmsEditable().startEditing();
-                                       }
-                               }
-                       }
-               }
-
-               @Override
-               public void mouseDown(MouseEvent e) {
-                       if (getCmsEditable().isEditing()) {
-                               if (e.button == 1) {
-                                       Control source = (Control) e.getSource();
-                                       EditablePart composite = findDataParent(source);
-                                       Point point = new Point(e.x, e.y);
-                                       if (!(composite instanceof Img))
-                                               edit(composite, source.toDisplay(point));
-                               } else if (e.button == 3) {
-                                       // EditablePart composite = findDataParent((Control) e
-                                       // .getSource());
-                                       // if (styledTools != null)
-                                       // styledTools.show(composite, new Point(e.x, e.y));
-                               }
-                       }
-               }
-
-               protected synchronized void upload(EditablePart part) {
-                       if (part instanceof SectionPart) {
-                               if (part instanceof Img) {
-                                       if (getEdited() == part)
-                                               return;
-                                       edit(part, null);
-                                       layout(part.getControl());
-                               }
-                       }
-               }
-       }
-
-       @Override
-       public Control getControl() {
-               return mainSection;
-       }
-
-       protected CmsImageManager<Control, Node> imageManager() {
-               if (imageManager == null)
-                       imageManager = (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(mainSection).getImageManager();
-               return imageManager;
-       }
-
-       // LOCAL UI HELPERS
-       protected Section createSectionIfNeeded(Composite body, Node node) throws RepositoryException {
-               Section section = null;
-               if (node != null) {
-                       section = new Section(body, SWT.NO_FOCUS, node);
-                       section.setLayoutData(CmsSwtUtils.fillWidth());
-                       section.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               }
-               return section;
-       }
-
-       protected void createSimpleLT(Composite bodyRow, Node node, String propName, String label, String msg)
-                       throws RepositoryException {
-               if (getCmsEditable().canEdit() || node.hasProperty(propName)) {
-                       createPropertyLbl(bodyRow, label);
-                       EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg);
-                       eps.setMouseListener(getMouseListener());
-                       eps.setFocusListener(getFocusListener());
-                       eps.setLayoutData(CmsSwtUtils.fillWidth());
-               }
-       }
-
-       protected void createMultiStringLT(Composite bodyRow, Node node, String propName, String label, String msg)
-                       throws RepositoryException {
-               boolean canEdit = getCmsEditable().canEdit();
-               if (canEdit || node.hasProperty(propName)) {
-                       createPropertyLbl(bodyRow, label);
-
-                       List<String> valueStrings = new ArrayList<String>();
-
-                       if (node.hasProperty(propName)) {
-                               Value[] values = node.getProperty(propName).getValues();
-                               for (Value value : values)
-                                       valueStrings.add(value.getString());
-                       }
-
-                       // TODO use a drop down to display possible values to the end user
-                       EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node,
-                                       propName, valueStrings, new String[] { "Implement this" }, msg,
-                                       canEdit ? getRemoveValueSelListener() : null);
-                       addListeners(emsp);
-                       // emsp.setMouseListener(getMouseListener());
-                       emsp.setStyle(FormStyle.propertyMessage.style());
-                       emsp.setLayoutData(CmsSwtUtils.fillWidth());
-               }
-       }
-
-       protected Label createPropertyLbl(Composite parent, String value) {
-               return createPropertyLbl(parent, value, SWT.NONE);
-       }
-
-       protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
-               // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
-               Label label = new Label(parent, SWT.LEAD | SWT.WRAP);
-               label.setText(value + " ");
-               CmsSwtUtils.style(label, FormStyle.propertyLabel.style());
-               GridData gd = new GridData(SWT.LEAD, vAlign, false, false);
-               if (labelColWidth != null)
-                       gd.widthHint = labelColWidth;
-               label.setLayoutData(gd);
-               return label;
-       }
-
-       protected Label newStyledLabel(Composite parent, String style, String value) {
-               Label label = new Label(parent, SWT.NONE);
-               label.setText(value);
-               CmsSwtUtils.style(label, style);
-               return label;
-       }
-
-       protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException {
-               Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
-               bodyRow.setLayoutData(CmsSwtUtils.fillWidth());
-               RowLayout rl = new RowLayout(SWT.WRAP);
-               rl.type = SWT.HORIZONTAL;
-               rl.spacing = rowLayoutHSpacing;
-               rl.marginHeight = rl.marginWidth = 0;
-               rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
-               bodyRow.setLayout(rl);
-               return bodyRow;
-       }
-
-       protected Composite createAddImgComposite(final Section section, Composite parent, final Node parentNode)
-                       throws RepositoryException {
-
-               Composite body = new Composite(parent, SWT.NO_FOCUS);
-               body.setLayout(new GridLayout());
-
-               FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null);
-               final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver);
-               if (fileUploadListener != null)
-                       currentUploadHandler.addUploadListener(fileUploadListener);
-
-               // Button creation
-               final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
-               fileUpload.setText("Import an image");
-               fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-               fileUpload.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = 4869523412991968759L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               ServerPushSession pushSession = new ServerPushSession();
-                               pushSession.start();
-                               String uploadURL = currentUploadHandler.getUploadUrl();
-                               fileUpload.submit(uploadURL);
-                       }
-               });
-
-               return body;
-       }
-
-       protected class FormFileUploadReceiver extends FileUploadReceiver {
-
-               private Node context;
-               private Section section;
-               private String name;
-
-               public FormFileUploadReceiver(Section section, Node context, String name) {
-                       this.context = context;
-                       this.section = section;
-                       this.name = name;
-               }
-
-               @Override
-               public void receive(InputStream stream, FileDetails details) throws IOException {
-
-                       if (name == null)
-                               name = details.getFileName();
-
-                       // TODO clean image name more carefully
-                       String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "");
-                       // We add a unique prefix to workaround the cache issue: when
-                       // deleting and re-adding a new image with same name, the end user
-                       // browser will use the cache and the image will remain unchanged
-                       // for a while
-                       cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName;
-
-                       imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType());
-                       // TODO clean refresh strategy
-                       section.getDisplay().asyncExec(new Runnable() {
-                               @Override
-                               public void run() {
-                                       try {
-                                               FormPageViewer.this.refresh(section);
-                                               section.layout();
-                                               section.getParent().layout();
-                                       } catch (RepositoryException re) {
-                                               throw new JcrException("Unable to refresh " + "image section for " + context, re);
-                                       }
-                               }
-                       });
-               }
-       }
-
-       protected void addListeners(StyledControl control) {
-               control.setMouseListener(getMouseListener());
-               control.setFocusListener(getFocusListener());
-       }
-
-       protected Img createImgComposite(Composite parent, Node node, Point preferredSize) throws RepositoryException {
-               Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) {
-                       private static final long serialVersionUID = 1297900641952417540L;
-
-                       @Override
-                       protected void setContainerLayoutData(Composite composite) {
-                               composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-                       }
-
-                       @Override
-                       protected void setControlLayoutData(Control control) {
-                               control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-                       }
-               };
-               img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-               updateContent(img);
-               addListeners(img);
-               return img;
-       }
-
-       protected Composite addDeleteAbility(final Section section, final Node sessionNode, int topWeight,
-                       int rightWeight) {
-               Composite comp = new Composite(section, SWT.NONE);
-               comp.setLayoutData(CmsSwtUtils.fillAll());
-               comp.setLayout(new FormLayout());
-
-               // The body to be populated
-               Composite body = new Composite(comp, SWT.NO_FOCUS);
-               body.setLayoutData(EclipseUiUtils.fillFormData());
-
-               if (getCmsEditable().canEdit()) {
-                       // the delete button
-                       Button deleteBtn = new Button(comp, SWT.FLAT);
-                       CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
-                       FormData formData = new FormData();
-                       formData.right = new FormAttachment(rightWeight, 0);
-                       formData.top = new FormAttachment(topWeight, 0);
-                       deleteBtn.setLayoutData(formData);
-                       deleteBtn.moveAbove(body);
-
-                       deleteBtn.addSelectionListener(new SelectionAdapter() {
-                               private static final long serialVersionUID = 4304223543657238462L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       super.widgetSelected(e);
-                                       if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion",
-                                                       "Are you really you want to remove this?")) {
-                                               Session session;
-                                               try {
-                                                       session = sessionNode.getSession();
-                                                       Section parSection = section.getParentSection();
-                                                       sessionNode.remove();
-                                                       session.save();
-                                                       refresh(parSection);
-                                                       layout(parSection);
-                                               } catch (RepositoryException re) {
-                                                       throw new JcrException("Unable to delete " + sessionNode, re);
-                                               }
-
-                                       }
-
-                               }
-                       });
-               }
-               return body;
-       }
-
-//     // LOCAL HELPERS FOR NODE MANAGEMENT
-//     private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
-//             Node node = null;
-//             if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
-//                     node = JcrUtils.mkdirs(parent, nodeName, nodeType);
-//                     parent.getSession().save();
-//             }
-//
-//             if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
-//                     node = parent.getNode(nodeName);
-//
-//             return node;
-//     }
-
-       private SelectionListener getRemoveValueSelListener() {
-               return new SelectionAdapter() {
-                       private static final long serialVersionUID = 9022259089907445195L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               Object source = e.getSource();
-                               if (source instanceof Button) {
-                                       Button btn = (Button) source;
-                                       Object obj = btn.getData(FormConstants.LINKED_VALUE);
-                                       EditablePart ep = findDataParent(btn);
-                                       if (ep != null && ep instanceof EditableMultiStringProperty) {
-                                               EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
-                                               List<String> values = emsp.getValues();
-                                               if (values.contains(obj)) {
-                                                       values.remove(values.indexOf(obj));
-                                                       emsp.setValues(values);
-                                                       try {
-                                                               save(emsp);
-                                                               // TODO workaround to force refresh
-                                                               edit(emsp, 0);
-                                                               cancelEdit();
-                                                       } catch (RepositoryException e1) {
-                                                               throw new JcrException("Unable to remove value " + obj, e1);
-                                                       }
-                                                       layout(emsp);
-                                               }
-                                       }
-                               }
-                       }
-               };
-       }
-
-       protected void setPropertySilently(Node node, String propName, String value) throws RepositoryException {
-               try {
-                       // TODO Clean this:
-                       // Format strings to replace \n
-                       value = value.replaceAll("\n", "<br/>");
-                       // Do not make the update if validation fails
-                       try {
-                               MarkupValidatorCopy.getInstance().validate(value);
-                       } catch (Exception e) {
-                               log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node
-                                               + ", String cannot be validated - " + e.getMessage());
-                               return;
-                       }
-                       // TODO check if the newly created property is of the correct type,
-                       // otherwise the property will be silently created with a STRING
-                       // property type.
-                       node.setProperty(propName, value);
-               } catch (ValueFormatException vfe) {
-                       log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage());
-               }
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java
deleted file mode 100644 (file)
index 24067ea..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import org.argeo.api.cms.CmsStyle;
-
-/** Syles used */
-public enum FormStyle implements CmsStyle {
-       // Main
-       form, title,
-       // main part
-       header, headerBtn, headerCombo, section, sectionHeader,
-       // Property fields
-       propertyLabel, propertyText, propertyMessage, errorMessage,
-       // Date
-       popupCalendar,
-       // Buttons
-       starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete,
-       // Contacts
-       email, address, phone, website,
-       // Social Media
-       facebook, twitter, linkedIn, instagram;
-
-       @Override
-       public String getClassPrefix() {
-               return "argeo-form";
-       }
-
-       // TODO clean button style management
-       public final static String BUTTON_SUFFIX = "_btn";
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java
deleted file mode 100644 (file)
index 1a445bd..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.eclipse.jface.fieldassist.ControlDecoration;
-import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Utilitary methods to ease implementation of CMS forms */
-public class FormUtils {
-       private final static CmsLog log = CmsLog.getLog(FormUtils.class);
-
-       public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy";
-
-       /** Best effort to convert a String to a calendar. Fails silently */
-       public static Calendar parseDate(DateFormat dateFormat, String calStr) {
-               Calendar cal = null;
-               if (EclipseUiUtils.notEmpty(calStr)) {
-                       try {
-                               Date date = dateFormat.parse(calStr);
-                               cal = new GregorianCalendar();
-                               cal.setTime(date);
-                       } catch (ParseException pe) {
-                               // Silent
-                               log.warn("Unable to parse date: " + calStr + " - msg: "
-                                               + pe.getMessage());
-                       }
-               }
-               return cal;
-       }
-
-       /** Add a double click listener on tables that display a JCR node list */
-       public static void addCanonicalDoubleClickListener(final TableViewer v) {
-               v.addDoubleClickListener(new IDoubleClickListener() {
-
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               CmsView cmsView = CmsUiUtils.getCmsView();
-                               Node node = (Node) ((IStructuredSelection) event.getSelection())
-                                               .getFirstElement();
-                               try {
-                                       cmsView.navigateTo(node.getPath());
-                               } catch (RepositoryException e) {
-                                       throw new CmsException("Unable to get path for node "
-                                                       + node + " before calling navigateTo(path)", e);
-                               }
-                       }
-               });
-       }
-
-       // MANAGE ERROR DECORATION
-
-       public static ControlDecoration addDecoration(final Text text) {
-               final ControlDecoration dynDecoration = new ControlDecoration(text,
-                               SWT.LEFT);
-               Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR);
-               dynDecoration.setImage(icon);
-               dynDecoration.setMarginWidth(3);
-               dynDecoration.hide();
-               return dynDecoration;
-       }
-
-       public static void refreshDecoration(Text text, ControlDecoration deco,
-                       boolean isValid, boolean clean) {
-               if (isValid || clean) {
-                       text.setBackground(null);
-                       deco.hide();
-               } else {
-                       text.setBackground(new Color(text.getDisplay(), 250, 200, 150));
-                       deco.show();
-               }
-       }
-
-       public static Image getDecorationImage(String image) {
-               FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
-               return registry.getFieldDecoration(image).getImage();
-       }
-
-       public static void addCompulsoryDecoration(Label label) {
-               final ControlDecoration dynDecoration = new ControlDecoration(label,
-                               SWT.RIGHT | SWT.TOP);
-               Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED);
-               dynDecoration.setImage(icon);
-               dynDecoration.setMarginWidth(3);
-       }
-
-       // TODO the read only generation of read only links for various contact type
-       // should be factorised in the cms Utils.
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able phone number
-        */
-       public static String getPhoneLink(String value) {
-               return getPhoneLink(value, value);
-       }
-
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able phone number
-        * 
-        * @param value
-        * @param label
-        *            a potentially distinct label
-        * @return
-        */
-       public static String getPhoneLink(String value, String label) {
-               StringBuilder builder = new StringBuilder();
-               builder.append("<a href=\"tel:");
-               builder.append(value).append("\" target=\"_blank\" >").append(label)
-                               .append("</a>");
-               return builder.toString();
-       }
-
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able mail
-        */
-       public static String getMailLink(String value) {
-               return getMailLink(value, value);
-       }
-
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able mail
-        * 
-        * @param value
-        * @param label
-        *            a potentially distinct label
-        * @return
-        */
-       public static String getMailLink(String value, String label) {
-               StringBuilder builder = new StringBuilder();
-               value = replaceAmpersand(value);
-               builder.append("<a href=\"mailto:");
-               builder.append(value).append("\" >").append(label).append("</a>");
-               return builder.toString();
-       }
-
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able link
-        */
-       public static String getUrlLink(String value) {
-               return getUrlLink(value, value);
-       }
-
-       /**
-        * Creates the read-only HTML snippet to display in a label with styling
-        * enabled in order to provide a click-able link
-        */
-       public static String getUrlLink(String value, String label) {
-               StringBuilder builder = new StringBuilder();
-               value = replaceAmpersand(value);
-               label = replaceAmpersand(label);
-               if (!(value.startsWith("http://") || value.startsWith("https://")))
-                       value = "http://" + value;
-               builder.append("<a href=\"");
-               builder.append(value + "\" target=\"_blank\" >" + label + "</a>");
-               return builder.toString();
-       }
-
-       private static String AMPERSAND = "&#38;";
-
-       /**
-        * Cleans a String by replacing any '&#38;' by its HTML encoding '&#38;#38;' to
-        * avoid <code>SAXParseException</code> while rendering HTML with RWT
-        */
-       public static String replaceAmpersand(String value) {
-               value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND);
-               return value;
-       }
-
-       // Prevents instantiation
-       private FormUtils() {
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java
deleted file mode 100644 (file)
index 3f588d1..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import java.io.StringReader;
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.eclipse.rap.rwt.SingletonUtil;
-import org.eclipse.swt.widgets.Widget;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Copy of RAP v2.3 since it is in an internal package.
- */
-class MarkupValidatorCopy {
-
-       // Used by Eclipse Scout project
-       public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled";
-
-       private static final String DTD = createDTD();
-       private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
-       private final SAXParser saxParser;
-
-       public static MarkupValidatorCopy getInstance() {
-               return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class);
-       }
-
-       public MarkupValidatorCopy() {
-               saxParser = createSAXParser();
-       }
-
-       public void validate(String text) {
-               StringBuilder markup = new StringBuilder();
-               markup.append(DTD);
-               markup.append("<html>");
-               markup.append(text);
-               markup.append("</html>");
-               InputSource inputSource = new InputSource(new StringReader(markup.toString()));
-               try {
-                       saxParser.parse(inputSource, new MarkupHandler());
-               } catch (RuntimeException exception) {
-                       throw exception;
-               } catch (Exception exception) {
-                       throw new IllegalArgumentException("Failed to parse markup text", exception);
-               }
-       }
-
-       public static boolean isValidationDisabledFor(Widget widget) {
-               return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED));
-       }
-
-       private static SAXParser createSAXParser() {
-               SAXParser result = null;
-               SAXParserFactory parserFactory = SAXParserFactory.newInstance();
-               try {
-                       result = parserFactory.newSAXParser();
-               } catch (Exception exception) {
-                       throw new RuntimeException("Failed to create SAX parser", exception);
-               }
-               return result;
-       }
-
-       private static String createDTD() {
-               StringBuilder result = new StringBuilder();
-               result.append("<!DOCTYPE html [");
-               result.append("<!ENTITY quot \"&#34;\">");
-               result.append("<!ENTITY amp \"&#38;\">");
-               result.append("<!ENTITY apos \"&#39;\">");
-               result.append("<!ENTITY lt \"&#60;\">");
-               result.append("<!ENTITY gt \"&#62;\">");
-               result.append("<!ENTITY nbsp \"&#160;\">");
-               result.append("<!ENTITY ensp \"&#8194;\">");
-               result.append("<!ENTITY emsp \"&#8195;\">");
-               result.append("<!ENTITY ndash \"&#8211;\">");
-               result.append("<!ENTITY mdash \"&#8212;\">");
-               result.append("]>");
-               return result.toString();
-       }
-
-       private static Map<String, String[]> createSupportedElementsMap() {
-               Map<String, String[]> result = new HashMap<String, String[]>();
-               result.put("html", new String[0]);
-               result.put("br", new String[0]);
-               result.put("b", new String[] { "style" });
-               result.put("strong", new String[] { "style" });
-               result.put("i", new String[] { "style" });
-               result.put("em", new String[] { "style" });
-               result.put("sub", new String[] { "style" });
-               result.put("sup", new String[] { "style" });
-               result.put("big", new String[] { "style" });
-               result.put("small", new String[] { "style" });
-               result.put("del", new String[] { "style" });
-               result.put("ins", new String[] { "style" });
-               result.put("code", new String[] { "style" });
-               result.put("samp", new String[] { "style" });
-               result.put("kbd", new String[] { "style" });
-               result.put("var", new String[] { "style" });
-               result.put("cite", new String[] { "style" });
-               result.put("dfn", new String[] { "style" });
-               result.put("q", new String[] { "style" });
-               result.put("abbr", new String[] { "style", "title" });
-               result.put("span", new String[] { "style" });
-               result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" });
-               result.put("a", new String[] { "style", "href", "target", "title" });
-               return result;
-       }
-
-       private static class MarkupHandler extends DefaultHandler {
-
-               @Override
-               public void startElement(String uri, String localName, String name, Attributes attributes) {
-                       checkSupportedElements(name, attributes);
-                       checkSupportedAttributes(name, attributes);
-                       checkMandatoryAttributes(name, attributes);
-               }
-
-               private static void checkSupportedElements(String elementName, Attributes attributes) {
-                       if (!SUPPORTED_ELEMENTS.containsKey(elementName)) {
-                               throw new IllegalArgumentException("Unsupported element in markup text: " + elementName);
-                       }
-               }
-
-               private static void checkSupportedAttributes(String elementName, Attributes attributes) {
-                       if (attributes.getLength() > 0) {
-                               List<String> supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName));
-                               int index = 0;
-                               String attributeName = attributes.getQName(index);
-                               while (attributeName != null) {
-                                       if (!supportedAttributes.contains(attributeName)) {
-                                               String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
-                                               message = MessageFormat.format(message, new Object[] { attributeName, elementName });
-                                               throw new IllegalArgumentException(message);
-                                       }
-                                       index++;
-                                       attributeName = attributes.getQName(index);
-                               }
-                       }
-               }
-
-               private static void checkMandatoryAttributes(String elementName, Attributes attributes) {
-                       checkIntAttribute(elementName, attributes, "img", "width");
-                       checkIntAttribute(elementName, attributes, "img", "height");
-               }
-
-               private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName,
-                               String checkedAttributeName) {
-                       if (checkedElementName.equals(elementName)) {
-                               String attribute = attributes.getValue(checkedAttributeName);
-                               try {
-                                       Integer.parseInt(attribute);
-                               } catch (NumberFormatException exception) {
-                                       String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
-                                       Object[] arguments = new Object[] { checkedAttributeName, checkedElementName };
-                                       message = MessageFormat.format(message, arguments);
-                                       throw new IllegalArgumentException(message);
-                               }
-                       }
-               }
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java
deleted file mode 100644 (file)
index 5f954c1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS forms, based on SWT/JFace. */
-package org.argeo.cms.ui.forms;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java
deleted file mode 100644 (file)
index 5a5ecdb..0000000
+++ /dev/null
@@ -1,524 +0,0 @@
-package org.argeo.cms.ui.fs;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.Session;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
-import org.argeo.eclipse.ui.fs.FsTableViewer;
-import org.argeo.eclipse.ui.fs.FsUiConstants;
-import org.argeo.eclipse.ui.fs.FsUiUtils;
-import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.viewers.DoubleClickEvent;
-import org.eclipse.jface.viewers.IDoubleClickListener;
-import org.eclipse.jface.viewers.ISelectionChangedListener;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.jface.viewers.SelectionChangedEvent;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.ModifyEvent;
-import org.eclipse.swt.events.ModifyListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Text;
-
-/**
- * Default CMS browser composite: a sashForm layout with bookmarks at the left
- * hand side, a simple table in the middle and an overview at right hand side.
- */
-public class CmsFsBrowser extends Composite {
-       // private final static Log log = LogFactory.getLog(CmsFsBrowser.class);
-       private static final long serialVersionUID = -40347919096946585L;
-
-       private final FileSystemProvider nodeFileSystemProvider;
-       private final Node currentBaseContext;
-
-       // UI Parts for the browser
-       private Composite leftPannelCmp;
-       private Composite filterCmp;
-       private Text filterTxt;
-       private FsTableViewer directoryDisplayViewer;
-       private Composite rightPannelCmp;
-
-       private FsContextMenu contextMenu;
-
-       // Local context (this composite is state full)
-       private Path initialPath;
-       private Path currDisplayedFolder;
-       private Path currSelected;
-
-       // local variables (to be cleaned)
-       private int bookmarkColWith = 500;
-
-       /*
-        * WARNING: unfinalised implementation of the mechanism to retrieve base
-        * paths
-        */
-
-       private final static String NODE_PREFIX = "node://";
-
-       private String getCurrentHomePath() {
-               Session session = null;
-               try {
-                       Repository repo = currentBaseContext.getSession().getRepository();
-                       session = CurrentUser.tryAs(() -> repo.login());
-                       String homepath = CmsJcrUtils.getUserHome(session).getPath();
-                       return homepath;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot retrieve Current User Home Path", e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       protected Path[] getMyFilesPath() {
-               // return Paths.get(System.getProperty("user.dir"));
-               String currHomeUriStr = NODE_PREFIX + getCurrentHomePath();
-               try {
-                       URI uri = new URI(currHomeUriStr);
-                       FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri);
-                       if (fileSystem == null) {
-                               PrivilegedExceptionAction<FileSystem> pea = new PrivilegedExceptionAction<FileSystem>() {
-                                       @Override
-                                       public FileSystem run() throws Exception {
-                                               return nodeFileSystemProvider.newFileSystem(uri, null);
-                                       }
-
-                               };
-                               fileSystem = CurrentUser.tryAs(pea);
-                       }
-                       Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") };
-                       return paths;
-               } catch (URISyntaxException | PrivilegedActionException e) {
-                       throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e);
-               }
-       }
-
-       private Path[] getMyGroupsFilesPath() {
-               // TODO
-               Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") };
-               return paths;
-       }
-
-       private Path[] getMyBookmarks() {
-               // TODO
-               Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") };
-               return paths;
-       }
-
-       /* End of warning */
-
-       public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) {
-               super(parent, style);
-               this.nodeFileSystemProvider = fileSystemProvider;
-               this.currentBaseContext = context;
-
-               this.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               SashForm form = new SashForm(this, SWT.HORIZONTAL);
-
-               leftPannelCmp = new Composite(form, SWT.NO_FOCUS);
-               // Bookmarks are still static
-               populateBookmarks(leftPannelCmp);
-
-               Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
-               createDisplay(centerCmp);
-
-               rightPannelCmp = new Composite(form, SWT.NO_FOCUS);
-
-               form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               form.setWeights(new int[] { 15, 40, 20 });
-       }
-
-       void refresh() {
-               modifyFilter(false);
-               // also refresh bookmarks and groups
-       }
-
-       private void createDisplay(final Composite parent) {
-               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               // top filter
-               filterCmp = new Composite(parent, SWT.NO_FOCUS);
-               filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
-               addFilterPanel(filterCmp);
-
-               // Main display
-               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
-               List<ColumnDefinition> colDefs = new ArrayList<>();
-               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
-               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
-                               "Last modified", 400));
-               final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
-               table.setLayoutData(EclipseUiUtils.fillAll());
-
-               // table.addKeyListener(new KeyListener() {
-               // private static final long serialVersionUID = -8083424284436715709L;
-               //
-               // @Override
-               // public void keyReleased(KeyEvent e) {
-               // }
-               //
-               // @Override
-               // public void keyPressed(KeyEvent e) {
-               // if (log.isDebugEnabled())
-               // log.debug("Key event received: " + e.keyCode);
-               // IStructuredSelection selection = (IStructuredSelection)
-               // directoryDisplayViewer.getSelection();
-               // Path selected = null;
-               // if (!selection.isEmpty())
-               // selected = ((Path) selection.getFirstElement());
-               // if (e.keyCode == SWT.CR) {
-               // if (!Files.isDirectory(selected))
-               // return;
-               // if (selected != null) {
-               // currDisplayedFolder = selected;
-               // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
-               // }
-               // } else if (e.keyCode == SWT.BS) {
-               // currDisplayedFolder = currDisplayedFolder.getParent();
-               // directoryDisplayViewer.setInput(currDisplayedFolder, "*");
-               // directoryDisplayViewer.getTable().setFocus();
-               // }
-               // }
-               // });
-
-               directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
-
-                       @Override
-                       public void selectionChanged(SelectionChangedEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (selection.isEmpty())
-                                       setSelected(null);
-                               else
-                                       selected = ((Path) selection.getFirstElement());
-                               if (selected != null) {
-                                       // TODO manage multiple selection
-                                       setSelected(selected);
-                               }
-                       }
-               });
-
-               directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
-                       @Override
-                       public void doubleClick(DoubleClickEvent event) {
-                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
-                               Path selected = null;
-                               if (!selection.isEmpty())
-                                       selected = ((Path) selection.getFirstElement());
-                               if (selected != null) {
-                                       if (!Files.isDirectory(selected))
-                                               return;
-                                       setInput(selected);
-                               }
-                       }
-               });
-
-               // The context menu
-               contextMenu = new FsContextMenu(this);
-
-               table.addMouseListener(new MouseAdapter() {
-                       private static final long serialVersionUID = 6737579410648595940L;
-
-                       @Override
-                       public void mouseDown(MouseEvent e) {
-                               if (e.button == 3) {
-                                       // contextMenu.setCurrFolderPath(currDisplayedFolder);
-                                       contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder);
-                               }
-                       }
-               });
-       }
-
-       private void addPathElementBtn(Path path) {
-               Button elemBtn = new Button(filterCmp, SWT.PUSH);
-               String nameStr;
-               if (path.toString().equals("/"))
-                       nameStr = "[jcr:root]";
-               else
-                       nameStr = path.getFileName().toString();
-               elemBtn.setText(nameStr + " >> ");
-               CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
-               elemBtn.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = -4103695476023480651L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               setInput(path);
-                       }
-               });
-       }
-
-       public void setInput(Path path) {
-               if (path.equals(currDisplayedFolder))
-                       return;
-               currDisplayedFolder = path;
-
-               Path diff = initialPath.relativize(currDisplayedFolder);
-
-               for (Control child : filterCmp.getChildren())
-                       if (!child.equals(filterTxt))
-                               child.dispose();
-
-               addPathElementBtn(initialPath);
-               Path currTarget = initialPath;
-               if (!diff.toString().equals(""))
-                       for (Path pathElem : diff) {
-                               currTarget = currTarget.resolve(pathElem);
-                               addPathElementBtn(currTarget);
-                       }
-
-               filterTxt.setText("");
-               filterTxt.moveBelow(null);
-               setSelected(null);
-               filterCmp.getParent().layout(true, true);
-       }
-
-       private void setSelected(Path path) {
-               currSelected = path;
-               setOverviewInput(path);
-       }
-
-       public Viewer getViewer() {
-               return directoryDisplayViewer;
-       }
-
-       private void populateBookmarks(Composite parent) {
-               CmsSwtUtils.clear(parent);
-               parent.setLayout(new GridLayout());
-               ISelectionChangedListener selList = new BookmarksSelChangeListener();
-
-               FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
-               Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith);
-               GridData gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 10;
-               table.setLayoutData(gd);
-               homeViewer.addSelectionChangedListener(selList);
-               homeViewer.setPathsInput(getMyFilesPath());
-
-               appendTitle(parent, "Shared files");
-               FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
-               table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith);
-               gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 10;
-               table.setLayoutData(gd);
-               groupsViewer.addSelectionChangedListener(selList);
-               groupsViewer.setPathsInput(getMyGroupsFilesPath());
-
-               appendTitle(parent, "My bookmarks");
-               FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL);
-               table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith);
-               gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 10;
-               table.setLayoutData(gd);
-               bookmarksViewer.addSelectionChangedListener(selList);
-               bookmarksViewer.setPathsInput(getMyBookmarks());
-       }
-
-       /**
-        * Recreates the content of the box that displays information about the
-        * current selected Path.
-        */
-       private void setOverviewInput(Path path) {
-               try {
-                       EclipseUiUtils.clear(rightPannelCmp);
-                       rightPannelCmp.setLayout(new GridLayout());
-                       if (path != null) {
-                               // if (isImg(context)) {
-                               // EditableImage image = new Img(parent, RIGHT, context,
-                               // imageWidth);
-                               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
-                               // true, false,
-                               // 2, 1));
-                               // }
-
-                               Label contextL = new Label(rightPannelCmp, SWT.NONE);
-                               contextL.setText(path.getFileName().toString());
-                               contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp));
-                               addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString());
-                               // addProperty(rightPannelCmp, "Owner",
-                               // Files.getOwner(path).getName());
-                               if (Files.isDirectory(path)) {
-                                       addProperty(rightPannelCmp, "Type", "Folder");
-                               } else {
-                                       String mimeType = Files.probeContentType(path);
-                                       if (EclipseUiUtils.isEmpty(mimeType))
-                                               mimeType = "<i>Unknown</i>";
-                                       addProperty(rightPannelCmp, "Type", mimeType);
-                                       addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
-                               }
-                       }
-                       rightPannelCmp.layout(true, true);
-               } catch (IOException e) {
-                       throw new CmsException("Cannot display details for " + path.toString(), e);
-               }
-       }
-
-       private void addFilterPanel(Composite parent) {
-               RowLayout rl = new RowLayout(SWT.HORIZONTAL);
-               rl.wrap = true;
-               parent.setLayout(rl);
-               // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
-               // false)));
-
-               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
-               filterTxt.setMessage("Search current folder");
-               filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
-               filterTxt.addModifyListener(new ModifyListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       public void modifyText(ModifyEvent event) {
-                               modifyFilter(false);
-                       }
-               });
-               filterTxt.addKeyListener(new KeyListener() {
-                       private static final long serialVersionUID = 2533535233583035527L;
-
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                       }
-
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               // boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
-                               // // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
-                               // FilterEntitiesVirtualTable currTable = null;
-                               // if (currEdited != null) {
-                               // FilterEntitiesVirtualTable table =
-                               // browserCols.get(currEdited);
-                               // if (table != null && !table.isDisposed())
-                               // currTable = table;
-                               // }
-                               //
-                               // if (e.keyCode == SWT.ARROW_DOWN)
-                               // currTable.setFocus();
-                               // else if (e.keyCode == SWT.BS) {
-                               // if (filterTxt.getText().equals("")
-                               // && !(currEdited.getNameCount() == 1 ||
-                               // currEdited.equals(initialPath))) {
-                               // Path oldEdited = currEdited;
-                               // Path parentPath = currEdited.getParent();
-                               // setEdited(parentPath);
-                               // if (browserCols.containsKey(parentPath))
-                               // browserCols.get(parentPath).setSelected(oldEdited);
-                               // filterTxt.setFocus();
-                               // e.doit = false;
-                               // }
-                               // } else if (e.keyCode == SWT.TAB && !shiftPressed) {
-                               // Path uniqueChild = getOnlyChild(currEdited,
-                               // filterTxt.getText());
-                               // if (uniqueChild != null) {
-                               // // Highlight the unique chosen child
-                               // currTable.setSelected(uniqueChild);
-                               // setEdited(uniqueChild);
-                               // }
-                               // filterTxt.setFocus();
-                               // e.doit = false;
-                               // }
-                       }
-               });
-       }
-
-       private Path getOnlyChild(Path parent, String filter) {
-               try (DirectoryStream<Path> stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
-                       Path uniqueChild = null;
-                       boolean moreThanOne = false;
-                       loop: for (Path entry : stream) {
-                               if (uniqueChild == null) {
-                                       uniqueChild = entry;
-                               } else {
-                                       moreThanOne = true;
-                                       break loop;
-                               }
-                       }
-                       if (!moreThanOne)
-                               return uniqueChild;
-                       return null;
-               } catch (IOException ioe) {
-                       throw new CmsException(
-                                       "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
-                                       ioe);
-               }
-       }
-
-       private void modifyFilter(boolean fromOutside) {
-               if (!fromOutside)
-                       if (currDisplayedFolder != null) {
-                               String filter = filterTxt.getText() + "*";
-                               directoryDisplayViewer.setInput(currDisplayedFolder, filter);
-                       }
-       }
-
-       private class BookmarksSelChangeListener implements ISelectionChangedListener {
-
-               @Override
-               public void selectionChanged(SelectionChangedEvent event) {
-                       IStructuredSelection selection = (IStructuredSelection) event.getSelection();
-                       if (selection.isEmpty())
-                               return;
-                       else {
-                               Path newSelected = (Path) selection.getFirstElement();
-                               if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath))
-                                       return;
-                               initialPath = newSelected;
-                               setInput(newSelected);
-                       }
-               }
-       }
-
-       // Simplify UI implementation
-       private void addProperty(Composite parent, String propName, String value) {
-               Label contextL = new Label(parent, SWT.NONE);
-               contextL.setText(propName + ": " + value);
-       }
-
-       private Label appendTitle(Composite parent, String value) {
-               Label titleLbl = new Label(parent, SWT.NONE);
-               titleLbl.setText(value);
-               titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
-               GridData gd = EclipseUiUtils.fillWidth();
-               gd.horizontalIndent = 5;
-               gd.verticalIndent = 5;
-               titleLbl.setLayoutData(gd);
-               return titleLbl;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java
deleted file mode 100644 (file)
index e875b5a..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.ui.fs;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.specific.FileDropAdapter;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.widgets.Control;
-
-/** Allows a control to receive file drops. */
-public class FileDrop {
-       private final static CmsLog log = CmsLog.getLog(FileDrop.class);
-
-       public void createDropTarget(Control control) {
-               FileDropAdapter fileDropAdapter = new FileDropAdapter() {
-                       @Override
-                       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-                               if (log.isDebugEnabled())
-                                       log.debug("Process upload of " + fileName + " (" + contentType + ")");
-                               processFileUpload(in, fileName, contentType);
-                       }
-               };
-               DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY);
-               fileDropAdapter.prepareDropTarget(control, dropTarget);
-       }
-
-       public void handleFileDrop(Control control, DropTargetEvent event) {
-       }
-
-       /** Executed in UI thread */
-       protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException {
-
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java
deleted file mode 100644 (file)
index c548e2a..0000000
+++ /dev/null
@@ -1,383 +0,0 @@
-package org.argeo.cms.ui.fs;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.dialogs.SingleValue;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.viewers.IStructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** Generic popup context menu to manage NIO Path in a Viewer. */
-public class FsContextMenu extends Shell {
-       private static final long serialVersionUID = -9120261153509855795L;
-
-       private final static CmsLog log = CmsLog.getLog(FsContextMenu.class);
-
-       // Default known actions
-       public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
-       public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
-       public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
-       public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
-       public final static String ACTION_ID_DELETE = "delete";
-       public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
-       public final static String ACTION_ID_OPEN = "open";
-
-       // Local context
-       private final CmsFsBrowser browser;
-       // private final Viewer viewer;
-       private final static String KEY_ACTION_ID = "actionId";
-       private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
-                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE,
-                       ACTION_ID_OPEN };
-       private Map<String, Button> actionButtons = new HashMap<String, Button>();
-
-       private Path currFolderPath;
-
-       public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display
-                                                                                                       // display) {
-               super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               this.browser = browser;
-               setLayout(EclipseUiUtils.noSpaceGridLayout());
-
-               Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER);
-               boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
-               CmsSwtUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX);
-               createContextMenu(boxCmp);
-
-               addShellListener(new ActionsShellListener());
-       }
-
-       protected void createContextMenu(Composite boxCmp) {
-               ActionsSelListener asl = new ActionsSelListener();
-               for (String actionId : DEFAULT_ACTIONS) {
-                       Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD);
-                       btn.setText(getLabel(actionId));
-                       btn.setLayoutData(EclipseUiUtils.fillWidth());
-                       CmsSwtUtils.markup(btn);
-                       CmsSwtUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX);
-                       btn.setData(KEY_ACTION_ID, actionId);
-                       btn.addSelectionListener(asl);
-                       actionButtons.put(actionId, btn);
-               }
-       }
-
-       protected String getLabel(String actionId) {
-               switch (actionId) {
-               case ACTION_ID_CREATE_FOLDER:
-                       return "Create Folder";
-               case ACTION_ID_BOOKMARK_FOLDER:
-                       return "Bookmark Folder";
-               case ACTION_ID_SHARE_FOLDER:
-                       return "Share Folder";
-               case ACTION_ID_DOWNLOAD_FOLDER:
-                       return "Download as zip archive";
-               case ACTION_ID_DELETE:
-                       return "Delete";
-               case ACTION_ID_UPLOAD_FILE:
-                       return "Upload Files";
-               case ACTION_ID_OPEN:
-                       return "Open";
-               default:
-                       throw new IllegalArgumentException("Unknown action ID " + actionId);
-               }
-       }
-
-       protected void aboutToShow(Control source, Point location) {
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               boolean emptySel = true;
-               boolean multiSel = false;
-               boolean isFolder = true;
-               if (selection != null && !selection.isEmpty()) {
-                       emptySel = false;
-                       multiSel = selection.size() > 1;
-                       if (!multiSel && selection.getFirstElement() instanceof Path) {
-                               isFolder = Files.isDirectory((Path) selection.getFirstElement());
-                       }
-               }
-               if (emptySel) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN,
-                                       // to be implemented
-                                       ACTION_ID_BOOKMARK_FOLDER);
-               } else if (multiSel) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN,
-                                       // to be implemented
-                                       ACTION_ID_BOOKMARK_FOLDER);
-               } else if (isFolder) {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
-                       setVisible(false, ACTION_ID_OPEN,
-                                       // to be implemented
-                                       ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
-               } else {
-                       setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE);
-                       setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER,
-                                       // to be implemented
-                                       ACTION_ID_BOOKMARK_FOLDER);
-               }
-       }
-
-       private void setVisible(boolean visible, String... buttonIds) {
-               for (String id : buttonIds) {
-                       Button button = actionButtons.get(id);
-                       button.setVisible(visible);
-                       GridData gd = (GridData) button.getLayoutData();
-                       gd.heightHint = visible ? SWT.DEFAULT : 0;
-               }
-       }
-
-       public void show(Control source, Point location, Path currFolderPath) {
-               if (isVisible())
-                       setVisible(false);
-               // TODO find a better way to retrieve the parent path (cannot be deduced
-               // from table content because it will fail on an empty folder)
-               this.currFolderPath = currFolderPath;
-               aboutToShow(source, location);
-               pack();
-               layout();
-               if (source instanceof Control)
-                       setLocation(((Control) source).toDisplay(location.x, location.y));
-               open();
-       }
-
-       class StyleButton extends Label {
-               private static final long serialVersionUID = 7731102609123946115L;
-
-               public StyleButton(Composite parent, int swtStyle) {
-                       super(parent, swtStyle);
-               }
-
-       }
-
-       // class ActionsMouseListener extends MouseAdapter {
-       // private static final long serialVersionUID = -1041871937815812149L;
-       //
-       // @Override
-       // public void mouseDown(MouseEvent e) {
-       // Object eventSource = e.getSource();
-       // if (e.button == 1) {
-       // if (eventSource instanceof Button) {
-       // Button pressedBtn = (Button) eventSource;
-       // String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
-       // switch (actionId) {
-       // case ACTION_ID_CREATE_FOLDER:
-       // createFolder();
-       // break;
-       // case ACTION_ID_DELETE:
-       // deleteItems();
-       // break;
-       // default:
-       // throw new IllegalArgumentException("Unimplemented action " + actionId);
-       // // case ACTION_ID_SHARE_FOLDER:
-       // // return "Share Folder";
-       // // case ACTION_ID_DOWNLOAD_FOLDER:
-       // // return "Download as zip archive";
-       // // case ACTION_ID_UPLOAD_FILE:
-       // // return "Upload Files";
-       // // case ACTION_ID_OPEN:
-       // // return "Open";
-       // }
-       // }
-       // }
-       // viewer.getControl().setFocus();
-       // // setVisible(false);
-       // }
-       // }
-
-       class ActionsSelListener extends SelectionAdapter {
-               private static final long serialVersionUID = -1041871937815812149L;
-
-               @Override
-               public void widgetSelected(SelectionEvent e) {
-                       Object eventSource = e.getSource();
-                       if (eventSource instanceof Button) {
-                               Button pressedBtn = (Button) eventSource;
-                               String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
-                               switch (actionId) {
-                               case ACTION_ID_CREATE_FOLDER:
-                                       createFolder();
-                                       break;
-                               case ACTION_ID_DELETE:
-                                       deleteItems();
-                                       break;
-                               case ACTION_ID_OPEN:
-                                       openFile();
-                                       break;
-                               case ACTION_ID_UPLOAD_FILE:
-                                       uploadFiles();
-                                       break;
-                               default:
-                                       throw new IllegalArgumentException("Unimplemented action " + actionId);
-                                       // case ACTION_ID_SHARE_FOLDER:
-                                       // return "Share Folder";
-                                       // case ACTION_ID_DOWNLOAD_FOLDER:
-                                       // return "Download as zip archive";
-                                       // case ACTION_ID_OPEN:
-                                       // return "Open";
-                               }
-                       }
-                       browser.setFocus();
-                       // viewer.getControl().setFocus();
-                       // setVisible(false);
-
-               }
-       }
-
-       class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
-               private static final long serialVersionUID = -5092341449523150827L;
-
-               @Override
-               public void shellDeactivated(ShellEvent e) {
-                       setVisible(false);
-               }
-       }
-
-       private void openFile() {
-               log.warn("Implement single sourced, workbench independant \"Open File\" action");
-       }
-
-       private void deleteItems() {
-               IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
-               if (selection.isEmpty())
-                       return;
-
-               StringBuilder builder = new StringBuilder();
-               @SuppressWarnings("unchecked")
-               Iterator<Object> iterator = selection.iterator();
-               List<Path> paths = new ArrayList<>();
-
-               while (iterator.hasNext()) {
-                       Path path = (Path) iterator.next();
-                       builder.append(path.getFileName() + ", ");
-                       paths.add(path);
-               }
-               String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
-                               + ". Are you sure?";
-               if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) {
-                       for (Path path : paths) {
-                               try {
-                                       // Might have already been deleted if we are in a tree
-                                       Files.deleteIfExists(path);
-                               } catch (IOException e) {
-                                       throw new CmsException("Cannot delete path " + path, e);
-                               }
-                       }
-                       browser.refresh();
-               }
-       }
-
-       private void createFolder() {
-               String msg = "Please provide a name.";
-               String name = SingleValue.ask("Create folder", msg);
-               // TODO enhance check of name validity
-               if (EclipseUiUtils.notEmpty(name)) {
-                       try {
-                               Path child = currFolderPath.resolve(name);
-                               if (Files.exists(child))
-                                       throw new CmsException("An item with name " + name + " already exists at "
-                                                       + currFolderPath.toString() + ", cannot create");
-                               else
-                                       Files.createDirectories(child);
-                               browser.refresh();
-                       } catch (IOException e) {
-                               throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
-                       }
-               }
-       }
-
-       private void uploadFiles() {
-               try {
-                       FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI);
-                       dialog.setText("Choose one or more files to upload");
-
-                       if (EclipseUiUtils.notEmpty(dialog.open())) {
-                               String[] names = dialog.getFileNames();
-                               // Workaround small differences between RAP and RCP
-                               // 1. returned names are absolute path on RAP and
-                               // relative in RCP
-                               // 2. in RCP we must use getFilterPath that does not
-                               // exists on RAP
-                               Method filterMethod = null;
-                               Path parPath = null;
-                               try {
-                                       filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
-                                       String filterPath = (String) filterMethod.invoke(dialog);
-                                       parPath = Paths.get(filterPath);
-                               } catch (NoSuchMethodException nsme) { // RAP
-                               }
-                               if (names.length == 0)
-                                       return;
-                               else {
-                                       loop: for (String name : names) {
-                                               Path tmpPath = Paths.get(name);
-                                               if (parPath != null)
-                                                       tmpPath = parPath.resolve(tmpPath);
-                                               if (Files.exists(tmpPath)) {
-                                                       URI uri = tmpPath.toUri();
-                                                       String uriStr = uri.toString();
-
-                                                       if (Files.isDirectory(tmpPath)) {
-                                                               MessageDialog.openError(browser.getShell(), "Unimplemented directory import",
-                                                                               "Upload of directories in the system is not yet implemented");
-                                                               continue loop;
-                                                       }
-                                                       Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
-                                                       InputStream in = null;
-                                                       try {
-                                                               in = new ByteArrayInputStream(Files.readAllBytes(tmpPath));
-                                                               Files.copy(in, targetPath);
-                                                               Files.delete(tmpPath);
-                                                       } finally {
-                                                               IOUtils.closeQuietly(in);
-                                                       }
-                                                       if (log.isDebugEnabled())
-                                                               log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
-                                               } else {
-                                                       String msg = "Cannot copy tmp file from " + tmpPath.toString();
-                                                       if (parPath != null)
-                                                               msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
-                                                       MessageDialog.openError(browser.getShell(), "Missing file", msg);
-                                                       continue loop;
-                                               }
-                                       }
-                                       browser.refresh();
-                               }
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath);
-               }
-       }
-
-       public void setCurrFolderPath(Path currFolderPath) {
-               this.currFolderPath = currFolderPath;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java
deleted file mode 100644 (file)
index 9ae3192..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.ui.fs;
-
-/** FS Ui specific CSS styles */
-public interface FsStyles {
-       String BREAD_CRUMB_BTN = "breadCrumb_btn";
-       String CONTEXT_MENU_BOX = "contextMenu_box";
-       String BUTTON_SUFFIX = "_btn";
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java
deleted file mode 100644 (file)
index 6a6c272..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace file system components. */
-package org.argeo.cms.ui.fs;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java
deleted file mode 100644 (file)
index e10da3a..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import org.argeo.api.cms.CmsState;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class Activator implements BundleActivator {
-
-       // avoid dependency to RWT OSGi
-       private final static String CONTEXT_NAME_PROP = "contextName";
-
-       private static ServiceTracker<CmsState, CmsState> nodeState;
-
-       // @Override
-       public void start(BundleContext bc) throws Exception {
-               // UI
-//             bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(),
-//                             LangUtils.dico(CONTEXT_NAME_PROP, "system"));
-//             bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user"));
-
-               nodeState = new ServiceTracker<>(bc, CmsState.class, null);
-               nodeState.open();
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-               if (nodeState != null) {
-                       nodeState.close();
-                       nodeState = null;
-               }
-       }
-
-       public static CmsState getNodeState() {
-               return nodeState.getService();
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java
deleted file mode 100644 (file)
index 44885b1..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import java.util.ArrayList;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.CmsException;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-@Deprecated
-class JcrContentProvider implements ITreeContentProvider {
-       private static final long serialVersionUID = -1333678161322488674L;
-
-       @Override
-       public void dispose() {
-       }
-
-       @Override
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               if (newInput == null)
-                       return;
-               if (!(newInput instanceof Node))
-                       throw new CmsException("Input " + newInput + " must be a node");
-       }
-
-       @Override
-       public Object[] getElements(Object inputElement) {
-               try {
-                       Node node = (Node) inputElement;
-                       ArrayList<Node> arr = new ArrayList<Node>();
-                       NodeIterator nit = node.getNodes();
-                       while (nit.hasNext()) {
-                               arr.add(nit.nextNode());
-                       }
-                       return arr.toArray();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot get elements", e);
-               }
-       }
-
-       @Override
-       public Object[] getChildren(Object parentElement) {
-               try {
-                       Node node = (Node) parentElement;
-                       ArrayList<Node> arr = new ArrayList<Node>();
-                       NodeIterator nit = node.getNodes();
-                       while (nit.hasNext()) {
-                               arr.add(nit.nextNode());
-                       }
-                       return arr.toArray();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot get elements", e);
-               }
-       }
-
-       @Override
-       public Object getParent(Object element) {
-               try {
-                       Node node = (Node) element;
-                       if (node.getName().equals(""))
-                               return null;
-                       else
-                               return node.getParent();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot get elements", e);
-               }
-       }
-
-       @Override
-       public boolean hasChildren(Object element) {
-               try {
-                       Node node = (Node) element;
-                       return node.hasNodes();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot get elements", e);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java
deleted file mode 100644 (file)
index c8582f0..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import static javax.jcr.nodetype.NodeType.NT_FILE;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.FilenameUtils;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.cms.ui.widgets.Img;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-
-public class JcrFileUploadReceiver extends FileUploadReceiver {
-       private Img img;
-       private final Node parentNode;
-       private final String nodeName;
-       private final CmsImageManager imageManager;
-
-       /** If nodeName is null, use the uploaded file name */
-       public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) {
-               super();
-               this.img = img;
-               this.parentNode = parentNode;
-               this.nodeName = nodeName;
-               this.imageManager = imageManager;
-       }
-
-       @Override
-       public void receive(InputStream stream, FileDetails details) throws IOException {
-               try {
-                       String fileName = nodeName != null ? nodeName : details.getFileName();
-                       String contentType = details.getContentType();
-                       if (isImage(details.getFileName(), contentType)) {
-                               imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType);
-                               return;
-                       }
-
-                       Node fileNode;
-                       if (parentNode.hasNode(fileName)) {
-                               fileNode = parentNode.getNode(fileName);
-                               if (!fileNode.isNodeType(NT_FILE))
-                                       fileNode.remove();
-                       }
-                       fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream);
-
-                       if (contentType != null) {
-                               fileNode.addMixin(NodeType.MIX_MIMETYPE);
-                               fileNode.setProperty(Property.JCR_MIMETYPE, contentType);
-                       }
-                       processNewFile(fileNode);
-                       fileNode.getSession().save();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot receive " + details, e);
-               }
-       }
-
-       protected Boolean isImage(String fileName, String contentType) {
-               String ext = FilenameUtils.getExtension(fileName);
-               return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg"));
-       }
-
-       protected void processNewFile(Node node) {
-
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java
deleted file mode 100644 (file)
index c5c1a01..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Text;
-
-/** NOT working yet. */
-public class SimpleEditableImage extends EditableImage {
-       private static final long serialVersionUID = -5689145523114022890L;
-
-       private String src;
-       private Cms2DSize imageSize;
-
-       public SimpleEditableImage(Composite parent, int swtStyle) {
-               super(parent, swtStyle);
-               // load(getControl());
-               getParent().layout();
-       }
-
-       public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) {
-               super(parent, swtStyle);
-               this.src = src;
-               this.imageSize = imageSize;
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               if (isEditing()) {
-                       return createText(box, style);
-               } else {
-                       return createLabel(box, style);
-               }
-       }
-
-       protected String createImgTag() throws RepositoryException {
-               String imgTag;
-               if (src != null)
-                       imgTag = CmsUiUtils.img(src, imageSize);
-               else
-                       imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE);
-               return imgTag;
-       }
-
-       protected Text createText(Composite box, String style) {
-               Text text = new Text(box, getStyle());
-               CmsSwtUtils.style(text, style);
-               return text;
-       }
-
-       public String getSrc() {
-               return src;
-       }
-
-       public void setSrc(String src) {
-               this.src = src;
-       }
-
-       public Cms2DSize getImageSize() {
-               return imageSize;
-       }
-
-       public void setImageSize(Cms2DSize imageSize) {
-               this.imageSize = imageSize;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java
deleted file mode 100644 (file)
index 3806341..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java
deleted file mode 100644 (file)
index 0f7ee77..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java
deleted file mode 100644 (file)
index e4c5873..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-package org.argeo.cms.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.jcr.model.RepositoriesElem;
-import org.argeo.cms.ui.jcr.model.RepositoryElem;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.cms.ui.jcr.model.WorkspaceElem;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/** Useful methods to manage the JCR Browser */
-public class JcrBrowserUtils {
-
-       public static String getPropertyTypeAsString(Property prop) {
-               try {
-                       return PropertyType.nameFromValue(prop.getType());
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot check type for " + prop, e);
-               }
-       }
-
-       /** Insure that the UI component is not stale, refresh if needed */
-       public static void forceRefreshIfNeeded(TreeParent element) {
-               Node curNode = null;
-
-               boolean doRefresh = false;
-
-               try {
-                       if (element instanceof SingleJcrNodeElem) {
-                               curNode = ((SingleJcrNodeElem) element).getNode();
-                       } else if (element instanceof WorkspaceElem) {
-                               curNode = ((WorkspaceElem) element).getRootNode();
-                       }
-
-                       if (curNode != null && element.getChildren().length != curNode.getNodes().getSize())
-                               doRefresh = true;
-                       else if (element instanceof RepositoryElem) {
-                               RepositoryElem rn = (RepositoryElem) element;
-                               if (rn.isConnected()) {
-                                       String[] wkpNames = rn.getAccessibleWorkspaceNames();
-                                       if (element.getChildren().length != wkpNames.length)
-                                               doRefresh = true;
-                               }
-                       } else if (element instanceof RepositoriesElem) {
-                               doRefresh = true;
-                               // Always force refresh for RepositoriesElem : the condition
-                               // below does not take remote repository into account and it is
-                               // not trivial to do so.
-
-                               // RepositoriesElem rn = (RepositoriesElem) element;
-                               // if (element.getChildren().length !=
-                               // rn.getRepositoryRegister()
-                               // .getRepositories().size())
-                               // doRefresh = true;
-                       }
-                       if (doRefresh) {
-                               element.clearChildren();
-                               element.getChildren();
-                       }
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java
deleted file mode 100644 (file)
index 1707681..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java
deleted file mode 100644 (file)
index d1d1e31..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java
deleted file mode 100644 (file)
index cc8479f..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java
deleted file mode 100644 (file)
index 00449df..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-package org.argeo.cms.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.Keyring;
-import org.argeo.cms.ui.jcr.model.RepositoriesElem;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.eclipse.ui.TreeParent;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * Implementation of the {@code ITreeContentProvider} to display multiple
- * repository environment in a tree like structure
- */
-public class NodeContentProvider implements ITreeContentProvider {
-       private static final long serialVersionUID = -4083809398848374403L;
-       final private RepositoryRegister repositoryRegister;
-       final private RepositoryFactory repositoryFactory;
-
-       // Current user session on the default workspace of the argeo Node
-       final private Session userSession;
-       final private Keyring keyring;
-       private boolean sortChildren;
-
-       // Reference for cleaning
-       private SingleJcrNodeElem homeNode = null;
-       private RepositoriesElem repositoriesNode = null;
-
-       // Utils
-       private TreeBrowserComparator itemComparator = new TreeBrowserComparator();
-
-       public NodeContentProvider(Session userSession, Keyring keyring,
-                       RepositoryRegister repositoryRegister,
-                       RepositoryFactory repositoryFactory, Boolean sortChildren) {
-               this.userSession = userSession;
-               this.keyring = keyring;
-               this.repositoryRegister = repositoryRegister;
-               this.repositoryFactory = repositoryFactory;
-               this.sortChildren = sortChildren;
-       }
-
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-               if (newInput == null)// dispose
-                       return;
-
-               if (userSession != null) {
-                       Node userHome = CmsJcrUtils.getUserHome(userSession);
-                       if (userHome != null) {
-                               // TODO : find a way to dynamically get alias for the node
-                               if (homeNode != null)
-                                       homeNode.dispose();
-                               homeNode = new SingleJcrNodeElem(null, userHome,
-                                               userSession.getUserID(), CmsConstants.EGO_REPOSITORY);
-                       }
-               }
-               if (repositoryRegister != null) {
-                       if (repositoriesNode != null)
-                               repositoriesNode.dispose();
-                       repositoriesNode = new RepositoriesElem("Repositories",
-                                       repositoryRegister, repositoryFactory, null, userSession,
-                                       keyring);
-               }
-       }
-
-       /**
-        * Sends back the first level of the Tree. Independent from inputElement
-        * that can be null
-        */
-       public Object[] getElements(Object inputElement) {
-               List<Object> objs = new ArrayList<Object>();
-               if (homeNode != null)
-                       objs.add(homeNode);
-               if (repositoriesNode != null)
-                       objs.add(repositoriesNode);
-               return objs.toArray();
-       }
-
-       public Object[] getChildren(Object parentElement) {
-               if (parentElement instanceof TreeParent) {
-                       if (sortChildren) {
-                               Object[] tmpArr = ((TreeParent) parentElement).getChildren();
-                               if (tmpArr == null)
-                                       return new Object[0];
-                               TreeParent[] arr = new TreeParent[tmpArr.length];
-                               for (int i = 0; i < tmpArr.length; i++)
-                                       arr[i] = (TreeParent) tmpArr[i];
-                               Arrays.sort(arr, itemComparator);
-                               return arr;
-                       } else
-                               return ((TreeParent) parentElement).getChildren();
-               } else
-                       return new Object[0];
-       }
-
-       /**
-        * Sets whether the content provider should order the children nodes or not.
-        * It is user duty to call a full refresh of the tree after changing this
-        * parameter.
-        */
-       public void setSortChildren(boolean sortChildren) {
-               this.sortChildren = sortChildren;
-       }
-
-       public Object getParent(Object element) {
-               if (element instanceof TreeParent) {
-                       return ((TreeParent) element).getParent();
-               } else
-                       return null;
-       }
-
-       public boolean hasChildren(Object element) {
-               if (element instanceof RepositoriesElem) {
-                       RepositoryRegister rr = ((RepositoriesElem) element)
-                                       .getRepositoryRegister();
-                       return rr.getRepositories().size() > 0;
-               } else if (element instanceof TreeParent) {
-                       TreeParent tp = (TreeParent) element;
-                       return tp.hasChildren();
-               }
-               return false;
-       }
-
-       public void dispose() {
-               if (homeNode != null)
-                       homeNode.dispose();
-               if (repositoriesNode != null) {
-                       // logs out open sessions
-                       // see https://bugzilla.argeo.org/show_bug.cgi?id=23
-                       repositoriesNode.dispose();
-               }
-       }
-
-       /**
-        * Specific comparator for this view. See specification here:
-        * https://www.argeo.org/bugzilla/show_bug.cgi?id=139
-        */
-       private class TreeBrowserComparator implements Comparator<TreeParent> {
-
-               public int category(TreeParent element) {
-                       if (element instanceof SingleJcrNodeElem) {
-                               Node node = ((SingleJcrNodeElem) element).getNode();
-                               try {
-                                       if (node.isNodeType(NodeType.NT_FOLDER))
-                                               return 5;
-                               } catch (RepositoryException e) {
-                                       // TODO Auto-generated catch block
-                                       e.printStackTrace();
-                               }
-                       }
-                       return 10;
-               }
-
-               public int compare(TreeParent o1, TreeParent o2) {
-                       int cat1 = category(o1);
-                       int cat2 = category(o2);
-
-                       if (cat1 != cat2) {
-                               return cat1 - cat2;
-                       }
-                       return o1.getName().compareTo(o2.getName());
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java
deleted file mode 100644 (file)
index a5751c0..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java
deleted file mode 100644 (file)
index 444350a..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java
deleted file mode 100644 (file)
index fd544bb..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java
deleted file mode 100644 (file)
index 37b90f7..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.cms.ui.jcr;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ViewerCell;
-
-/** Default basic label provider for a given JCR Node's properties */
-public class PropertyLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = -5405794508731390147L;
-
-       // To be able to change column order easily
-       public static final int COLUMN_PROPERTY = 0;
-       public static final int COLUMN_VALUE = 1;
-       public static final int COLUMN_TYPE = 2;
-       public static final int COLUMN_ATTRIBUTES = 3;
-
-       // Utils
-       protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.DATE_TIME_FORMAT);
-
-       public void update(ViewerCell cell) {
-               Object element = cell.getElement();
-               cell.setText(getColumnText(element, cell.getColumnIndex()));
-       }
-
-       public String getColumnText(Object element, int columnIndex) {
-               try {
-                       if (element instanceof Property) {
-                               Property prop = (Property) element;
-                               if (prop.isMultiple()) {
-                                       switch (columnIndex) {
-                                       case COLUMN_PROPERTY:
-                                               return prop.getName();
-                                       case COLUMN_VALUE:
-                                               // Corresponding values are listed on children
-                                               return "";
-                                       case COLUMN_TYPE:
-                                               return JcrBrowserUtils.getPropertyTypeAsString(prop);
-                                       case COLUMN_ATTRIBUTES:
-                                               return JcrUtils.getPropertyDefinitionAsString(prop);
-                                       }
-                               } else {
-                                       switch (columnIndex) {
-                                       case COLUMN_PROPERTY:
-                                               return prop.getName();
-                                       case COLUMN_VALUE:
-                                               return formatValueAsString(prop.getValue());
-                                       case COLUMN_TYPE:
-                                               return JcrBrowserUtils.getPropertyTypeAsString(prop);
-                                       case COLUMN_ATTRIBUTES:
-                                               return JcrUtils.getPropertyDefinitionAsString(prop);
-                                       }
-                               }
-                       } else if (element instanceof Value) {
-                               Value val = (Value) element;
-                               switch (columnIndex) {
-                               case COLUMN_PROPERTY:
-                                       // Nothing to show
-                                       return "";
-                               case COLUMN_VALUE:
-                                       return formatValueAsString(val);
-                               case COLUMN_TYPE:
-                                       // listed on the parent
-                                       return "";
-                               case COLUMN_ATTRIBUTES:
-                                       // Corresponding attributes are listed on the parent
-                                       return "";
-                               }
-                       }
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Cannot retrieve prop value on " + element, re);
-               }
-               return null;
-       }
-
-       private String formatValueAsString(Value value) {
-               // TODO enhance this method
-               try {
-                       String strValue;
-
-                       if (value.getType() == PropertyType.BINARY)
-                               strValue = "<binary>";
-                       else if (value.getType() == PropertyType.DATE)
-                               strValue = timeFormatter.format(value.getDate().getTime());
-                       else
-                               strValue = value.getString();
-                       return strValue;
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("unexpected error while formatting value", e);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java
deleted file mode 100644 (file)
index 802c756..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java
deleted file mode 100644 (file)
index 37dfe2b..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-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/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java
deleted file mode 100644 (file)
index 61654b6..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import javax.jcr.Repository;
-
-import org.argeo.eclipse.ui.TreeParent;
-
-/** Wrap a MaintainedRepository */
-public class MaintainedRepositoryElem extends RepositoryElem {
-
-       public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) {
-               super(alias, repository, parent);
-               // if (!(repository instanceof MaintainedRepository)) {
-               // throw new ArgeoException("Repository " + alias
-               // + " is not a maintained repository");
-               // }
-       }
-
-       // protected MaintainedRepository getMaintainedRepository() {
-       // return (MaintainedRepository) getRepository();
-       // }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java
deleted file mode 100644 (file)
index 428e7f1..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import java.util.Arrays;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.Keyring;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/** Root of a remote repository */
-public class RemoteRepositoryElem extends RepositoryElem {
-       private final Keyring keyring;
-       /**
-        * A session of the logged in user on the default workspace of the node
-        * repository.
-        */
-       private final Session userSession;
-       private final String remoteNodePath;
-
-       private final RepositoryFactory repositoryFactory;
-       private final String uri;
-
-       public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent,
-                       Session userSession, Keyring keyring, String remoteNodePath) {
-               super(alias, null, parent);
-               this.repositoryFactory = repositoryFactory;
-               this.uri = uri;
-               this.keyring = keyring;
-               this.userSession = userSession;
-               this.remoteNodePath = remoteNodePath;
-       }
-
-       @Override
-       protected Session repositoryLogin(String workspaceName) throws RepositoryException {
-               Node remoteRepository = userSession.getNode(remoteNodePath);
-               String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString();
-               if (userID.trim().equals("")) {
-                       return getRepository().login(workspaceName);
-               } else {
-                       String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD;
-                       char[] password = keyring.getAsChars(pwdPath);
-                       try {
-                               SimpleCredentials credentials = new SimpleCredentials(userID, password);
-                               return getRepository().login(credentials, workspaceName);
-                       } finally {
-                               Arrays.fill(password, 0, password.length, ' ');
-                       }
-               }
-       }
-
-       @Override
-       public Repository getRepository() {
-               if (repository == null)
-                       repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri);
-               return super.getRepository();
-       }
-
-       public void remove() {
-               try {
-                       Node remoteNode = userSession.getNode(remoteNodePath);
-                       remoteNode.remove();
-                       remoteNode.getSession().save();
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot remove " + remoteNodePath, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java
deleted file mode 100644 (file)
index 8586332..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.jcr.Session;
-
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.security.Keyring;
-import org.argeo.cms.ui.jcr.RepositoryRegister;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-
-/**
- * UI Tree component that implements the Argeo abstraction of a
- * {@link RepositoryFactory} that enable a user to "mount" various repositories
- * in a single Tree like View. It is usually meant to be at the root of the UI
- * Tree and thus {@link getParent()} method will return null.
- * 
- * The {@link RepositoryFactory} is injected at instantiation time and must be
- * use get or register new {@link Repository} objects upon which a reference is
- * kept here.
- */
-
-public class RepositoriesElem extends TreeParent implements ArgeoNames {
-       private final RepositoryRegister repositoryRegister;
-       private final RepositoryFactory repositoryFactory;
-
-       /**
-        * A session of the logged in user on the default workspace of the node
-        * repository.
-        */
-       private final Session userSession;
-       private final Keyring keyring;
-
-       public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory,
-                       TreeParent parent, Session userSession, Keyring keyring) {
-               super(name);
-               this.repositoryRegister = repositoryRegister;
-               this.repositoryFactory = repositoryFactory;
-               this.userSession = userSession;
-               this.keyring = keyring;
-       }
-
-       /**
-        * Override normal behavior to initialize the various repositories only at
-        * request time
-        */
-       @Override
-       public synchronized Object[] getChildren() {
-               if (isLoaded()) {
-                       return super.getChildren();
-               } else {
-                       // initialize current object
-                       Map<String, Repository> refRepos = repositoryRegister.getRepositories();
-                       for (String name : refRepos.keySet()) {
-                               Repository repository = refRepos.get(name);
-                               // if (repository instanceof MaintainedRepository)
-                               // super.addChild(new MaintainedRepositoryElem(name,
-                               // repository, this));
-                               // else
-                               super.addChild(new RepositoryElem(name, repository, this));
-                       }
-
-                       // remote
-                       if (keyring != null) {
-                               try {
-                                       addRemoteRepositories(keyring);
-                               } catch (RepositoryException e) {
-                                       throw new EclipseUiException("Cannot browse remote repositories", e);
-                               }
-                       }
-                       return super.getChildren();
-               }
-       }
-
-       protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException {
-               Node userHome = CmsJcrUtils.getUserHome(userSession);
-               if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) {
-                       NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes();
-                       while (it.hasNext()) {
-                               Node remoteNode = it.nextNode();
-                               String uri = remoteNode.getProperty(ARGEO_URI).getString();
-                               try {
-                                       RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(),
-                                                       repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath());
-                                       super.addChild(remoteRepositoryNode);
-                               } catch (Exception e) {
-                                       ErrorFeedback.show("Cannot add remote repository " + remoteNode, e);
-                               }
-                       }
-               }
-       }
-
-       public void registerNewRepository(String alias, Repository repository) {
-               // TODO: implement this
-               // Create a new RepositoryNode Object
-               // add it
-               // super.addChild(new RepositoriesNode(...));
-       }
-
-       /** Returns the {@link RepositoryRegister} wrapped by this object. */
-       public RepositoryRegister getRepositoryRegister() {
-               return repositoryRegister;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java
deleted file mode 100644 (file)
index afff3ef..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * UI Tree component that wraps a JCR {@link Repository}. It also keeps a
- * reference to its parent Tree Ui component; typically the unique
- * {@link RepositoriesElem} object of the current view to enable bi-directionnal
- * browsing in the tree.
- */
-
-public class RepositoryElem extends TreeParent {
-       private String alias;
-       protected Repository repository;
-       private Session defaultSession = null;
-
-       /** Create a new repository with distinct name and alias */
-       public RepositoryElem(String alias, Repository repository, TreeParent parent) {
-               super(alias);
-               this.repository = repository;
-               setParent(parent);
-               this.alias = alias;
-       }
-
-       public void login() {
-               try {
-                       defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE);
-                       String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
-                       for (String wkpName : wkpNames) {
-                               if (wkpName.equals(defaultSession.getWorkspace().getName()))
-                                       addChild(new WorkspaceElem(this, wkpName, defaultSession));
-                               else
-                                       addChild(new WorkspaceElem(this, wkpName));
-                       }
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot connect to repository " + alias, e);
-               }
-       }
-
-       public synchronized void logout() {
-               for (Object child : getChildren()) {
-                       if (child instanceof WorkspaceElem)
-                               ((WorkspaceElem) child).logout();
-               }
-               clearChildren();
-               JcrUtils.logoutQuietly(defaultSession);
-               defaultSession = null;
-       }
-
-       /**
-        * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)}
-        * method. To be overridden.
-        */
-       protected Session repositoryLogin(String workspaceName) throws RepositoryException {
-               return repository.login(workspaceName);
-       }
-
-       public String[] getAccessibleWorkspaceNames() {
-               try {
-                       return defaultSession.getWorkspace().getAccessibleWorkspaceNames();
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot retrieve workspace names", e);
-               }
-       }
-
-       public void createWorkspace(String workspaceName) {
-               if (!isConnected())
-                       login();
-               try {
-                       defaultSession.getWorkspace().createWorkspace(workspaceName);
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot create workspace", e);
-               }
-       }
-
-       /** returns the {@link Repository} referenced by the current UI Node */
-       public Repository getRepository() {
-               return repository;
-       }
-
-       public String getAlias() {
-               return alias;
-       }
-
-       public Boolean isConnected() {
-               if (defaultSession != null && defaultSession.isLive())
-                       return true;
-               else
-                       return false;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java
deleted file mode 100644 (file)
index 859deee..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Workspace;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-
-/**
- * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a
- * reference to its parent node that can either be a {@link WorkspaceElem}, a
- * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the
- * UI tree.
- */
-public class SingleJcrNodeElem extends TreeParent {
-
-       private final Node node;
-       private String alias = null;
-
-       /** Creates a new UiNode in the UI Tree */
-       public SingleJcrNodeElem(TreeParent parent, Node node, String name) {
-               super(name);
-               setParent(parent);
-               this.node = node;
-       }
-
-       /**
-        * Creates a new UiNode in the UI Tree, keeping a reference to the alias of
-        * the corresponding repository in the current UI environment. It is useful
-        * to be able to mount nodes as roots of the UI tree.
-        */
-       public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) {
-               super(name);
-               setParent(parent);
-               this.node = node;
-               this.alias = alias;
-       }
-
-       /** Returns the node wrapped by the current UI object */
-       public Node getNode() {
-               return node;
-       }
-
-       protected String getRepositoryAlias() {
-               return alias;
-       }
-
-       /**
-        * Overrides normal behaviour to initialise children only when first
-        * requested
-        */
-       @Override
-       public synchronized Object[] getChildren() {
-               if (isLoaded()) {
-                       return super.getChildren();
-               } else {
-                       // initialize current object
-                       try {
-                               NodeIterator ni = node.getNodes();
-                               while (ni.hasNext()) {
-                                       Node curNode = ni.nextNode();
-                                       addChild(new SingleJcrNodeElem(this, curNode, curNode.getName()));
-                               }
-                               return super.getChildren();
-                       } catch (RepositoryException re) {
-                               throw new EclipseUiException("Cannot initialize SingleJcrNode children", re);
-                       }
-               }
-       }
-
-       @Override
-       public boolean hasChildren() {
-               try {
-                       if (node.getSession().isLive())
-                               return node.hasNodes();
-                       else
-                               return false;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Cannot check children node existence", re);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java
deleted file mode 100644 (file)
index 24fc575..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.argeo.cms.ui.jcr.model;
-
-import javax.jcr.AccessDeniedException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-// import javax.jcr.Workspace;
-import javax.jcr.Workspace;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.TreeParent;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also
- * keeps a reference to its parent {@link RepositoryElem}, to be able to
- * retrieve alias of the current used repository
- */
-public class WorkspaceElem extends TreeParent {
-       private Session session = null;
-
-       public WorkspaceElem(RepositoryElem parent, String name) {
-               this(parent, name, null);
-       }
-
-       public WorkspaceElem(RepositoryElem parent, String name, Session session) {
-               super(name);
-               this.session = session;
-               setParent(parent);
-       }
-
-       public synchronized Session getSession() {
-               return session;
-       }
-
-       public synchronized Node getRootNode() {
-               try {
-                       if (session != null)
-                               return session.getRootNode();
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot get root node of workspace " + getName(), e);
-               }
-       }
-
-       public synchronized void login() {
-               try {
-                       session = ((RepositoryElem) getParent()).repositoryLogin(getName());
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot connect to repository " + getName(), e);
-               }
-       }
-
-       public Boolean isConnected() {
-               if (session != null && session.isLive())
-                       return true;
-               else
-                       return false;
-       }
-
-       @Override
-       public synchronized void dispose() {
-               logout();
-               super.dispose();
-       }
-
-       /** Logouts the session, does not nothing if there is no live session. */
-       public synchronized void logout() {
-               clearChildren();
-               JcrUtils.logoutQuietly(session);
-               session = null;
-       }
-
-       @Override
-       public synchronized boolean hasChildren() {
-               try {
-                       if (isConnected())
-                               try {
-                                       return session.getRootNode().hasNodes();
-                               } catch (AccessDeniedException e) {
-                                       // current user may not have access to the root node
-                                       return false;
-                               }
-                       else
-                               return false;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unexpected error while checking children node existence", re);
-               }
-       }
-
-       /** Override normal behaviour to initialize display of the workspace */
-       @Override
-       public synchronized Object[] getChildren() {
-               if (isLoaded()) {
-                       return super.getChildren();
-               } else {
-                       // initialize current object
-                       try {
-                               Node rootNode;
-                               if (session == null)
-                                       return null;
-                               else
-                                       rootNode = session.getRootNode();
-                               NodeIterator ni = rootNode.getNodes();
-                               while (ni.hasNext()) {
-                                       Node node = ni.nextNode();
-                                       addChild(new SingleJcrNodeElem(this, node, node.getName()));
-                               }
-                               return super.getChildren();
-                       } catch (RepositoryException e) {
-                               throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e);
-                       }
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java
deleted file mode 100644 (file)
index 8f54744..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Model for SWT/JFace JCR components. */
-package org.argeo.cms.ui.jcr.model;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java
deleted file mode 100644 (file)
index 26ae330..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace JCR components. */
-package org.argeo.cms.ui.jcr;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java
deleted file mode 100644 (file)
index 82fdee7..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace components for Argeo CMS. */
-package org.argeo.cms.ui;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java
deleted file mode 100644 (file)
index 3821e60..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsStyle;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.JcrException;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-
-/** A link to an internal or external location. */
-public class CmsLink implements CmsUiProvider {
-       private final static CmsLog log = CmsLog.getLog(CmsLink.class);
-       private BundleContext bundleContext;
-
-       private String label;
-       private String style;
-       private String target;
-       private String image;
-       private boolean openNew = false;
-       private MouseListener mouseListener;
-
-       private int horizontalAlignment = SWT.CENTER;
-       private int verticalAlignment = SWT.CENTER;
-
-       private String loggedInLabel = null;
-       private String loggedInTarget = null;
-
-       // internal
-       // private Boolean isUrl = false;
-       private Integer imageWidth, imageHeight;
-
-       public CmsLink() {
-               super();
-       }
-
-       public CmsLink(String label, String target) {
-               this(label, target, (String) null);
-       }
-
-       public CmsLink(String label, String target, CmsStyle style) {
-               this(label, target, style != null ? style.style() : null);
-       }
-
-       public CmsLink(String label, String target, String style) {
-               super();
-               this.label = label;
-               this.target = target;
-               this.style = style;
-               init();
-       }
-
-       public void init() {
-               if (image != null) {
-                       ImageData image = loadImage();
-                       if (imageHeight == null && imageWidth == null) {
-                               imageWidth = image.width;
-                               imageHeight = image.height;
-                       } else if (imageHeight == null) {
-                               imageHeight = (imageWidth * image.height) / image.width;
-                       } else if (imageWidth == null) {
-                               imageWidth = (imageHeight * image.width) / image.height;
-                       }
-               }
-       }
-
-       /** @return {@link Composite} with a single {@link Label} child. */
-       @Override
-       public Control createUi(final Composite parent, Node context) {
-//             if (image != null && (imageWidth == null || imageHeight == null)) {
-//                     throw new CmsException("Image is not properly configured."
-//                                     + " Make sure bundleContext property is set and init() method has been called.");
-//             }
-
-               Composite comp = new Composite(parent, SWT.NONE);
-               comp.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               Label link = new Label(comp, SWT.NONE);
-               CmsSwtUtils.markup(link);
-               GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false);
-               if (image != null) {
-                       if (imageHeight != null)
-                               layoutData.heightHint = imageHeight;
-                       if (label == null)
-                               if (imageWidth != null)
-                                       layoutData.widthHint = imageWidth;
-               }
-
-               link.setLayoutData(layoutData);
-               CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle());
-               CmsSwtUtils.style(link, style != null ? style : getDefaultStyle());
-
-               // label
-               StringBuilder labelText = new StringBuilder();
-               if (loggedInTarget != null && isLoggedIn()) {
-                       labelText.append("<a style='color:inherit;text-decoration:inherit;' href=\"");
-                       if (loggedInTarget.equals("")) {
-                               try {
-                                       Node homeNode = CmsJcrUtils.getUserHome(context.getSession());
-                                       String homePath = homeNode.getPath();
-                                       labelText.append("/#" + homePath);
-                               } catch (RepositoryException e) {
-                                       throw new JcrException("Cannot get home path", e);
-                               }
-                       } else {
-                               labelText.append(loggedInTarget);
-                       }
-                       labelText.append("\">");
-               } else if (target != null) {
-                       labelText.append("<a style='color:inherit;text-decoration:inherit;' href='");
-                       labelText.append(target).append("'");
-                       if (openNew) {
-                               labelText.append(" target='_blank'");
-                       }
-                       labelText.append(">");
-               }
-               if (image != null) {
-                       registerImageIfNeeded();
-                       String imageLocation = RWT.getResourceManager().getLocation(image);
-                       labelText.append("<img");
-                       if (imageWidth != null)
-                               labelText.append(" width='").append(imageWidth).append('\'');
-                       if (imageHeight != null)
-                               labelText.append(" height='").append(imageHeight).append('\'');
-                       labelText.append(" src=\"").append(imageLocation).append("\"/>");
-
-               }
-
-               if (loggedInLabel != null && isLoggedIn()) {
-                       labelText.append(' ').append(loggedInLabel);
-               } else if (label != null) {
-                       labelText.append(' ').append(label);
-               }
-
-               if ((loggedInTarget != null && isLoggedIn()) || target != null)
-                       labelText.append("</a>");
-
-               link.setText(labelText.toString());
-
-               if (mouseListener != null)
-                       link.addMouseListener(mouseListener);
-
-               return comp;
-       }
-
-       private void registerImageIfNeeded() {
-               ResourceManager resourceManager = RWT.getResourceManager();
-               if (!resourceManager.isRegistered(image)) {
-                       URL res = getImageUrl();
-                       try (InputStream inputStream = res.openStream()) {
-                               resourceManager.register(image, inputStream);
-                               if (log.isTraceEnabled())
-                                       log.trace("Registered image " + image);
-                       } catch (IOException e) {
-                               throw new RuntimeException("Cannot load image " + image, e);
-                       }
-               }
-       }
-
-       private ImageData loadImage() {
-               URL url = getImageUrl();
-               ImageData result = null;
-               try (InputStream inputStream = url.openStream()) {
-                       result = new ImageData(inputStream);
-                       if (log.isTraceEnabled())
-                               log.trace("Loaded image " + image);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot load image " + image, e);
-               }
-               return result;
-       }
-
-       private URL getImageUrl() {
-               URL url;
-               try {
-                       // pure URL
-                       url = new URL(image);
-               } catch (MalformedURLException e1) {
-                       url = bundleContext.getBundle().getResource(image);
-               }
-
-               if (url == null)
-                       throw new IllegalStateException("No image " + image + " available.");
-
-               return url;
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-       public void setLabel(String label) {
-               this.label = label;
-       }
-
-       public void setStyle(String style) {
-               this.style = style;
-       }
-
-       /** @deprecated Use {@link #setStyle(String)} instead. */
-       @Deprecated
-       public void setCustom(String custom) {
-               this.style = custom;
-       }
-
-       public void setTarget(String target) {
-               this.target = target;
-               // try {
-               // new URL(target);
-               // isUrl = true;
-               // } catch (MalformedURLException e1) {
-               // isUrl = false;
-               // }
-       }
-
-       public void setImage(String image) {
-               this.image = image;
-       }
-
-       public void setLoggedInLabel(String loggedInLabel) {
-               this.loggedInLabel = loggedInLabel;
-       }
-
-       public void setLoggedInTarget(String loggedInTarget) {
-               this.loggedInTarget = loggedInTarget;
-       }
-
-       public void setMouseListener(MouseListener mouseListener) {
-               this.mouseListener = mouseListener;
-       }
-
-       public void setvAlign(String vAlign) {
-               if ("bottom".equals(vAlign)) {
-                       verticalAlignment = SWT.BOTTOM;
-               } else if ("top".equals(vAlign)) {
-                       verticalAlignment = SWT.TOP;
-               } else if ("center".equals(vAlign)) {
-                       verticalAlignment = SWT.CENTER;
-               } else {
-                       throw new IllegalArgumentException(
-                                       "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)");
-               }
-       }
-
-       protected boolean isLoggedIn() {
-               return !CurrentUser.isAnonymous();
-       }
-
-       public void setImageWidth(Integer imageWidth) {
-               this.imageWidth = imageWidth;
-       }
-
-       public void setImageHeight(Integer imageHeight) {
-               this.imageHeight = imageHeight;
-       }
-
-       public void setOpenNew(boolean openNew) {
-               this.openNew = openNew;
-       }
-
-       protected String getDefaultStyle() {
-               return SimpleStyle.link.name();
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java
deleted file mode 100644 (file)
index fc0c821..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Composite;
-
-/** The main pane of a CMS display, with QA and support areas. */
-public class CmsPane {
-
-       private Composite mainArea;
-       private Composite qaArea;
-       private Composite supportArea;
-
-       public CmsPane(Composite parent, int style) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-//             qaArea = new Composite(parent, SWT.NONE);
-//             qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-//             RowLayout qaLayout = new RowLayout();
-//             qaLayout.spacing = 0;
-//             qaArea.setLayout(qaLayout);
-
-               mainArea = new Composite(parent, SWT.NONE);
-               mainArea.setLayout(new GridLayout());
-               mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-//             supportArea = new Composite(parent, SWT.NONE);
-//             supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-//             RowLayout supportLayout = new RowLayout();
-//             supportLayout.spacing = 0;
-//             supportArea.setLayout(supportLayout);
-       }
-
-       public Composite getMainArea() {
-               return mainArea;
-       }
-
-       public Composite getQaArea() {
-               return qaArea;
-       }
-
-       public Composite getSupportArea() {
-               return supportArea;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java
deleted file mode 100644 (file)
index 8b38479..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.StringTokenizer;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.layout.RowData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Table;
-
-/** Static utilities for the CMS framework. */
-public class CmsUiUtils {
-       // private final static Log log = LogFactory.getLog(CmsUiUtils.class);
-
-       /*
-        * CMS VIEW
-        */
-
-       /**
-        * The CMS view related to this display, or null if none is available from this
-        * call.
-        * 
-        * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead.
-        */
-       @Deprecated
-       public static CmsView getCmsView() {
-//             return UiContext.getData(CmsView.class.getName());
-               return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell());
-       }
-
-       public static StringBuilder getServerBaseUrl(HttpServletRequest request) {
-               try {
-                       URL url = new URL(request.getRequestURL().toString());
-                       StringBuilder buf = new StringBuilder();
-                       buf.append(url.getProtocol()).append("://").append(url.getHost());
-                       if (url.getPort() != -1)
-                               buf.append(':').append(url.getPort());
-                       return buf;
-               } catch (MalformedURLException e) {
-                       throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e);
-               }
-       }
-
-       //
-       public static String getDataUrl(Node node, HttpServletRequest request) {
-               try {
-                       StringBuilder buf = getServerBaseUrl(request);
-                       buf.append(getDataPath(node));
-                       return new URL(buf.toString()).toString();
-               } catch (MalformedURLException e) {
-                       throw new IllegalArgumentException("Cannot build data URL for " + node, e);
-               }
-       }
-
-       /** A path in the node repository */
-       public static String getDataPath(Node node) {
-               return getDataPath(CmsConstants.EGO_REPOSITORY, node);
-       }
-
-       public static String getDataPath(String cn, Node node) {
-               return CmsJcrUtils.getDataPath(cn, node);
-       }
-
-       /** Clean reserved URL characters for use in HTTP links. */
-       public static String getDataPathForUrl(Node node) {
-               return cleanPathForUrl(getDataPath(node));
-       }
-
-       /** Clean reserved URL characters for use in HTTP links. */
-       public static String cleanPathForUrl(String path) {
-               StringTokenizer st = new StringTokenizer(path, "/");
-               StringBuilder sb = new StringBuilder();
-               while (st.hasMoreElements()) {
-                       sb.append('/');
-                       String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
-                       encoded = encoded.replace("+", "%20");
-                       sb.append(encoded);
-
-               }
-               return sb.toString();
-       }
-
-       /** @deprecated Use rowData16px() instead. GridData should not be reused. */
-       @Deprecated
-       public static RowData ROW_DATA_16px = new RowData(16, 16);
-
-       
-
-       /*
-        * FORM LAYOUT
-        */
-
-       
-
-       @Deprecated
-       public static void setItemHeight(Table table, int height) {
-               table.setData(CmsUiConstants.ITEM_HEIGHT, height);
-       }
-
-       //
-       // JCR
-       //
-       public static Node getOrAddEmptyFile(Node parent, Enum<?> child) throws RepositoryException {
-               if (has(parent, child))
-                       return child(parent, child);
-               return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]);
-       }
-
-       public static Node child(Node parent, Enum<?> en) throws RepositoryException {
-               return parent.getNode(en.name());
-       }
-
-       public static Boolean has(Node parent, Enum<?> en) throws RepositoryException {
-               return parent.hasNode(en.name());
-       }
-
-       public static Node getOrAdd(Node parent, Enum<?> en) throws RepositoryException {
-               return getOrAdd(parent, en, null);
-       }
-
-       public static Node getOrAdd(Node parent, Enum<?> en, String primaryType) throws RepositoryException {
-               if (has(parent, en))
-                       return child(parent, en);
-               else if (primaryType == null)
-                       return parent.addNode(en.name());
-               else
-                       return parent.addNode(en.name(), primaryType);
-       }
-
-       // IMAGES
-
-       public static String img(Node fileNode, String width, String height) {
-               return img(null, fileNode, width, height);
-       }
-
-       public static String img(String serverBase, Node fileNode, String width, String height) {
-//             String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode);
-               String src;
-               src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode);
-               return imgBuilder(src, width, height).append("/>").toString();
-       }
-
-       public static String img(String src, String width, String height) {
-               return imgBuilder(src, width, height).append("/>").toString();
-       }
-
-       public static String img(String src, Cms2DSize size) {
-               return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
-       }
-
-       public static StringBuilder imgBuilder(String src, String width, String height) {
-               return new StringBuilder(64).append("<img width='").append(width).append("' height='").append(height)
-                               .append("' src='").append(src).append("'");
-       }
-
-       public static String noImg(Cms2DSize size) {
-               ResourceManager rm = RWT.getResourceManager();
-               return CmsUiUtils.img(rm.getLocation(CmsUiConstants.NO_IMAGE), size);
-       }
-
-       public static String noImg() {
-               return noImg(CmsUiConstants.NO_IMAGE_SIZE);
-       }
-
-       public static Image noImage(Cms2DSize size) {
-               ResourceManager rm = RWT.getResourceManager();
-               InputStream in = null;
-               try {
-                       in = rm.getRegisteredContent(CmsUiConstants.NO_IMAGE);
-                       ImageData id = new ImageData(in);
-                       ImageData scaled = id.scaledTo(size.getWidth(), size.getHeight());
-                       Image image = new Image(Display.getCurrent(), scaled);
-                       return image;
-               } finally {
-                       try {
-                               in.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-       }
-
-       /** Lorem ipsum text to be used during development. */
-       public final static String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
-                       + " Etiam eleifend hendrerit sem, ac ultricies massa ornare ac."
-                       + " Cras aliquam sodales risus, vitae varius lacus molestie quis."
-                       + " Vivamus consequat, leo id lacinia volutpat, eros diam efficitur urna, finibus interdum risus turpis at nisi."
-                       + " Curabitur vulputate nulla quis scelerisque fringilla. Integer consectetur turpis id lobortis accumsan."
-                       + " Pellentesque commodo turpis ac diam ultricies dignissim."
-                       + " Curabitur sit amet dolor volutpat lacus aliquam ornare quis sed velit."
-                       + " Integer varius quis est et tristique."
-                       + " Suspendisse pharetra porttitor purus, eget condimentum magna."
-                       + " Duis vitae turpis eros. Sed tincidunt lacinia rutrum."
-                       + " Aliquam velit velit, rutrum ut augue sed, condimentum lacinia augue.";
-
-       /** Singleton. */
-       private CmsUiUtils() {
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java
deleted file mode 100644 (file)
index 1fc9bd1..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import static javax.jcr.Node.JCR_CONTENT;
-import static javax.jcr.Property.JCR_DATA;
-import static javax.jcr.nodetype.NodeType.NT_FILE;
-import static javax.jcr.nodetype.NodeType.NT_RESOURCE;
-import static org.argeo.cms.ui.CmsUiConstants.NO_IMAGE_SIZE;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.service.ResourceManager;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Label;
-
-/** Manages only public images so far. */
-public class DefaultImageManager implements CmsImageManager<Control, Node> {
-       private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class);
-//     private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
-
-       public Boolean load(Node node, Control control, Cms2DSize preferredSize) {
-               Cms2DSize imageSize = getImageSize(node);
-               Cms2DSize size;
-               String imgTag = null;
-               if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0
-                               || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) {
-                       if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) {
-                               // actual image size if completely known
-                               size = imageSize;
-                       } else {
-                               // no image if not completely known
-                               size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize);
-                               imgTag = CmsUiUtils.noImg(size);
-                       }
-
-               } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) {
-                       // given size if completely provided
-                       size = preferredSize;
-               } else {
-                       // at this stage :
-                       // image is completely known
-                       assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0;
-                       // one and only one of the dimension as been specified
-                       assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0;
-                       size = resizeTo(imageSize, preferredSize);
-               }
-
-               boolean loaded = false;
-               if (control == null)
-                       return loaded;
-
-               if (control instanceof Label) {
-                       if (imgTag == null) {
-                               // IMAGE RETRIEVED HERE
-                               imgTag = getImageTag(node, size);
-                               //
-                               if (imgTag == null)
-                                       imgTag = CmsUiUtils.noImg(size);
-                               else
-                                       loaded = true;
-                       }
-
-                       Label lbl = (Label) control;
-                       lbl.setText(imgTag);
-                       // lbl.setSize(size);
-               } else if (control instanceof FileUpload) {
-                       FileUpload lbl = (FileUpload) control;
-                       lbl.setImage(CmsUiUtils.noImage(size));
-                       lbl.setSize(new Point(size.getWidth(), size.getHeight()));
-                       return loaded;
-               } else
-                       loaded = false;
-
-               return loaded;
-       }
-
-       private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) {
-               if (constraints.getWidth() != 0 && constraints.getHeight() != 0) {
-                       return constraints;
-               } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) {
-                       return orig;
-               } else if (constraints.getHeight() == 0) {// force width
-                       return new Cms2DSize(constraints.getWidth(),
-                                       scale(orig.getHeight(), orig.getWidth(), constraints.getWidth()));
-               } else if (constraints.getWidth() == 0) {// force height
-                       return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()),
-                                       constraints.getHeight());
-               }
-               throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints);
-       }
-
-       private int scale(int origDimension, int otherDimension, int otherConstraint) {
-               return Math.round(origDimension * divide(otherConstraint, otherDimension));
-       }
-
-       private float divide(int a, int b) {
-               return ((float) a) / ((float) b);
-       }
-
-       public Cms2DSize getImageSize(Node node) {
-               // TODO optimise
-               Image image = getSwtImage(node);
-               return new Cms2DSize(image.getBounds().width, image.getBounds().height);
-       }
-
-       /** @return null if not available */
-       @Override
-       public String getImageTag(Node node) {
-               return getImageTag(node, getImageSize(node));
-       }
-
-       private String getImageTag(Node node, Cms2DSize size) {
-               StringBuilder buf = getImageTagBuilder(node, size);
-               if (buf == null)
-                       return null;
-               return buf.append("/>").toString();
-       }
-
-       /** @return null if not available */
-       @Override
-       public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) {
-               return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
-       }
-
-       /** @return null if not available */
-       private StringBuilder getImageTagBuilder(Node node, String width, String height) {
-               String url = getImageUrl(node);
-               if (url == null)
-                       return null;
-               return CmsUiUtils.imgBuilder(url, width, height);
-       }
-
-       /** @return null if not available */
-       @Override
-       public String getImageUrl(Node node) {
-               return CmsUiUtils.getDataPathForUrl(node);
-       }
-
-       protected String getResourceName(Node node) {
-               try {
-                       String workspace = node.getSession().getWorkspace().getName();
-                       if (node.hasNode(JCR_CONTENT))
-                               return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier();
-                       else
-                               return workspace + '_' + node.getIdentifier();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       public Binary getImageBinary(Node node) {
-               try {
-                       if (node.isNodeType(NT_FILE)) {
-                               return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
-                       } else {
-                               return null;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-       public Image getSwtImage(Node node) {
-               InputStream inputStream = null;
-               Binary binary = getImageBinary(node);
-               if (binary == null)
-                       return null;
-               try {
-                       inputStream = binary.getStream();
-                       return new Image(Display.getCurrent(), inputStream);
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } finally {
-                       IOUtils.closeQuietly(inputStream);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       @Override
-       public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) {
-               InputStream inputStream = null;
-               try {
-                       String previousResourceName = null;
-                       if (parentNode.hasNode(fileName)) {
-                               Node node = parentNode.getNode(fileName);
-                               previousResourceName = getResourceName(node);
-                               if (node.hasNode(JCR_CONTENT)) {
-                                       node.getNode(JCR_CONTENT).remove();
-                                       node.addNode(JCR_CONTENT, NT_RESOURCE);
-                               }
-                       }
-
-                       byte[] arr = IOUtils.toByteArray(in);
-                       Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr);
-                       inputStream = new ByteArrayInputStream(arr);
-                       ImageData id = new ImageData(inputStream);
-                       processNewImageFile(context, fileNode, id);
-
-                       String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName));
-                       if (mime != null) {
-                               fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime);
-                       }
-                       fileNode.getSession().save();
-
-                       // reset resource manager
-                       ResourceManager resourceManager = RWT.getResourceManager();
-                       if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) {
-                               resourceManager.unregister(previousResourceName);
-                               if (log.isDebugEnabled())
-                                       log.debug("Unregistered image " + previousResourceName);
-                       }
-                       return CmsUiUtils.getDataPath(fileNode);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e);
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } finally {
-                       IOUtils.closeQuietly(inputStream);
-               }
-       }
-
-       /** Does nothing by default. */
-       protected void processNewImageFile(Node context, Node fileNode, ImageData id)
-                       throws RepositoryException, IOException {
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java
deleted file mode 100644 (file)
index 284d2bd..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.swt.CmsStyles;
-
-/**
- * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on
- * a {@link CmsLink} when simple menus are used.
- */
-public class MenuLink extends CmsLink {
-       public MenuLink() {
-               setCustom(CmsStyles.CMS_MENU_LINK);
-       }
-
-       public MenuLink(String label, String target, String custom) {
-               super(label, target, custom);
-       }
-
-       public MenuLink(String label, String target) {
-               super(label, target, CmsStyles.CMS_MENU_LINK);
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java
deleted file mode 100644 (file)
index e8bf662..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** A header in three parts */
-public class SimpleCmsHeader implements CmsUiProvider {
-       private List<CmsUiProvider> lead = new ArrayList<CmsUiProvider>();
-       private List<CmsUiProvider> center = new ArrayList<CmsUiProvider>();
-       private List<CmsUiProvider> end = new ArrayList<CmsUiProvider>();
-
-       private Boolean subPartsSameWidth = false;
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               Composite header = new Composite(parent, SWT.NONE);
-               header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER);
-               header.setBackgroundMode(SWT.INHERIT_DEFAULT);
-               header.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
-
-               configurePart(context, header, lead);
-               configurePart(context, header, center);
-               configurePart(context, header, end);
-               return header;
-       }
-
-       protected void configurePart(Node context, Composite parent, List<CmsUiProvider> partProviders)
-                       throws RepositoryException {
-               final int style;
-               final String custom;
-               if (lead == partProviders) {
-                       style = SWT.LEAD;
-                       custom = CmsStyles.CMS_HEADER_LEAD;
-               } else if (center == partProviders) {
-                       style = SWT.CENTER;
-                       custom = CmsStyles.CMS_HEADER_CENTER;
-               } else if (end == partProviders) {
-                       style = SWT.END;
-                       custom = CmsStyles.CMS_HEADER_END;
-               } else {
-                       throw new CmsException("Unsupported part providers " + partProviders);
-               }
-
-               Composite part = new Composite(parent, SWT.NONE);
-               part.setData(RWT.CUSTOM_VARIANT, custom);
-               GridData gridData = new GridData(style, SWT.FILL, true, true);
-               part.setLayoutData(gridData);
-               part.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(partProviders.size(), subPartsSameWidth)));
-               for (CmsUiProvider uiProvider : partProviders) {
-                       Control subPart = uiProvider.createUi(part, context);
-                       subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               }
-       }
-
-       public void setLead(List<CmsUiProvider> lead) {
-               this.lead = lead;
-       }
-
-       public void setCenter(List<CmsUiProvider> center) {
-               this.center = center;
-       }
-
-       public void setEnd(List<CmsUiProvider> end) {
-               this.end = end;
-       }
-
-       public void setSubPartsSameWidth(Boolean subPartsSameWidth) {
-               this.subPartsSameWidth = subPartsSameWidth;
-       }
-
-       public List<CmsUiProvider> getLead() {
-               return lead;
-       }
-
-       public List<CmsUiProvider> getCenter() {
-               return center;
-       }
-
-       public List<CmsUiProvider> getEnd() {
-               return end;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java
deleted file mode 100644 (file)
index 8e0e7c1..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class SimpleDynamicPages implements CmsUiProvider {
-
-       @Override
-       public Control createUi(Composite parent, Node context)
-                       throws RepositoryException {
-               if (context == null)
-                       throw new CmsException("Context cannot be null");
-               parent.setLayout(new GridLayout(2, false));
-
-               // parent
-               if (!context.getPath().equals("/")) {
-                       new CmsLink("..", context.getParent().getPath()).createUi(parent,
-                                       context);
-                       new Label(parent, SWT.NONE).setText(context.getParent()
-                                       .getPrimaryNodeType().getName());
-               }
-
-               // context
-               Label contextL = new Label(parent, SWT.NONE);
-               contextL.setData(RWT.MARKUP_ENABLED, true);
-               contextL.setText("<b>" + context.getName() + "</b>");
-               new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType()
-                               .getName());
-
-               // children
-               // Label childrenL = new Label(parent, SWT.NONE);
-               // childrenL.setData(RWT.MARKUP_ENABLED, true);
-               // childrenL.setText("<i>Children:</i>");
-               // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false,
-               // false, 2, 1));
-
-               for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
-                       Node child = nIt.nextNode();
-                       new CmsLink(child.getName(), child.getPath()).createUi(parent,
-                                       context);
-
-                       new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType()
-                                       .getName());
-               }
-
-               // properties
-               // Label propsL = new Label(parent, SWT.NONE);
-               // propsL.setData(RWT.MARKUP_ENABLED, true);
-               // propsL.setText("<i>Properties:</i>");
-               // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false,
-               // 2, 1));
-               for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) {
-                       Property property = pIt.nextProperty();
-
-                       Label label = new Label(parent, SWT.NONE);
-                       label.setText(property.getName());
-                       label.setToolTipText(JcrUtils
-                                       .getPropertyDefinitionAsString(property));
-
-                       new Label(parent, SWT.NONE).setText(getPropAsString(property));
-               }
-
-               return null;
-       }
-
-       private String getPropAsString(Property property)
-                       throws RepositoryException {
-               String result = "";
-               DateFormat timeFormatter = new SimpleDateFormat("");
-               if (property.isMultiple()) {
-                       result = getMultiAsString(property, ", ");
-               } else {
-                       Value value = property.getValue();
-                       if (value.getType() == PropertyType.BINARY)
-                               result = "<binary>";
-                       else if (value.getType() == PropertyType.DATE)
-                               result = timeFormatter.format(value.getDate().getTime());
-                       else
-                               result = value.getString();
-               }
-               return result;
-       }
-
-       private String getMultiAsString(Property property, String separator)
-                       throws RepositoryException {
-               if (separator == null)
-                       separator = "; ";
-               Value[] values = property.getValues();
-               StringBuilder builder = new StringBuilder();
-               for (Value val : values) {
-                       String currStr = val.getString();
-                       if (!"".equals(currStr.trim()))
-                               builder.append(currStr).append(separator);
-               }
-               if (builder.lastIndexOf(separator) >= 0)
-                       return builder.substring(0, builder.length() - separator.length());
-               else
-                       return builder.toString();
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java
deleted file mode 100644 (file)
index ac09b2a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.cms.ui.util;
-
-public class SimpleImageManager extends DefaultImageManager {
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java
deleted file mode 100644 (file)
index 63e504b..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-public class SimpleStaticPage implements CmsUiProvider {
-       private String text;
-
-       @Override
-       public Control createUi(Composite parent, Node context)
-                       throws RepositoryException {
-               Label textC = new Label(parent,  SWT.WRAP);
-               textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT);
-               textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
-               textC.setText(text);
-               
-               return textC;
-       }
-
-       public void setText(String text) {
-               this.text = text;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java
deleted file mode 100644 (file)
index 8ed06a2..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.api.cms.CmsStyle;
-
-/** Simple styles used by the CMS UI utilities. */
-public enum SimpleStyle implements CmsStyle {
-       link;
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java
deleted file mode 100644 (file)
index ad1523c..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} caching stylesheets. */
-public class StyleSheetResourceLoader implements ResourceLoader {
-       private Bundle themeBundle;
-       private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
-
-       public StyleSheetResourceLoader(Bundle themeBundle) {
-               this.themeBundle = themeBundle;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               if (!stylesheets.containsKey(resourceName)) {
-                       // TODO deal with other bundles
-                       // Bundle bundle = bundleContext.getBundle();
-                       // String location =
-                       // bundle.getLocation().substring("initial@reference:".length());
-                       // if (location.startsWith("file:")) {
-                       // Path path = null;
-                       // try {
-                       // path = Paths.get(new URI(location));
-                       // } catch (URISyntaxException e) {
-                       // e.printStackTrace();
-                       // }
-                       // if (path != null) {
-                       // Path resourcePath = path.resolve(resourceName);
-                       // if (Files.exists(resourcePath))
-                       // return Files.newInputStream(resourcePath);
-                       // }
-                       // }
-
-                       URL res = themeBundle.getEntry(resourceName);
-                       if (res == null)
-                               throw new CmsException(
-                                               "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
-                       ByteArrayOutputStream out = new ByteArrayOutputStream();
-                       IOUtils.copy(res.openStream(), out);
-                       stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
-               }
-               return new ByteArrayInputStream(stylesheets.get(resourceName).getData());
-               // return res.openStream();
-       }
-
-       private class StyleSheet {
-               private byte[] data;
-
-               public StyleSheet(byte[] data) {
-                       super();
-                       this.data = data;
-               }
-
-               public byte[] getData() {
-                       return data;
-               }
-
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java
deleted file mode 100644 (file)
index 156a608..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Shell;
-
-/** Shell displaying system notifications such as exceptions */
-public class SystemNotifications extends Shell implements CmsStyles,
-               MouseListener {
-       private static final long serialVersionUID = -8129377525216022683L;
-
-       private Control source;
-
-       public SystemNotifications(Control source) {
-               super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-               setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
-
-               this.source = source;
-
-               // TODO UI
-               // setLocation(source.toDisplay(source.getSize().x - getSize().x,
-               // source.getSize().y));
-               setLayout(new GridLayout());
-               addMouseListener(this);
-
-               addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = 5178980294808435833L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               close();
-                               dispose();
-                       }
-               });
-
-       }
-
-       public void notifyException(Throwable exception) {
-               Composite pane = this;
-
-               Label lbl = new Label(pane, SWT.NONE);
-               lbl.setText(exception.getLocalizedMessage()
-                               + (exception instanceof CmsException ? "" : "("
-                                               + exception.getClass().getName() + ")") + "\n");
-               lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               lbl.addMouseListener(this);
-               if (exception.getCause() != null)
-                       appendCause(pane, exception.getCause());
-
-               StringBuilder mailToUrl = new StringBuilder("mailto:?");
-               try {
-                       mailToUrl.append("subject=").append(
-                                       URLEncoder.encode(
-                                                       "Exception "
-                                                                       + new SimpleDateFormat("yyyy-MM-dd hh:mm")
-                                                                                       .format(new Date()), "UTF-8")
-                                                       .replace("+", "%20"));
-
-                       StringWriter sw = new StringWriter();
-                       exception.printStackTrace(new PrintWriter(sw));
-                       IOUtils.closeQuietly(sw);
-
-                       // see
-                       // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character
-                       String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace(
-                                       "+", "%20");
-                       mailToUrl.append("&amp;body=").append(encoded);
-               } catch (UnsupportedEncodingException e) {
-                       mailToUrl.append("&amp;body=").append("Could not encode: ")
-                                       .append(e.getMessage());
-               }
-               Label mailTo = new Label(pane, SWT.NONE);
-               CmsSwtUtils.markup(mailTo);
-               mailTo.setText("<a href=\"" + mailToUrl + "\">Send details</a>");
-               mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
-
-               pack();
-               layout();
-
-               setLocation(source.toDisplay(source.getSize().x - getSize().x,
-                               source.getSize().y - getSize().y));
-               open();
-       }
-
-       private void appendCause(Composite parent, Throwable e) {
-               Label lbl = new Label(parent, SWT.NONE);
-               lbl.setText(" caused by: " + e.getLocalizedMessage() + " ("
-                               + e.getClass().getName() + ")" + "\n");
-               lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               lbl.addMouseListener(this);
-               if (e.getCause() != null)
-                       appendCause(parent, e.getCause());
-       }
-
-       @Override
-       public void mouseDoubleClick(MouseEvent e) {
-       }
-
-       @Override
-       public void mouseDown(MouseEvent e) {
-               close();
-               dispose();
-       }
-
-       @Override
-       public void mouseUp(MouseEvent e) {
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java
deleted file mode 100644 (file)
index 008ec2c..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** The site-related user menu */
-public class UserMenu extends CmsLoginShell {
-       private final Control source;
-       private final Node context;
-
-       public UserMenu(Control source, Node context) {
-               // FIXME pass CMS context
-               super(CmsUiUtils.getCmsView(), null);
-               this.context = context;
-               createUi();
-               if (source == null)
-                       throw new CmsException("Source control cannot be null.");
-               this.source = source;
-               open();
-       }
-
-       @Override
-       protected Shell createShell() {
-               return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-       }
-
-       @Override
-       public void open() {
-               Shell shell = getShell();
-               shell.pack();
-               shell.layout();
-               shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
-               shell.addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = 5178980294808435833L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               closeShell();
-                       }
-               });
-               super.open();
-       }
-
-       protected Node getContext() {
-               return context;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java
deleted file mode 100644 (file)
index 317a7b5..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import javax.jcr.Node;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.swt.events.DisposeEvent;
-import org.eclipse.swt.events.DisposeListener;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** Open the user menu when clicked */
-public class UserMenuLink extends MenuLink {
-
-       public UserMenuLink() {
-               setCustom(CmsStyles.CMS_USER_MENU_LINK);
-       }
-
-       @Override
-       public Control createUi(Composite parent, Node context) {
-               if (CurrentUser.isAnonymous())
-                       setLabel(CmsMsg.login.lead());
-               else {
-                       setLabel(CurrentUser.getDisplayName());
-               }
-               Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0];
-               link.addMouseListener(new UserMenuLinkController(context));
-               return link.getParent();
-       }
-
-       protected CmsLoginShell createUserMenu(Control source, Node context) {
-               return new UserMenu(source.getParent(), context);
-       }
-
-       private class UserMenuLinkController implements MouseListener, DisposeListener {
-               private static final long serialVersionUID = 3634864186295639792L;
-
-               private CmsLoginShell userMenu = null;
-               private long lastDisposeTS = 0l;
-
-               private final Node context;
-
-               public UserMenuLinkController(Node context) {
-                       this.context = context;
-               }
-
-               //
-               // MOUSE LISTENER
-               //
-               @Override
-               public void mouseDown(MouseEvent e) {
-                       if (e.button == 1) {
-                               Control source = (Control) e.getSource();
-                               if (userMenu == null) {
-                                       long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS;
-                                       // avoid to reopen the menu, if one has clicked gain
-                                       if (durationSinceLastDispose > 200) {
-                                               userMenu = createUserMenu(source, context);
-                                               userMenu.getShell().addDisposeListener(this);
-                                       }
-                               }
-                       }
-               }
-
-               @Override
-               public void mouseDoubleClick(MouseEvent e) {
-               }
-
-               @Override
-               public void mouseUp(MouseEvent e) {
-               }
-
-               @Override
-               public void widgetDisposed(DisposeEvent event) {
-                       userMenu = null;
-                       lastDisposeTS = System.currentTimeMillis();
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java
deleted file mode 100644 (file)
index 7f846c9..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-public class VerticalMenu implements CmsUiProvider {
-       private List<CmsUiProvider> items = new ArrayList<CmsUiProvider>();
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               Composite part = new Composite(parent, SWT.NONE);
-               part.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
-//             part.setData(RWT.CUSTOM_VARIANT, custom);
-               part.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               for (CmsUiProvider uiProvider : items) {
-                       Control subPart = uiProvider.createUi(part, context);
-                       subPart.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
-               }
-               return part;
-       }
-
-       public void add(CmsUiProvider uiProvider) {
-               items.add(uiProvider);
-       }
-
-       public List<CmsUiProvider> getItems() {
-               return items;
-       }
-
-       public void setItems(List<CmsUiProvider> items) {
-               this.items = items;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java
deleted file mode 100644 (file)
index 566df88..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS UI utilities. */
-package org.argeo.cms.ui.util;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java
deleted file mode 100644 (file)
index ef24ee0..0000000
+++ /dev/null
@@ -1,350 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.Observable;
-import java.util.Observer;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ui.widgets.ScrolledPage;
-import org.argeo.jcr.JcrException;
-import org.eclipse.jface.viewers.ContentViewer;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Widget;
-import org.xml.sax.SAXParseException;
-
-/** Base class for viewers related to a page */
-public abstract class AbstractPageViewer extends ContentViewer implements Observer {
-       private static final long serialVersionUID = 5438688173410341485L;
-
-       private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class);
-
-       private final boolean readOnly;
-       /** The basis for the layouts, typically a ScrolledPage. */
-       private final Composite page;
-       private final CmsEditable cmsEditable;
-
-       private MouseListener mouseListener;
-       private FocusListener focusListener;
-
-       private EditablePart edited;
-       private ISelection selection = StructuredSelection.EMPTY;
-
-       private AccessControlContext accessControlContext;
-
-       protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) {
-               // read only at UI level
-               readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
-
-               this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
-               if (this.cmsEditable instanceof Observable)
-                       ((Observable) this.cmsEditable).addObserver(this);
-
-               if (cmsEditable.canEdit()) {
-                       mouseListener = createMouseListener();
-                       focusListener = createFocusListener();
-               }
-               page = findPage(parent);
-               accessControlContext = AccessController.getContext();
-       }
-
-       /**
-        * Can be called to simplify the called to isModelInitialized() and initModel()
-        */
-       protected void initModelIfNeeded(Node node) {
-               try {
-                       if (!isModelInitialized(node))
-                               if (getCmsEditable().canEdit()) {
-                                       initModel(node);
-                                       node.getSession().save();
-                               }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot initialize model", e);
-               }
-       }
-
-       /** Called if user can edit and model is not initialized */
-       protected Boolean isModelInitialized(Node node) throws RepositoryException {
-               return true;
-       }
-
-       /** Called if user can edit and model is not initialized */
-       protected void initModel(Node node) throws RepositoryException {
-       }
-
-       /** Create (retrieve) the MouseListener to use. */
-       protected MouseListener createMouseListener() {
-               return new MouseAdapter() {
-                       private static final long serialVersionUID = 1L;
-               };
-       }
-
-       /** Create (retrieve) the FocusListener to use. */
-       protected FocusListener createFocusListener() {
-               return new FocusListener() {
-                       private static final long serialVersionUID = 1L;
-
-                       @Override
-                       public void focusLost(FocusEvent event) {
-                       }
-
-                       @Override
-                       public void focusGained(FocusEvent event) {
-                       }
-               };
-       }
-
-       protected Composite findPage(Composite composite) {
-               if (composite instanceof ScrolledPage) {
-                       return (ScrolledPage) composite;
-               } else {
-                       if (composite.getParent() == null)
-                               return composite;
-                       return findPage(composite.getParent());
-               }
-       }
-
-       public void layoutPage() {
-               if (page != null)
-                       page.layout(true, true);
-       }
-
-       protected void showControl(Control control) {
-               if (page != null && (page instanceof ScrolledPage))
-                       ((ScrolledPage) page).showControl(control);
-       }
-
-       @Override
-       public void update(Observable o, Object arg) {
-               if (o == cmsEditable)
-                       editingStateChanged(cmsEditable);
-       }
-
-       /** To be overridden in order to provide the actual refresh */
-       protected void refresh(Control control) throws RepositoryException {
-       }
-
-       /** To be overridden.Save the edited part. */
-       protected void save(EditablePart part) throws RepositoryException {
-       }
-
-       /** Prepare the edited part */
-       protected void prepare(EditablePart part, Object caretPosition) {
-       }
-
-       /** Notified when the editing state changed. Does nothing, to be overridden */
-       protected void editingStateChanged(CmsEditable cmsEditable) {
-       }
-
-       @Override
-       public void refresh() {
-               // TODO check actual context in order to notice a discrepancy
-               Subject viewerSubject = getViewerSubject();
-               Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
-                       try {
-                               if (cmsEditable.canEdit() && !readOnly)
-                                       mouseListener = createMouseListener();
-                               else
-                                       mouseListener = null;
-                               refresh(getControl());
-                               // layout(getControl());
-                               if (!getControl().isDisposed())
-                                       layoutPage();
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot refresh", e);
-                       }
-                       return null;
-               });
-       }
-
-       @Override
-       public void setSelection(ISelection selection, boolean reveal) {
-               this.selection = selection;
-       }
-
-       protected void updateContent(EditablePart part) throws RepositoryException {
-       }
-
-       // LOW LEVEL EDITION
-       protected void edit(EditablePart part, Object caretPosition) {
-               try {
-                       if (edited == part)
-                               return;
-
-                       if (edited != null && edited != part) {
-                               EditablePart previouslyEdited = edited;
-                               try {
-                                       stopEditing(true);
-                               } catch (Exception e) {
-                                       notifyEditionException(e);
-                                       edit(previouslyEdited, caretPosition);
-                                       return;
-                               }
-                       }
-
-                       part.startEditing();
-                       edited = part;
-                       updateContent(part);
-                       prepare(part, caretPosition);
-                       edited.getControl().addFocusListener(new FocusListener() {
-                               private static final long serialVersionUID = 6883521812717097017L;
-
-                               @Override
-                               public void focusLost(FocusEvent event) {
-                                       stopEditing(true);
-                               }
-
-                               @Override
-                               public void focusGained(FocusEvent event) {
-                               }
-                       });
-
-                       layout(part.getControl());
-                       showControl(part.getControl());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot edit " + part, e);
-               }
-       }
-
-       protected void stopEditing(Boolean save) {
-               if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
-                       edited = null;
-                       return;
-               }
-
-               assert edited != null;
-               if (edited == null) {
-                       if (log.isTraceEnabled())
-                               log.warn("Told to stop editing while not editing anything");
-                       return;
-               }
-
-               try {
-                       if (save)
-                               save(edited);
-
-                       edited.stopEditing();
-                       EditablePart editablePart = edited;
-                       Control control = ((EditablePart) edited).getControl();
-                       edited = null;
-                       // TODO make edited state management more robust
-                       updateContent(editablePart);
-                       layout(control);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot stop editing", e);
-               } finally {
-                       edited = null;
-               }
-       }
-
-       // METHODS AVAILABLE TO EXTENDING CLASSES
-       protected void saveEdit() {
-               if (edited != null)
-                       stopEditing(true);
-       }
-
-       protected void cancelEdit() {
-               if (edited != null)
-                       stopEditing(false);
-       }
-
-       /** Layout this controls from the related base page. */
-       public void layout(Control... controls) {
-               page.layout(controls);
-       }
-
-       /**
-        * Find the first {@link EditablePart} in the parents hierarchy of this control
-        */
-       protected EditablePart findDataParent(Control parent) {
-               if (parent instanceof EditablePart) {
-                       return (EditablePart) parent;
-               }
-               if (parent.getParent() != null)
-                       return findDataParent(parent.getParent());
-               else
-                       throw new IllegalStateException("No data parent found");
-       }
-
-       // UTILITIES
-       /** Check whether the edited part is in a proper state */
-       protected void checkEdited() {
-               if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
-                       throw new IllegalStateException("Edited should not be null or disposed at this stage");
-       }
-
-       /** Persist all changes. */
-       protected void persistChanges(Session session) throws RepositoryException {
-               session.save();
-               session.refresh(false);
-               // TODO notify that changes have been persisted
-       }
-
-       /** Convenience method using a Node in order to save the underlying session. */
-       protected void persistChanges(Node anyNode) throws RepositoryException {
-               persistChanges(anyNode.getSession());
-       }
-
-       /** Notify edition exception */
-       protected void notifyEditionException(Throwable e) {
-               Throwable eToLog = e;
-               if (e instanceof IllegalArgumentException)
-                       if (e.getCause() instanceof SAXParseException)
-                               eToLog = e.getCause();
-               log.error(eToLog.getMessage(), eToLog);
-//             if (log.isTraceEnabled())
-//                     log.trace("Full stack of " + eToLog.getMessage(), e);
-               // TODO Light error notification popup
-       }
-
-       protected Subject getViewerSubject() {
-               Subject res = null;
-               if (accessControlContext != null) {
-                       res = Subject.getSubject(accessControlContext);
-               }
-               if (res == null)
-                       throw new IllegalStateException("No subject associated with this viewer");
-               return res;
-       }
-
-       // GETTERS / SETTERS
-       public boolean isReadOnly() {
-               return readOnly;
-       }
-
-       protected EditablePart getEdited() {
-               return edited;
-       }
-
-       public MouseListener getMouseListener() {
-               return mouseListener;
-       }
-
-       public FocusListener getFocusListener() {
-               return focusListener;
-       }
-
-       public CmsEditable getCmsEditable() {
-               return cmsEditable;
-       }
-
-       @Override
-       public ISelection getSelection() {
-               return selection;
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java
deleted file mode 100644 (file)
index 3967c97..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import org.eclipse.swt.widgets.Control;
-
-/** Manages whether an editable or non editable control is shown. */
-public interface EditablePart {
-       public void startEditing();
-
-       public void stopEditing();
-
-       public Control getControl();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java
deleted file mode 100644 (file)
index 4ca45d1..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-/** An editable part related to a JCR Item */
-public interface ItemPart<T extends Item> {
-       public Item getItem() throws RepositoryException;
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java
deleted file mode 100644 (file)
index 11162e8..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import java.util.Observable;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.api.cms.CmsEditable;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsEditionEvent;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Event;
-import org.eclipse.swt.widgets.Listener;
-
-/** Provides the CmsEditable semantic based on JCR versioning. */
-public class JcrVersionCmsEditable extends Observable implements CmsEditable {
-       private final String nodePath;// cache
-       private final VersionManager versionManager;
-       private final Boolean canEdit;
-
-       public JcrVersionCmsEditable(Node node) throws RepositoryException {
-               this.nodePath = node.getPath();
-               if (node.getSession().hasPermission(node.getPath(),
-                               Session.ACTION_SET_PROPERTY)) {
-                       // was Session.ACTION_ADD_NODE
-                       canEdit = true;
-                       if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) {
-                               node.addMixin(NodeType.MIX_VERSIONABLE);
-                               node.getSession().save();
-                       }
-                       versionManager = node.getSession().getWorkspace()
-                                       .getVersionManager();
-               } else {
-                       canEdit = false;
-                       versionManager = null;
-               }
-
-               // bind keys
-               if (canEdit) {
-                       Display display = Display.getCurrent();
-                       display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN",
-                                       "CTRL+E" });
-                       display.addFilter(SWT.KeyDown, new Listener() {
-                               private static final long serialVersionUID = -4378653870463187318L;
-
-                               public void handleEvent(Event e) {
-                                       boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0;
-                                       if (ctrlPressed && e.keyCode == '\r')
-                                               stopEditing();
-                                       else if (ctrlPressed && e.keyCode == 'E')
-                                               stopEditing();
-                               }
-                       });
-               }
-       }
-
-       @Override
-       public Boolean canEdit() {
-               return canEdit;
-       }
-
-       public Boolean isEditing() {
-               try {
-                       if (!canEdit())
-                               return false;
-                       return versionManager.isCheckedOut(nodePath);
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot check whether " + nodePath
-                                       + " is editing", e);
-               }
-       }
-
-       @Override
-       public void startEditing() {
-               try {
-                       versionManager.checkout(nodePath);
-                       setChanged();
-               } catch (RepositoryException e1) {
-                       throw new CmsException("Cannot publish " + nodePath);
-               }
-               notifyObservers(new CmsEditionEvent(nodePath,
-                               CmsEditionEvent.START_EDITING));
-       }
-
-       @Override
-       public void stopEditing() {
-               try {
-                       versionManager.checkin(nodePath);
-                       setChanged();
-               } catch (RepositoryException e1) {
-                       throw new CmsException("Cannot publish " + nodePath, e1);
-               }
-               notifyObservers(new CmsEditionEvent(nodePath,
-                               CmsEditionEvent.STOP_EDITING));
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java
deleted file mode 100644 (file)
index b51d4fc..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Node;
-
-/** An editable part related to a node */
-public interface NodePart extends ItemPart<Node> {
-       public Node getNode();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java
deleted file mode 100644 (file)
index 793079e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import javax.jcr.Property;
-
-/** An editable part related to a JCR Property */
-public interface PropertyPart extends ItemPart<Property> {
-       public Property getProperty();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java
deleted file mode 100644 (file)
index d282eeb..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.widgets.JcrComposite;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** A structured UI related to a JCR context. */
-public class Section extends JcrComposite {
-       private static final long serialVersionUID = -5933796173755739207L;
-
-       private final Section parentSection;
-       private Composite sectionHeader;
-       private final Integer relativeDepth;
-
-       public Section(Composite parent, int style, Node node) {
-               this(parent, findSection(parent), style, node);
-       }
-
-       public Section(Section section, int style, Node node) {
-               this(section, section, style, node);
-       }
-
-       protected Section(Composite parent, Section parentSection, int style, Node node) {
-               super(parent, style, node);
-               try {
-                       this.parentSection = parentSection;
-                       if (parentSection != null) {
-                               relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth();
-                       } else {
-                               relativeDepth = 0;
-                       }
-                       setLayout(CmsSwtUtils.noSpaceGridLayout());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot create section from " + node, e);
-               }
-       }
-
-       public Map<String, Section> getSubSections() throws RepositoryException {
-               LinkedHashMap<String, Section> result = new LinkedHashMap<String, Section>();
-               for (Control child : getChildren()) {
-                       if (child instanceof Composite) {
-                               collectDirectSubSections((Composite) child, result);
-                       }
-               }
-               return Collections.unmodifiableMap(result);
-       }
-
-       private void collectDirectSubSections(Composite composite, LinkedHashMap<String, Section> subSections)
-                       throws RepositoryException {
-               if (composite == sectionHeader || composite instanceof EditablePart)
-                       return;
-               if (composite instanceof Section) {
-                       Section section = (Section) composite;
-                       subSections.put(section.getNodeId(), section);
-                       return;
-               }
-
-               for (Control child : composite.getChildren())
-                       if (child instanceof Composite)
-                               collectDirectSubSections((Composite) child, subSections);
-       }
-
-       public Composite createHeader() {
-               return createHeader(this);
-       }
-
-       public Composite createHeader(Composite parent) {
-               if (sectionHeader != null)
-                       sectionHeader.dispose();
-
-               sectionHeader = new Composite(parent, SWT.NONE);
-               sectionHeader.setLayoutData(CmsSwtUtils.fillWidth());
-               sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               // sectionHeader.moveAbove(null);
-               // layout();
-               return sectionHeader;
-       }
-
-       public Composite getHeader() {
-               if (sectionHeader != null && sectionHeader.isDisposed())
-                       sectionHeader = null;
-               return sectionHeader;
-       }
-
-       // SECTION PARTS
-       public SectionPart getSectionPart(String partId) {
-               for (Control child : getChildren()) {
-                       if (child instanceof SectionPart) {
-                               SectionPart sectionPart = (SectionPart) child;
-                               if (sectionPart.getPartId().equals(partId))
-                                       return sectionPart;
-                       }
-               }
-               return null;
-       }
-
-       public SectionPart nextSectionPart(SectionPart sectionPart) {
-               Control[] children = getChildren();
-               for (int i = 0; i < children.length; i++) {
-                       if (sectionPart == children[i]) {
-                               for (int j = i + 1; j < children.length; j++) {
-                                       if (children[i + 1] instanceof SectionPart) {
-                                               return (SectionPart) children[i + 1];
-                                       }
-                               }
-
-//                             if (i + 1 < children.length) {
-//                                     Composite next = (Composite) children[i + 1];
-//                                     return (SectionPart) next;
-//                             } else {
-//                                     // next section
-//                             }
-                       }
-               }
-               return null;
-       }
-
-       public SectionPart previousSectionPart(SectionPart sectionPart) {
-               Control[] children = getChildren();
-               for (int i = 0; i < children.length; i++) {
-                       if (sectionPart == children[i])
-                               if (i != 0) {
-                                       Composite previous = (Composite) children[i - 1];
-                                       return (SectionPart) previous;
-                               } else {
-                                       // previous section
-                               }
-               }
-               return null;
-       }
-
-       @Override
-       public String toString() {
-               if (parentSection == null)
-                       return "Main section " + getNode();
-               return "Section " + getNode();
-       }
-
-       public Section getParentSection() {
-               return parentSection;
-       }
-
-       public Integer getRelativeDepth() {
-               return relativeDepth;
-       }
-
-       /** Recursively finds the related section in the parents (can be itself) */
-       public static Section findSection(Control control) {
-               if (control == null)
-                       return null;
-               if (control instanceof Section)
-                       return (Section) control;
-               else
-                       return findSection(control.getParent());
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java
deleted file mode 100644 (file)
index f0b367f..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.cms.ui.viewers;
-
-
-/** An editable part dynamically related to a Section */
-public interface SectionPart extends EditablePart, NodePart {
-       public String getPartId();
-
-       public Section getSection();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java
deleted file mode 100644 (file)
index 2f07931..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS generic viewers, based on JFace. */
-package org.argeo.cms.ui.viewers;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java
deleted file mode 100644 (file)
index 7bc0f79..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-
-/**
- * Manages a lightweight shell which is related to a {@link Control}, typically
- * in order to reproduce a dropdown semantic, but with more flexibility.
- */
-public class ContextOverlay extends ScrolledPage {
-       private static final long serialVersionUID = 6702077429573324009L;
-
-//     private Shell shell;
-       private Control control;
-
-       private int maxHeight = 400;
-
-       public ContextOverlay(Control control, int style) {
-               super(createShell(control, style), SWT.NONE);
-               Shell shell = getShell();
-               setLayoutData(CmsSwtUtils.fillAll());
-               // TODO make autohide configurable?
-               //shell.addShellListener(new AutoHideShellListener());
-               this.control = control;
-               control.addDisposeListener((e) -> {
-                       dispose();
-                       shell.dispose();
-               });
-       }
-
-       private static Composite createShell(Control control, int style) {
-               if (control == null)
-                       throw new IllegalArgumentException("Control cannot be null");
-               if (control.isDisposed())
-                       throw new IllegalArgumentException("Control is disposed");
-               Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);
-               shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite placeholder = new Composite(shell, SWT.BORDER);
-               placeholder.setLayoutData(CmsSwtUtils.fillAll());
-               placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               return placeholder;
-       }
-
-       public void show() {
-               Point relativeControlLocation = control.getLocation();
-               Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y);
-
-               int controlWidth = control.getBounds().width;
-
-               Shell shell = getShell();
-
-               layout(true, true);
-               shell.pack();
-               shell.layout(true, true);
-               int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x;
-               if (shell.getSize().y > maxHeight) {
-                       shell.setSize(targetShellWidth, maxHeight);
-               } else {
-                       shell.setSize(targetShellWidth, shell.getSize().y);
-               }
-
-               int shellHeight = shell.getSize().y;
-               int controlHeight = control.getBounds().height;
-               Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight);
-               int displayHeight = shell.getDisplay().getBounds().height;
-               if (shellLocation.y + shellHeight > displayHeight) {// bottom of page
-                       shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight);
-               }
-               shell.setLocation(shellLocation);
-
-               if (getChildren().length != 0)
-                       shell.open();
-               if (!control.isDisposed())
-                       control.setFocus();
-       }
-
-       public void hide() {
-               getShell().setVisible(false);
-               onHide();
-       }
-
-       public boolean isShellVisible() {
-               if (isDisposed())
-                       return false;
-               return getShell().isVisible();
-       }
-
-       /** to be overridden */
-       protected void onHide() {
-               // does nothing by default.
-       }
-
-       private class AutoHideShellListener extends ShellAdapter {
-               private static final long serialVersionUID = 7743287433907938099L;
-
-               @Override
-               public void shellDeactivated(ShellEvent e) {
-                       try {
-                               Thread.sleep(1000);
-                       } catch (InterruptedException e1) {
-                               // silent
-                       }
-                       if (!control.isDisposed() && !control.isFocusControl())
-                               hide();
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java
deleted file mode 100644 (file)
index c2393f2..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** A stylable and editable image. */
-public abstract class EditableImage extends StyledControl {
-       private static final long serialVersionUID = -5689145523114022890L;
-       private final static CmsLog log = CmsLog.getLog(EditableImage.class);
-
-       private Cms2DSize preferredImageSize;
-       private Boolean loaded = false;
-
-       public EditableImage(Composite parent, int swtStyle) {
-               super(parent, swtStyle);
-       }
-
-       public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) {
-               super(parent, swtStyle);
-               this.preferredImageSize = preferredImageSize;
-       }
-
-       public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize)
-                       throws RepositoryException {
-               super(parent, style, node, cacheImmediately);
-               this.preferredImageSize = preferredImageSize;
-       }
-
-       @Override
-       protected void setContainerLayoutData(Composite composite) {
-               // composite.setLayoutData(fillWidth());
-       }
-
-       @Override
-       protected void setControlLayoutData(Control control) {
-               // control.setLayoutData(fillWidth());
-       }
-
-       /** To be overriden. */
-       protected String createImgTag() throws RepositoryException {
-               return CmsUiUtils
-                               .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y));
-       }
-
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = new Label(box, getStyle());
-               // lbl.setLayoutData(CmsUiUtils.fillWidth());
-               CmsSwtUtils.markup(lbl);
-               CmsSwtUtils.style(lbl, style);
-               if (mouseListener != null)
-                       lbl.addMouseListener(mouseListener);
-               load(lbl);
-               return lbl;
-       }
-
-       /** To be overriden. */
-       protected synchronized Boolean load(Control control) {
-               String imgTag;
-               try {
-                       imgTag = createImgTag();
-               } catch (Exception e) {
-                       // throw new CmsException("Cannot retrieve image", e);
-                       log.error("Cannot retrieve image", e);
-                       imgTag = CmsUiUtils.noImg(preferredImageSize);
-                       loaded = false;
-               }
-
-               if (imgTag == null) {
-                       loaded = false;
-                       imgTag = CmsUiUtils.noImg(preferredImageSize);
-               } else
-                       loaded = true;
-               if (control != null) {
-                       ((Label) control).setText(imgTag);
-                       control.setSize(preferredImageSize != null
-                                       ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight())
-                                       : getSize());
-               } else {
-                       loaded = false;
-               }
-               getParent().layout();
-               return loaded;
-       }
-
-       public void setPreferredSize(Cms2DSize size) {
-               this.preferredImageSize = size;
-               if (!loaded) {
-                       load((Label) getControl());
-               }
-       }
-
-       protected Text createText(Composite box, String style) {
-               Text text = new Text(box, getStyle());
-               CmsSwtUtils.style(text, style);
-               return text;
-       }
-
-       public Cms2DSize getPreferredImageSize() {
-               return preferredImageSize;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java
deleted file mode 100644 (file)
index e3499ac..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Text;
-
-/** Editable text part displaying styled text. */
-public class EditableText extends StyledControl {
-       private static final long serialVersionUID = -6372283442330912755L;
-
-       private boolean editable = true;
-
-       private Color highlightColor;
-       private Composite highlight;
-
-       private boolean useTextAsLabel = false;
-
-       public EditableText(Composite parent, int style) {
-               super(parent, style);
-               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
-               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
-       }
-
-       public EditableText(Composite parent, int style, Item item) throws RepositoryException {
-               this(parent, style, item, false);
-       }
-
-       public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException {
-               super(parent, style, item, cacheImmediately);
-               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
-               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               if (isEditing() && getEditable()) {
-                       return createText(box, style, true);
-               } else {
-                       if (useTextAsLabel) {
-                               return createTextLabel(box, style);
-                       } else {
-                               return createLabel(box, style);
-                       }
-               }
-       }
-
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = new Label(box, getStyle() | SWT.WRAP);
-               lbl.setLayoutData(CmsSwtUtils.fillWidth());
-               if (style != null)
-                       CmsSwtUtils.style(lbl, style);
-               CmsSwtUtils.markup(lbl);
-               if (mouseListener != null)
-                       lbl.addMouseListener(mouseListener);
-               return lbl;
-       }
-
-       protected Text createTextLabel(Composite box, String style) {
-               Text lbl = new Text(box, getStyle() | SWT.MULTI);
-               lbl.setEditable(false);
-               lbl.setLayoutData(CmsSwtUtils.fillWidth());
-               if (style != null)
-                       CmsSwtUtils.style(lbl, style);
-               CmsSwtUtils.markup(lbl);
-               if (mouseListener != null)
-                       lbl.addMouseListener(mouseListener);
-               return lbl;
-       }
-
-       protected Text createText(Composite box, String style, boolean editable) {
-               highlight = new Composite(box, SWT.NONE);
-               highlight.setBackground(highlightColor);
-               GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
-               highlightGd.widthHint = 5;
-               highlightGd.heightHint = 3;
-               highlight.setLayoutData(highlightGd);
-
-               final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP);
-               text.setEditable(editable);
-               GridData textLayoutData = CmsSwtUtils.fillWidth();
-               // textLayoutData.heightHint = preferredHeight;
-               text.setLayoutData(textLayoutData);
-               if (style != null)
-                       CmsSwtUtils.style(text, style);
-               text.setFocus();
-               return text;
-       }
-
-       @Override
-       protected void clear(boolean deep) {
-               if (highlight != null)
-                       highlight.dispose();
-               super.clear(deep);
-       }
-
-       public void setText(String text) {
-               Control child = getControl();
-               if (child instanceof Label)
-                       ((Label) child).setText(text);
-               else if (child instanceof Text)
-                       ((Text) child).setText(text);
-       }
-
-       public Text getAsText() {
-               return (Text) getControl();
-       }
-
-       public Label getAsLabel() {
-               return (Label) getControl();
-       }
-
-       public String getText() {
-               Control child = getControl();
-
-               if (child instanceof Label)
-                       return ((Label) child).getText();
-               else if (child instanceof Text)
-                       return ((Text) child).getText();
-               else
-                       throw new IllegalStateException("Unsupported control " + child.getClass());
-       }
-
-       /** @deprecated Use {@link #isEditable()} instead. */
-       @Deprecated
-       public boolean getEditable() {
-               return isEditable();
-       }
-
-       public boolean isEditable() {
-               return editable;
-       }
-
-       public void setUseTextAsLabel(boolean useTextAsLabel) {
-               this.useTextAsLabel = useTextAsLabel;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java
deleted file mode 100644 (file)
index 3a4a60c..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.Cms2DSize;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.internal.JcrFileUploadReceiver;
-import org.argeo.cms.ui.viewers.NodePart;
-import org.argeo.cms.ui.viewers.Section;
-import org.argeo.cms.ui.viewers.SectionPart;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadListener;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.service.ServerPushSession;
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** An image within the Argeo Text framework */
-public class Img extends EditableImage implements SectionPart, NodePart {
-       private static final long serialVersionUID = 6233572783968188476L;
-
-       private final Section section;
-
-       private final CmsImageManager<Control, Node> imageManager;
-       private FileUploadHandler currentUploadHandler = null;
-       private FileUploadListener fileUploadListener;
-
-       public Img(Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize) throws RepositoryException {
-               this(Section.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
-               setStyle(TextStyles.TEXT_IMAGE);
-       }
-
-       public Img(Composite parent, int swtStyle, Node imgNode) throws RepositoryException {
-               this(Section.findSection(parent), parent, swtStyle, imgNode, null, null);
-               setStyle(TextStyles.TEXT_IMAGE);
-       }
-
-       public Img(Composite parent, int swtStyle, Node imgNode, CmsImageManager<Control, Node> imageManager)
-                       throws RepositoryException {
-               this(Section.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
-               setStyle(TextStyles.TEXT_IMAGE);
-       }
-
-       Img(Section section, Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize,
-                       CmsImageManager<Control, Node> imageManager) throws RepositoryException {
-               super(parent, swtStyle, imgNode, false, preferredImageSize);
-               this.section = section;
-               this.imageManager = imageManager != null ? imageManager
-                               : (CmsImageManager<Control, Node>) CmsSwtUtils.getCmsView(section).getImageManager();
-               CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
-       }
-
-       @Override
-       protected Control createControl(Composite box, String style) {
-               if (isEditing()) {
-                       try {
-                               return createImageChooser(box, style);
-                       } catch (RepositoryException e) {
-                               throw new JcrException("Cannot create image chooser", e);
-                       }
-               } else {
-                       return createLabel(box, style);
-               }
-       }
-
-       @Override
-       public synchronized void stopEditing() {
-               super.stopEditing();
-               fileUploadListener = null;
-       }
-
-       @Override
-       protected synchronized Boolean load(Control lbl) {
-               Node imgNode = getNode();
-               boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
-               // getParent().layout();
-               return loaded;
-       }
-
-       protected Node getUploadFolder() {
-               return Jcr.getParent(getNode());
-       }
-
-       protected String getUploadName() {
-               Node node = getNode();
-               return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']';
-       }
-
-       protected CmsImageManager<Control, Node> getImageManager() {
-               return imageManager;
-       }
-
-       protected Control createImageChooser(Composite box, String style) throws RepositoryException {
-               JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
-                               imageManager);
-               if (currentUploadHandler != null)
-                       currentUploadHandler.dispose();
-               currentUploadHandler = prepareUpload(receiver);
-               final ServerPushSession pushSession = new ServerPushSession();
-               final FileUpload fileUpload = new FileUpload(box, SWT.NONE);
-               CmsSwtUtils.style(fileUpload, style);
-               fileUpload.addSelectionListener(new SelectionAdapter() {
-                       private static final long serialVersionUID = -9158471843941668562L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               pushSession.start();
-                               fileUpload.submit(currentUploadHandler.getUploadUrl());
-                       }
-               });
-               return fileUpload;
-       }
-
-       protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
-               final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
-               if (fileUploadListener != null)
-                       uploadHandler.addUploadListener(fileUploadListener);
-               return uploadHandler;
-       }
-
-       @Override
-       public Section getSection() {
-               return section;
-       }
-
-       public void setFileUploadListener(FileUploadListener fileUploadListener) {
-               this.fileUploadListener = fileUploadListener;
-               if (currentUploadHandler != null)
-                       currentUploadHandler.addUploadListener(fileUploadListener);
-       }
-
-       @Override
-       public Node getItem() throws RepositoryException {
-               return getNode();
-       }
-
-       @Override
-       public String getPartId() {
-               return getNodeId();
-       }
-
-       @Override
-       public String toString() {
-               return "Img #" + getPartId();
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java
deleted file mode 100644 (file)
index 5d3576f..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.jcr.JcrException;
-import org.eclipse.swt.widgets.Composite;
-
-/** A composite which can (optionally) manage a JCR Item. */
-public class JcrComposite extends Composite {
-       private static final long serialVersionUID = -1447009015451153367L;
-
-       private Session session;
-
-       private String nodeId;
-       private String property = null;
-       private Node cache;
-
-       /** Regular composite constructor. No layout is set. */
-       public JcrComposite(Composite parent, int style) {
-               super(parent, style);
-               session = null;
-               nodeId = null;
-       }
-
-       public JcrComposite(Composite parent, int style, Item item) {
-               this(parent, style, item, false);
-       }
-
-       public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) {
-               super(parent, style);
-               if (item != null)
-                       try {
-                               this.session = item.getSession();
-//                             if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) {
-//                                     // (useless?) optimization: we only save a pointer to the session,
-//                                     // not even a reference to the item
-//                                     this.nodeId = null;
-//                             } else {
-                               Node node;
-                               Property property = null;
-                               if (item instanceof Node) {
-                                       node = (Node) item;
-                               } else {// Property
-                                       property = (Property) item;
-                                       if (property.isMultiple())// TODO manage property index
-                                               throw new UnsupportedOperationException("Multiple properties not supported yet.");
-                                       this.property = property.getName();
-                                       node = property.getParent();
-                               }
-                               this.nodeId = node.getIdentifier();
-                               if (cacheImmediately)
-                                       this.cache = node;
-//                             }
-                               setLayout(CmsSwtUtils.noSpaceGridLayout());
-                       } catch (RepositoryException e) {
-                               throw new IllegalStateException("Cannot create composite from " + item, e);
-                       }
-       }
-
-       public synchronized Node getNode() {
-               try {
-                       if (!itemIsNode())
-                               throw new IllegalStateException("Item is not a Node");
-                       return getNodeInternal();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node " + nodeId, e);
-               }
-       }
-
-       private synchronized Node getNodeInternal() throws RepositoryException {
-               if (cache != null)
-                       return cache;
-               else if (session != null)
-                       if (nodeId != null)
-                               return session.getNodeByIdentifier(nodeId);
-                       else
-                               return null;
-               else
-                       return null;
-       }
-
-       public synchronized String getPropertyName() {
-               try {
-                       return getProperty().getName();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property name", e);
-               }
-       }
-
-       public synchronized Node getPropertyNode() {
-               try {
-                       return getProperty().getNode();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property name", e);
-               }
-       }
-
-       public synchronized Property getProperty() {
-               try {
-                       if (itemIsNode())
-                               throw new IllegalStateException("Item is not a Property");
-                       Node node = getNodeInternal();
-                       if (!node.hasProperty(property))
-                               throw new IllegalStateException("Property " + property + " is not set on " + node);
-                       return node.getProperty(property);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + property + " from node " + nodeId, e);
-               }
-       }
-
-       public synchronized boolean itemIsNode() {
-               return property == null;
-       }
-
-       public synchronized boolean itemExists() {
-               if (session == null)
-                       return false;
-               try {
-                       Node n = session.getNodeByIdentifier(nodeId);
-                       if (!itemIsNode())
-                               return n.hasProperty(property);
-                       else
-                               return true;
-               } catch (ItemNotFoundException e) {
-                       return false;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check whether node exists", e);
-               }
-       }
-
-       /** Set/update the cache or change the node */
-       public synchronized void setNode(Node node) {
-               if (!itemIsNode())
-                       throw new IllegalArgumentException("Cannot set a Node on a Property");
-
-               if (node == null) {// clear cache
-                       this.cache = null;
-                       return;
-               }
-
-               try {
-//                     if (session != null || session != node.getSession())// check session
-//                             throw new IllegalArgumentException("Uncompatible session");
-//                     if (session == null)
-                       session = node.getSession();
-                       if (nodeId == null || !nodeId.equals(node.getIdentifier())) {
-                               nodeId = node.getIdentifier();
-                               cache = node;
-                               itemUpdated();
-                       } else {
-                               cache = node;// set/update cache
-                       }
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       /** Set/update the cache or change the property */
-       public synchronized void setProperty(Property prop) {
-               if (itemIsNode())
-                       throw new IllegalArgumentException("Cannot set a Property on a Node");
-
-               if (prop == null) {// clear cache
-                       this.cache = null;
-                       return;
-               }
-
-               try {
-                       if (session == null || session != prop.getSession())// check session
-                               throw new IllegalArgumentException("Uncompatible session");
-
-                       Node node = prop.getNode();
-                       if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) {
-                               nodeId = node.getIdentifier();
-                               property = prop.getName();
-                               cache = node;
-                               itemUpdated();
-                       } else {
-                               cache = node;// set/update cache
-                       }
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public synchronized String getNodeId() {
-               return nodeId;
-       }
-
-       /** Change the node, does nothing if same. */
-       public synchronized void setNodeId(String nodeId) throws RepositoryException {
-               if (this.nodeId != null && this.nodeId.equals(nodeId))
-                       return;
-               this.nodeId = nodeId;
-               if (cache != null)
-                       cache = session.getNodeByIdentifier(this.nodeId);
-               itemUpdated();
-       }
-
-       protected synchronized void itemUpdated() {
-               layout();
-       }
-
-       public Session getSession() {
-               return session;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java
deleted file mode 100644 (file)
index 517e796..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.ScrolledComposite;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/**
- * A composite that can be scrolled vertically. It wraps a
- * {@link ScrolledComposite} (and is being wrapped by it), simplifying its
- * configuration.
- */
-public class ScrolledPage extends Composite {
-       private static final long serialVersionUID = 1593536965663574437L;
-
-       private ScrolledComposite scrolledComposite;
-
-       public ScrolledPage(Composite parent, int style) {
-               this(parent, style, false);
-       }
-
-       public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) {
-               super(createScrolledComposite(parent, alwaysShowScroll), style);
-               scrolledComposite = (ScrolledComposite) getParent();
-               scrolledComposite.setContent(this);
-
-               scrolledComposite.setExpandVertical(true);
-               scrolledComposite.setExpandHorizontal(true);
-               scrolledComposite.addControlListener(new ScrollControlListener());
-       }
-
-       private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) {
-               ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
-               scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll);
-               return scrolledComposite;
-       }
-
-       @Override
-       public void layout(boolean changed, boolean all) {
-               updateScroll();
-               super.layout(changed, all);
-       }
-
-       public void showControl(Control control) {
-               scrolledComposite.showControl(control);
-       }
-
-       protected void updateScroll() {
-               Rectangle r = scrolledComposite.getClientArea();
-               Point preferredSize = computeSize(r.width, SWT.DEFAULT);
-               scrolledComposite.setMinHeight(preferredSize.y);
-       }
-
-       // public ScrolledComposite getScrolledComposite() {
-       // return this.scrolledComposite;
-       // }
-
-       /** Set it on the wrapping scrolled composite */
-       @Override
-       public void setLayoutData(Object layoutData) {
-               scrolledComposite.setLayoutData(layoutData);
-       }
-
-       private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter {
-               private static final long serialVersionUID = -3586986238567483316L;
-
-               public void controlResized(ControlEvent e) {
-                       updateScroll();
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java
deleted file mode 100644 (file)
index e3a5cb4..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.FocusListener;
-import org.eclipse.swt.events.MouseListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Editable text part displaying styled text. */
-public abstract class StyledControl extends JcrComposite implements CmsUiConstants {
-       private static final long serialVersionUID = -6372283442330912755L;
-       private Control control;
-
-       private Composite container;
-       private Composite box;
-
-       protected MouseListener mouseListener;
-       protected FocusListener focusListener;
-
-       private Boolean editing = Boolean.FALSE;
-
-       private Composite ancestorToLayout;
-
-       public StyledControl(Composite parent, int swtStyle) {
-               super(parent, swtStyle);
-               setLayout(CmsSwtUtils.noSpaceGridLayout());
-       }
-
-       public StyledControl(Composite parent, int style, Item item) {
-               super(parent, style, item);
-       }
-
-       public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) {
-               super(parent, style, item, cacheImmediately);
-       }
-
-       protected abstract Control createControl(Composite box, String style);
-
-       protected Composite createBox() {
-               Composite box = new Composite(container, SWT.INHERIT_DEFAULT);
-               setContainerLayoutData(box);
-               box.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
-               return box;
-       }
-
-       protected Composite createContainer() {
-               Composite container = new Composite(this, SWT.INHERIT_DEFAULT);
-               setContainerLayoutData(container);
-               container.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               return container;
-       }
-
-       public Control getControl() {
-               return control;
-       }
-
-       protected synchronized Boolean isEditing() {
-               return editing;
-       }
-
-       public synchronized void startEditing() {
-               assert !isEditing();
-               editing = true;
-               // int height = control.getSize().y;
-               String style = (String) EclipseUiSpecificUtils.getStyleData(control);
-               clear(false);
-               refreshControl(style);
-
-               // add the focus listener to the newly created edition control
-               if (focusListener != null)
-                       control.addFocusListener(focusListener);
-       }
-
-       public synchronized void stopEditing() {
-               assert isEditing();
-               editing = false;
-               String style = (String) EclipseUiSpecificUtils.getStyleData(control);
-               clear(false);
-               refreshControl(style);
-       }
-
-       protected void refreshControl(String style) {
-               control = createControl(box, style);
-               setControlLayoutData(control);
-               if (ancestorToLayout != null)
-                       ancestorToLayout.layout(true, true);
-               else
-                       getParent().layout(true, true);
-       }
-
-       public void setStyle(String style) {
-               Object currentStyle = null;
-               if (control != null)
-                       currentStyle = EclipseUiSpecificUtils.getStyleData(control);
-               if (currentStyle != null && currentStyle.equals(style))
-                       return;
-
-               clear(true);
-               refreshControl(style);
-
-               if (style != null) {
-                       CmsSwtUtils.style(box, style + "_box");
-                       CmsSwtUtils.style(container, style + "_container");
-               }
-       }
-
-       /** To be overridden */
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       /** To be overridden */
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsSwtUtils.fillWidth());
-       }
-
-       protected void clear(boolean deep) {
-               if (deep) {
-                       for (Control control : getChildren())
-                               control.dispose();
-                       container = createContainer();
-                       box = createBox();
-               } else {
-                       control.dispose();
-               }
-       }
-
-       public void setMouseListener(MouseListener mouseListener) {
-               if (this.mouseListener != null && control != null)
-                       control.removeMouseListener(this.mouseListener);
-               this.mouseListener = mouseListener;
-               if (control != null && this.mouseListener != null)
-                       control.addMouseListener(mouseListener);
-       }
-
-       public void setFocusListener(FocusListener focusListener) {
-               if (this.focusListener != null && control != null)
-                       control.removeFocusListener(this.focusListener);
-               this.focusListener = focusListener;
-               if (control != null && this.focusListener != null)
-                       control.addFocusListener(focusListener);
-       }
-
-       public void setAncestorToLayout(Composite ancestorToLayout) {
-               this.ancestorToLayout = ancestorToLayout;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java
deleted file mode 100644 (file)
index e461ed0..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-/** Styles references in the CSS. */
-public interface TextStyles {
-       /** The whole page area */
-       public final static String TEXT_AREA = "text_area";
-       /** Area providing controls for editing text */
-       public final static String TEXT_EDITOR_HEADER = "text_editor_header";
-       /** The styled composite for editing the text */
-       public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite";
-       /** A section */
-       public final static String TEXT_SECTION = "text_section";
-       /** A paragraph */
-       public final static String TEXT_PARAGRAPH = "text_paragraph";
-       /** An image */
-       public final static String TEXT_IMG = "text_img";
-       /** The dialog to edit styled paragraph */
-       public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog";
-
-       /*
-        * DEFAULT TEXT STYLES
-        */
-       /** Default style for text body */
-       public final static String TEXT_DEFAULT = "text_default";
-       /** Fixed-width, typically code */
-       public final static String TEXT_PRE = "text_pre";
-       /** Quote */
-       public final static String TEXT_QUOTE = "text_quote";
-       /** Title */
-       public final static String TEXT_TITLE = "text_title";
-       /** Header (to be dynamically completed with the depth, e.g. text_h1) */
-       public final static String TEXT_H = "text_h";
-
-       /** Default style for images */
-       public final static String TEXT_IMAGE = "text_image";
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java
deleted file mode 100644 (file)
index 514f753..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS generic widgets, based on SWT. */
-package org.argeo.cms.ui.widgets;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java
deleted file mode 100644 (file)
index fdafa98..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.AbstractTreeContentProvider;
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/** Canonical implementation of tree content provider manipulating JCR nodes. */
-public abstract class AbstractNodeContentProvider extends
-               AbstractTreeContentProvider {
-       private static final long serialVersionUID = -4905836490027272569L;
-
-       private final static CmsLog log = CmsLog
-                       .getLog(AbstractNodeContentProvider.class);
-
-       private Session session;
-
-       public AbstractNodeContentProvider(Session session) {
-               this.session = session;
-       }
-
-       /**
-        * Whether this path is a base path (and thus has no parent). By default it
-        * returns true if path is '/' (root node)
-        */
-       protected Boolean isBasePath(String path) {
-               // root node
-               return path.equals("/");
-       }
-
-       @Override
-       public Object[] getChildren(Object element) {
-               Object[] children;
-               if (element instanceof Node) {
-                       try {
-                               Node node = (Node) element;
-                               children = getChildren(node);
-                       } catch (RepositoryException e) {
-                               throw new EclipseUiException("Cannot get children of " + element, e);
-                       }
-               } else if (element instanceof WrappedNode) {
-                       WrappedNode wrappedNode = (WrappedNode) element;
-                       try {
-                               children = getChildren(wrappedNode.getNode());
-                       } catch (RepositoryException e) {
-                               throw new EclipseUiException("Cannot get children of "
-                                               + wrappedNode, e);
-                       }
-               } else if (element instanceof NodesWrapper) {
-                       NodesWrapper node = (NodesWrapper) element;
-                       children = node.getChildren();
-               } else {
-                       children = super.getChildren(element);
-               }
-
-               children = sort(element, children);
-               return children;
-       }
-
-       /** Do not sort by default. To be overidden to provide custom sort. */
-       protected Object[] sort(Object parent, Object[] children) {
-               return children;
-       }
-
-       /**
-        * To be overridden in order to filter out some nodes. Does nothing by
-        * default. The provided list is a temporary one and can thus be modified
-        * directly . (e.g. via an iterator)
-        */
-       protected List<Node> filterChildren(List<Node> children)
-                       throws RepositoryException {
-               return children;
-       }
-
-       protected Object[] getChildren(Node node) throws RepositoryException {
-               List<Node> nodes = new ArrayList<Node>();
-               for (NodeIterator nit = node.getNodes(); nit.hasNext();)
-                       nodes.add(nit.nextNode());
-               nodes = filterChildren(nodes);
-               return nodes.toArray();
-       }
-
-       @Override
-       public Object getParent(Object element) {
-               if (element instanceof Node) {
-                       Node node = (Node) element;
-                       try {
-                               String path = node.getPath();
-                               if (isBasePath(path))
-                                       return null;
-                               else
-                                       return node.getParent();
-                       } catch (RepositoryException e) {
-                               log.warn("Cannot get parent of " + element + ": " + e);
-                               return null;
-                       }
-               } else if (element instanceof WrappedNode) {
-                       WrappedNode wrappedNode = (WrappedNode) element;
-                       return wrappedNode.getParent();
-               } else if (element instanceof NodesWrapper) {
-                       NodesWrapper nodesWrapper = (NodesWrapper) element;
-                       return this.getParent(nodesWrapper.getNode());
-               }
-               return super.getParent(element);
-       }
-
-       @Override
-       public boolean hasChildren(Object element) {
-               try {
-                       if (element instanceof Node) {
-                               Node node = (Node) element;
-                               return node.hasNodes();
-                       } else if (element instanceof WrappedNode) {
-                               WrappedNode wrappedNode = (WrappedNode) element;
-                               return wrappedNode.getNode().hasNodes();
-                       } else if (element instanceof NodesWrapper) {
-                               NodesWrapper nodesWrapper = (NodesWrapper) element;
-                               return nodesWrapper.hasChildren();
-                       }
-
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot check whether " + element
-                                       + " has children", e);
-               }
-               return super.hasChildren(element);
-       }
-
-       public Session getSession() {
-               return session;
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java
deleted file mode 100644 (file)
index b880a63..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.swt.widgets.Display;
-
-/**
- * {@link EventListener} which simplifies running actions within the UI thread.
- */
-public abstract class AsyncUiEventListener implements EventListener {
-       // private final static Log logSuper = LogFactory
-       // .getLog(AsyncUiEventListener.class);
-       private final CmsLog logThis = CmsLog.getLog(getClass());
-
-       private final Display display;
-
-       public AsyncUiEventListener(Display display) {
-               super();
-               this.display = display;
-       }
-
-       /** Called asynchronously in the UI thread. */
-       protected abstract void onEventInUiThread(List<Event> events) throws RepositoryException;
-
-       /**
-        * Whether these events should be processed in the UI or skipped with no UI
-        * job created.
-        */
-       protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
-               return true;
-       }
-
-       protected CmsLog getLog() {
-               return logThis;
-       }
-
-       public final void onEvent(final EventIterator eventIterator) {
-               final List<Event> events = new ArrayList<Event>();
-               while (eventIterator.hasNext())
-                       events.add(eventIterator.nextEvent());
-
-               if (logThis.isTraceEnabled())
-                       logThis.trace("Received " + events.size() + " events");
-
-               try {
-                       if (!willProcessInUiThread(events))
-                               return;
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot test skip events " + events, e);
-               }
-
-               // Job job = new Job("JCR Events") {
-               // protected IStatus run(IProgressMonitor monitor) {
-               // if (display.isDisposed()) {
-               // logSuper.warn("Display is disposed cannot update UI");
-               // return Status.CANCEL_STATUS;
-               // }
-
-               if (!display.isDisposed())
-                       display.asyncExec(new Runnable() {
-                               public void run() {
-                                       try {
-                                               onEventInUiThread(events);
-                                       } catch (RepositoryException e) {
-                                               throw new EclipseUiException("Cannot process events " + events, e);
-                                       }
-                               }
-                       });
-
-               // return Status.OK_STATUS;
-               // }
-               // };
-               // job.schedule();
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java
deleted file mode 100644 (file)
index 22ffeaf..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/**
- * Default label provider to manage node and corresponding UI objects. It
- * provides reasonable overwrite-able default for known JCR types.
- */
-public class DefaultNodeLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = 1216182332792151235L;
-
-       public String getText(Object element) {
-               try {
-                       if (element instanceof Node) {
-                               return getText((Node) element);
-                       } else if (element instanceof WrappedNode) {
-                               return getText(((WrappedNode) element).getNode());
-                       } else if (element instanceof NodesWrapper) {
-                               return getText(((NodesWrapper) element).getNode());
-                       }
-                       return super.getText(element);
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot get text for of " + element, e);
-               }
-       }
-
-       protected String getText(Node node) throws RepositoryException {
-               if (node.isNodeType(NodeType.MIX_TITLE)
-                               && node.hasProperty(Property.JCR_TITLE))
-                       return node.getProperty(Property.JCR_TITLE).getString();
-               else
-                       return node.getName();
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               try {
-                       if (element instanceof Node) {
-                               return getImage((Node) element);
-                       } else if (element instanceof WrappedNode) {
-                               return getImage(((WrappedNode) element).getNode());
-                       } else if (element instanceof NodesWrapper) {
-                               return getImage(((NodesWrapper) element).getNode());
-                       }
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot retrieve image for " + element, e);
-               }
-               return super.getImage(element);
-       }
-
-       protected Image getImage(Node node) throws RepositoryException {
-               // FIXME who uses that?
-               return null;
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               try {
-                       if (element instanceof Node) {
-                               return getToolTipText((Node) element);
-                       } else if (element instanceof WrappedNode) {
-                               return getToolTipText(((WrappedNode) element).getNode());
-                       } else if (element instanceof NodesWrapper) {
-                               return getToolTipText(((NodesWrapper) element).getNode());
-                       }
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot get tooltip for " + element, e);
-               }
-               return super.getToolTipText(element);
-       }
-
-       protected String getToolTipText(Node node) throws RepositoryException {
-               return null;
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java
deleted file mode 100644 (file)
index b83aaa2..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import org.argeo.jcr.JcrMonitor;
-import org.eclipse.core.runtime.IProgressMonitor;
-
-/**
- * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to
- * framework agnostic Argeo routines.
- */
-public class EclipseJcrMonitor implements JcrMonitor {
-       private final IProgressMonitor progressMonitor;
-
-       public EclipseJcrMonitor(IProgressMonitor progressMonitor) {
-               this.progressMonitor = progressMonitor;
-       }
-
-       public void beginTask(String name, int totalWork) {
-               progressMonitor.beginTask(name, totalWork);
-       }
-
-       public void done() {
-               progressMonitor.done();
-       }
-
-       public boolean isCanceled() {
-               return progressMonitor.isCanceled();
-       }
-
-       public void setCanceled(boolean value) {
-               progressMonitor.setCanceled(value);
-       }
-
-       public void setTaskName(String name) {
-               progressMonitor.setTaskName(name);
-       }
-
-       public void subTask(String name) {
-               progressMonitor.subTask(name);
-       }
-
-       public void worked(int work) {
-               progressMonitor.worked(work);
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java
deleted file mode 100644 (file)
index 420154b..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.Calendar;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator;
-import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator;
-import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Table;
-
-/** Utility methods to simplify UI development using SWT (or RWT), jface  and JCR. */
-public class JcrUiUtils {
-
-       /**
-        * Centralizes management of updating property value. Among other to avoid
-        * infinite loop when the new value is the same as the ones that is already
-        * stored in JCR.
-        * 
-        * @return true if the value as changed
-        */
-       public static boolean setJcrProperty(Node node, String propName,
-                       int propertyType, Object value) {
-               try {
-                       switch (propertyType) {
-                       case PropertyType.STRING:
-                               if ("".equals((String) value)
-                                               && (!node.hasProperty(propName) || node
-                                                               .hasProperty(propName)
-                                                               && "".equals(node.getProperty(propName)
-                                                                               .getString())))
-                                       // workaround the fact that the Text widget value cannot be
-                                       // set to null
-                                       return false;
-                               else if (node.hasProperty(propName)
-                                               && node.getProperty(propName).getString()
-                                                               .equals((String) value))
-                                       // nothing changed yet
-                                       return false;
-                               else {
-                                       node.setProperty(propName, (String) value);
-                                       return true;
-                               }
-                       case PropertyType.BOOLEAN:
-                               if (node.hasProperty(propName)
-                                               && node.getProperty(propName).getBoolean() == (Boolean) value)
-                                       // nothing changed yet
-                                       return false;
-                               else {
-                                       node.setProperty(propName, (Boolean) value);
-                                       return true;
-                               }
-                       case PropertyType.DATE:
-                               if (node.hasProperty(propName)
-                                               && node.getProperty(propName).getDate()
-                                                               .equals((Calendar) value))
-                                       // nothing changed yet
-                                       return false;
-                               else {
-                                       node.setProperty(propName, (Calendar) value);
-                                       return true;
-                               }
-                       case PropertyType.LONG:
-                               Long lgValue = (Long) value;
-
-                               if (lgValue == null)
-                                       lgValue = 0L;
-
-                               if (node.hasProperty(propName)
-                                               && node.getProperty(propName).getLong() == lgValue)
-                                       // nothing changed yet
-                                       return false;
-                               else {
-                                       node.setProperty(propName, lgValue);
-                                       return true;
-                               }
-
-                       default:
-                               throw new EclipseUiException("Unimplemented property save");
-                       }
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unexpected error while setting property",
-                                       re);
-               }
-       }
-
-       /**
-        * Creates a new selection adapter in order to provide sorting abitily on a
-        * SWT Table that display a row list
-        **/
-       public static SelectionAdapter getRowSelectionAdapter(final int index,
-                       final int propertyType, final String selectorName,
-                       final String propertyName, final RowViewerComparator comparator,
-                       final TableViewer viewer) {
-               SelectionAdapter selectionAdapter = new SelectionAdapter() {
-                       private static final long serialVersionUID = -5738918304901437720L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               Table table = viewer.getTable();
-                               comparator.setColumn(propertyType, selectorName, propertyName);
-                               int dir = table.getSortDirection();
-                               if (table.getSortColumn() == table.getColumn(index)) {
-                                       dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
-                               } else {
-                                       dir = SWT.DOWN;
-                               }
-                               table.setSortDirection(dir);
-                               table.setSortColumn(table.getColumn(index));
-                               viewer.refresh();
-                       }
-               };
-               return selectionAdapter;
-       }
-
-       /**
-        * Creates a new selection adapter in order to provide sorting abitily on a
-        * swt table that display a row list
-        **/
-       public static SelectionAdapter getNodeSelectionAdapter(final int index,
-                       final int propertyType, final String propertyName,
-                       final NodeViewerComparator comparator, final TableViewer viewer) {
-               SelectionAdapter selectionAdapter = new SelectionAdapter() {
-                       private static final long serialVersionUID = -1683220869195484625L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               Table table = viewer.getTable();
-                               comparator.setColumn(propertyType, propertyName);
-                               int dir = table.getSortDirection();
-                               if (table.getSortColumn() == table.getColumn(index)) {
-                                       dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
-                               } else {
-                                       dir = SWT.DOWN;
-                               }
-                               table.setSortDirection(dir);
-                               table.setSortColumn(table.getColumn(index));
-                               viewer.refresh();
-                       }
-               };
-               return selectionAdapter;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java
deleted file mode 100644 (file)
index 7e12bec..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Image;
-
-/** Simplifies writing JCR-based column label provider. */
-public class NodeColumnLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = -6586692836928505358L;
-
-       protected String getNodeText(Node node) throws RepositoryException {
-               return super.getText(node);
-       }
-
-       protected String getNodeToolTipText(Node node) throws RepositoryException {
-               return super.getToolTipText(node);
-       }
-
-       protected Image getNodeImage(Node node) throws RepositoryException {
-               return super.getImage(node);
-       }
-
-       protected Font getNodeFont(Node node) throws RepositoryException {
-               return super.getFont(node);
-       }
-
-       public Color getNodeBackground(Node node) throws RepositoryException {
-               return super.getBackground(node);
-       }
-
-       public Color getNodeForeground(Node node) throws RepositoryException {
-               return super.getForeground(node);
-       }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeText((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeText(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeImage((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeImage(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeToolTipText((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeToolTipText(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Font getFont(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeFont((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeFont(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Color getBackground(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeBackground((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeBackground(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Color getForeground(Object element) {
-               try {
-                       if (element instanceof Node)
-                               return getNodeForeground((Node) element);
-                       else if (element instanceof NodeElement)
-                               return getNodeForeground(((NodeElement) element).getNode());
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java
deleted file mode 100644 (file)
index 787c92e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-
-/** An element which is related to a JCR {@link Node}. */
-public interface NodeElement {
-       Node getNode();
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java
deleted file mode 100644 (file)
index 2f3d64d..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.eclipse.ui.jcr;\r
-\r
-import javax.jcr.Node;\r
-import javax.jcr.RepositoryException;\r
-\r
-import org.argeo.eclipse.ui.EclipseUiException;\r
-import org.eclipse.jface.viewers.IElementComparer;\r
-\r
-/** Element comparer for JCR node, to be used in JFace viewers. */\r
-public class NodeElementComparer implements IElementComparer {\r
-\r
-       public boolean equals(Object a, Object b) {\r
-               try {\r
-                       if ((a instanceof Node) && (b instanceof Node)) {\r
-                               Node nodeA = (Node) a;\r
-                               Node nodeB = (Node) b;\r
-                               return nodeA.getIdentifier().equals(nodeB.getIdentifier());\r
-                       } else {\r
-                               return a.equals(b);\r
-                       }\r
-               } catch (RepositoryException e) {\r
-                       throw new EclipseUiException("Cannot compare nodes", e);\r
-               }\r
-       }\r
-\r
-       public int hashCode(Object element) {\r
-               try {\r
-                       if (element instanceof Node)\r
-                               return ((Node) element).getIdentifier().hashCode();\r
-                       return element.hashCode();\r
-               } catch (RepositoryException e) {\r
-                       throw new EclipseUiException("Cannot get hash code", e);\r
-               }\r
-       }\r
-\r
-}\r
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java
deleted file mode 100644 (file)
index 2f808a5..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/**
- * Element of tree which is based on a node, but whose children are not
- * necessarily this node children.
- */
-public class NodesWrapper {
-       private final Node node;
-
-       public NodesWrapper(Node node) {
-               super();
-               this.node = node;
-       }
-
-       protected NodeIterator getNodeIterator() throws RepositoryException {
-               return node.getNodes();
-       }
-
-       protected List<WrappedNode> getWrappedNodes() throws RepositoryException {
-               List<WrappedNode> nodes = new ArrayList<WrappedNode>();
-               for (NodeIterator nit = getNodeIterator(); nit.hasNext();)
-                       nodes.add(new WrappedNode(this, nit.nextNode()));
-               return nodes;
-       }
-
-       public Object[] getChildren() {
-               try {
-                       return getWrappedNodes().toArray();
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot get wrapped children", e);
-               }
-       }
-
-       /**
-        * @return true by default because we don't want to compute the wrapped
-        *         nodes twice
-        */
-       public Boolean hasChildren() {
-               return true;
-       }
-
-       public Node getNode() {
-               return node;
-       }
-
-       @Override
-       public int hashCode() {
-               return node.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof NodesWrapper)
-                       return node.equals(((NodesWrapper) obj).getNode());
-               else
-                       return false;
-       }
-
-       public String toString() {
-               return "nodes wrapper based on " + node;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java
deleted file mode 100644 (file)
index 934fa67..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Query;
-
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/** Content provider based on a JCR {@link Query}. */
-public class QueryTableContentProvider implements IStructuredContentProvider {
-       private static final long serialVersionUID = 760371460907204722L;
-
-       @Override
-       public void dispose() {
-       }
-
-       @Override
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-       }
-
-       @Override
-       public Object[] getElements(Object inputElement) {
-               Query query = (Query) inputElement;
-               try {
-                       NodeIterator nit = query.execute().getNodes();
-                       return JcrUtils.nodeIteratorToList(nit).toArray();
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java
deleted file mode 100644 (file)
index 70c71ef..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Image;
-
-/** Simplifies writing JCR-based column label provider. */
-public class RowColumnLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = -6586692836928505358L;
-
-       protected String getRowText(Row row) throws RepositoryException {
-               return super.getText(row);
-       }
-
-       protected String getRowToolTipText(Row row) throws RepositoryException {
-               return super.getToolTipText(row);
-       }
-
-       protected Image getRowImage(Row row) throws RepositoryException {
-               return super.getImage(row);
-       }
-
-       protected Font getRowFont(Row row) throws RepositoryException {
-               return super.getFont(row);
-       }
-
-       public Color getRowBackground(Row row) throws RepositoryException {
-               return super.getBackground(row);
-       }
-
-       public Color getRowForeground(Row row) throws RepositoryException {
-               return super.getForeground(row);
-       }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowText((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowImage((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowToolTipText((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Font getFont(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowFont((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Color getBackground(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowBackground((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Color getForeground(Object element) {
-               try {
-                       if (element instanceof Row)
-                               return getRowForeground((Row) element);
-                       else
-                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Repository exception when accessing " + element, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java
deleted file mode 100644 (file)
index cb235d7..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.jcr.JcrUtils;
-
-/** Simple JCR node content provider taking a list of String as base path. */
-public class SimpleNodeContentProvider extends AbstractNodeContentProvider {
-       private static final long serialVersionUID = -8245193308831384269L;
-       private final List<String> basePaths;
-       private Boolean mkdirs = false;
-
-       public SimpleNodeContentProvider(Session session, String... basePaths) {
-               this(session, Arrays.asList(basePaths));
-       }
-
-       public SimpleNodeContentProvider(Session session, List<String> basePaths) {
-               super(session);
-               this.basePaths = basePaths;
-       }
-
-       @Override
-       protected Boolean isBasePath(String path) {
-               if (basePaths.contains(path))
-                       return true;
-               return super.isBasePath(path);
-       }
-
-       public Object[] getElements(Object inputElement) {
-               try {
-                       List<Node> baseNodes = new ArrayList<Node>();
-                       for (String basePath : basePaths)
-                               if (mkdirs && !getSession().itemExists(basePath))
-                                       baseNodes.add(JcrUtils.mkdirs(getSession(), basePath));
-                               else
-                                       baseNodes.add(getSession().getNode(basePath));
-                       return baseNodes.toArray();
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot get base nodes for " + basePaths,
-                                       e);
-               }
-       }
-
-       public List<String> getBasePaths() {
-               return basePaths;
-       }
-
-       public void setMkdirs(Boolean mkdirs) {
-               this.mkdirs = mkdirs;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java
deleted file mode 100644 (file)
index 1ce3154..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.swt.graphics.Image;
-
-/** Simplifies writing JCR-based column label provider. */
-public class VersionColumnLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = -6117690082313161159L;
-
-       protected String getVersionText(Version version) throws RepositoryException {
-               return super.getText(version);
-       }
-
-       protected String getVersionToolTipText(Version version) throws RepositoryException {
-               return super.getToolTipText(version);
-       }
-
-       protected Image getVersionImage(Version version) throws RepositoryException {
-               return super.getImage(version);
-       }
-
-       protected String getUserName(Version version) throws RepositoryException {
-               Node node = version.getFrozenNode();
-               if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
-                       return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString();
-               if(node.hasProperty(Property.JCR_CREATED_BY))
-                       return node.getProperty(Property.JCR_CREATED_BY).getString();
-               return null;
-       }
-       
-//     protected String getActivityTitle(Version version) throws RepositoryException {
-//             Node activity = getActivity(version);
-//             if (activity == null)
-//                     return null;
-//             if (activity.hasProperty("jcr:activityTitle"))
-//                     return activity.getProperty("jcr:activityTitle").getString();
-//             else
-//                     return activity.getName();
-//     }
-//
-//     protected Node getActivity(Version version) throws RepositoryException {
-//             if (version.hasProperty(Property.JCR_ACTIVITY)) {
-//                     return version.getProperty(Property.JCR_ACTIVITY).getNode();
-//             } else
-//                     return null;
-//     }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       return getVersionText((Version) element);
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Runtime repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public Image getImage(Object element) {
-               try {
-                       return getVersionImage((Version) element);
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Runtime repository exception when accessing " + element, e);
-               }
-       }
-
-       @Override
-       public String getToolTipText(Object element) {
-               try {
-                       return getVersionToolTipText((Version) element);
-               } catch (RepositoryException e) {
-                       throw new RuntimeException("Runtime repository exception when accessing " + element, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java
deleted file mode 100644 (file)
index 32e5d30..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.version.VersionHistory;
-
-import org.argeo.jcr.Jcr;
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/** Content provider based on a {@link VersionHistory}. */
-public class VersionHistoryContentProvider implements IStructuredContentProvider {
-       private static final long serialVersionUID = -4921107883428887012L;
-
-       @Override
-       public void dispose() {
-       }
-
-       @Override
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
-       }
-
-       @Override
-       public Object[] getElements(Object inputElement) {
-               VersionHistory versionHistory = (VersionHistory) inputElement;
-               return Jcr.getLinearVersions(versionHistory).toArray();
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java
deleted file mode 100644 (file)
index 43df1fe..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.eclipse.ui.jcr;
-
-import javax.jcr.Node;
-
-/** Wrap a node (created from a {@link NodesWrapper}) */
-public class WrappedNode {
-       private final NodesWrapper parent;
-       private final Node node;
-
-       public WrappedNode(NodesWrapper parent, Node node) {
-               super();
-               this.parent = parent;
-               this.node = node;
-       }
-
-       public NodesWrapper getParent() {
-               return parent;
-       }
-
-       public Node getNode() {
-               return node;
-       }
-
-       public String toString() {
-               return "wrapped " + node;
-       }
-
-       @Override
-       public int hashCode() {
-               return node.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof WrappedNode)
-                       return node.equals(((WrappedNode) obj).getNode());
-               else
-                       return false;
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java
deleted file mode 100644 (file)
index c5dd733..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.ColumnDefinition;
-
-/**
- * Utility object to manage column in various tables and extracts displaying
- * data from JCR
- */
-public class JcrColumnDefinition extends ColumnDefinition {
-       private final static int DEFAULT_COLUMN_SIZE = 120;
-
-       private String selectorName;
-       private String propertyName;
-       private int propertyType;
-       private int columnSize;
-
-       /**
-        * Use this kind of columns to configure a table that displays JCR
-        * {@link Row}
-        * 
-        * @param selectorName
-        * @param propertyName
-        * @param propertyType
-        * @param headerLabel
-        */
-       public JcrColumnDefinition(String selectorName, String propertyName,
-                       int propertyType, String headerLabel) {
-               super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
-                               headerLabel);
-               this.selectorName = selectorName;
-               this.propertyName = propertyName;
-               this.propertyType = propertyType;
-               this.columnSize = DEFAULT_COLUMN_SIZE;
-       }
-
-       /**
-        * Use this kind of columns to configure a table that displays JCR
-        * {@link Row}
-        * 
-        * @param selectorName
-        * @param propertyName
-        * @param propertyType
-        * @param headerLabel
-        * @param columnSize
-        */
-       public JcrColumnDefinition(String selectorName, String propertyName,
-                       int propertyType, String headerLabel, int columnSize) {
-               super(new SimpleJcrRowLabelProvider(selectorName, propertyName),
-                               headerLabel, columnSize);
-               this.selectorName = selectorName;
-               this.propertyName = propertyName;
-               this.propertyType = propertyType;
-               this.columnSize = columnSize;
-       }
-
-       /**
-        * Use this kind of columns to configure a table that displays JCR
-        * {@link Node}
-        * 
-        * @param propertyName
-        * @param propertyType
-        * @param headerLabel
-        * @param columnSize
-        */
-       public JcrColumnDefinition(String propertyName, int propertyType,
-                       String headerLabel, int columnSize) {
-               super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel,
-                               columnSize);
-               this.propertyName = propertyName;
-               this.propertyType = propertyType;
-               this.columnSize = columnSize;
-       }
-
-       public String getSelectorName() {
-               return selectorName;
-       }
-
-       public void setSelectorName(String selectorName) {
-               this.selectorName = selectorName;
-       }
-
-       public String getPropertyName() {
-               return propertyName;
-       }
-
-       public void setPropertyName(String propertyName) {
-               this.propertyName = propertyName;
-       }
-
-       public int getPropertyType() {
-               return propertyType;
-       }
-
-       public void setPropertyType(int propertyType) {
-               this.propertyType = propertyType;
-       }
-
-       public int getColumnSize() {
-               return columnSize;
-       }
-
-       public void setColumnSize(int columnSize) {
-               this.columnSize = columnSize;
-       }
-
-       public String getHeaderLabel() {
-               return super.getLabel();
-       }
-
-       public void setHeaderLabel(String headerLabel) {
-               super.setLabel(headerLabel);
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java
deleted file mode 100644 (file)
index 341b3ab..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-package org.argeo.eclipse.ui.jcr.lists;
-
-import java.math.BigDecimal;
-import java.util.Calendar;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerComparator;
-
-/**
- * Base comparator to enable ordering on Table or Tree viewer that display Jcr
- * Nodes.
- * 
- * Note that the following snippet must be added before setting the comparator
- * to the corresponding control: <code>
- * // IMPORTANT: initialize comparator before setting it
- * JcrColumnDefinition firstCol = colDefs.get(0);
- * comparator.setColumn(firstCol.getPropertyType(),
- * firstCol.getPropertyName());
- * viewer.setComparator(comparator); </code>
- */
-public class NodeViewerComparator extends ViewerComparator {
-       private static final long serialVersionUID = -7782916140737279027L;
-
-       protected String propertyName;
-
-       protected int propertyType;
-       public static final int ASCENDING = 0, DESCENDING = 1;
-       protected int direction = DESCENDING;
-
-       public NodeViewerComparator() {
-       }
-
-       /**
-        * e1 and e2 must both be Jcr nodes.
-        * 
-        * @param viewer
-        * @param e1
-        * @param e2
-        * @return
-        */
-       @Override
-       public int compare(Viewer viewer, Object e1, Object e2) {
-               int rc = 0;
-               long lc = 0;
-
-               try {
-                       Node n1 = (Node) e1;
-                       Node n2 = (Node) e2;
-
-                       Value v1 = null;
-                       Value v2 = null;
-                       if (n1.hasProperty(propertyName))
-                               v1 = n1.getProperty(propertyName).getValue();
-                       if (n2.hasProperty(propertyName))
-                               v2 = n2.getProperty(propertyName).getValue();
-
-                       if (v2 == null && v1 == null)
-                               return 0;
-                       else if (v2 == null)
-                               return -1;
-                       else if (v1 == null)
-                               return 1;
-
-                       switch (propertyType) {
-                       case PropertyType.STRING:
-                               rc = v1.getString().compareTo(v2.getString());
-                               break;
-                       case PropertyType.BOOLEAN:
-                               boolean b1 = v1.getBoolean();
-                               boolean b2 = v2.getBoolean();
-                               if (b1 == b2)
-                                       rc = 0;
-                               else
-                                       // we assume true is greater than false
-                                       rc = b1 ? 1 : -1;
-                               break;
-                       case PropertyType.DATE:
-                               Calendar c1 = v1.getDate();
-                               Calendar c2 = v2.getDate();
-                               if (c1 == null || c2 == null)
-                                       // log.trace("undefined date");
-                                       ;
-                               lc = c1.getTimeInMillis() - c2.getTimeInMillis();
-                               if (lc < Integer.MIN_VALUE)
-                                       rc = -1;
-                               else if (lc > Integer.MAX_VALUE)
-                                       rc = 1;
-                               else
-                                       rc = (int) lc;
-                               break;
-                       case PropertyType.LONG:
-                               long l1;
-                               long l2;
-                               // TODO Sometimes an empty string is set instead of a long
-                               try {
-                                       l1 = v1.getLong();
-                               } catch (ValueFormatException ve) {
-                                       l1 = 0;
-                               }
-                               try {
-                                       l2 = v2.getLong();
-                               } catch (ValueFormatException ve) {
-                                       l2 = 0;
-                               }
-
-                               lc = l1 - l2;
-                               if (lc < Integer.MIN_VALUE)
-                                       rc = -1;
-                               else if (lc > Integer.MAX_VALUE)
-                                       rc = 1;
-                               else
-                                       rc = (int) lc;
-                               break;
-                       case PropertyType.DECIMAL:
-                               BigDecimal bd1 = v1.getDecimal();
-                               BigDecimal bd2 = v2.getDecimal();
-                               rc = bd1.compareTo(bd2);
-                               break;
-                       case PropertyType.DOUBLE:
-                               Double d1 = v1.getDouble();
-                               Double d2 = v2.getDouble();
-                               rc = d1.compareTo(d2);
-                               break;
-                       default:
-                               throw new EclipseUiException(
-                                               "Unimplemented comparaison for PropertyType "
-                                                               + propertyType);
-                       }
-                       // If descending order, flip the direction
-                       if (direction == DESCENDING) {
-                               rc = -rc;
-                       }
-
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unexpected error "
-                                       + "while comparing nodes", re);
-               }
-               return rc;
-       }
-
-       /**
-        * @param propertyType
-        *            Corresponding JCR type
-        * @param propertyName
-        *            name of the property to use.
-        */
-       public void setColumn(int propertyType, String propertyName) {
-               if (this.propertyName != null && this.propertyName.equals(propertyName)) {
-                       // Same column as last sort; toggle the direction
-                       direction = 1 - direction;
-               } else {
-                       // New column; do an ascending sort
-                       this.propertyType = propertyType;
-                       this.propertyName = propertyName;
-                       direction = ASCENDING;
-               }
-       }
-
-       // Getters and setters
-       protected String getPropertyName() {
-               return propertyName;
-       }
-
-       protected void setPropertyName(String propertyName) {
-               this.propertyName = propertyName;
-       }
-
-       protected int getPropertyType() {
-               return propertyType;
-       }
-
-       protected void setPropertyType(int propertyType) {
-               this.propertyType = propertyType;
-       }
-
-       protected int getDirection() {
-               return direction;
-       }
-
-       protected void setDirection(int direction) {
-               this.direction = direction;
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java
deleted file mode 100644 (file)
index 455fb0d..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * Base comparator to enable ordering on Table or Tree viewer that display Jcr
- * rows
- */
-public class RowViewerComparator extends NodeViewerComparator {
-       private static final long serialVersionUID = 7020939505172625113L;
-       protected String selectorName;
-
-       public RowViewerComparator() {
-       }
-
-       /**
-        * e1 and e2 must both be Jcr rows.
-        * 
-        * @param viewer
-        * @param e1
-        * @param e2
-        * @return
-        */
-       @Override
-       public int compare(Viewer viewer, Object e1, Object e2) {
-               try {
-                       Node n1 = ((Row) e1).getNode(selectorName);
-                       Node n2 = ((Row) e2).getNode(selectorName);
-                       return super.compare(viewer, n1, n2);
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unexpected error "
-                                       + "while comparing nodes", re);
-               }
-       }
-
-       /**
-        * @param propertyType
-        *            Corresponding JCR type
-        * @param propertyName
-        *            name of the property to use.
-        */
-       public void setColumn(int propertyType, String selectorName,
-                       String propertyName) {
-               if (this.selectorName != null && getPropertyName() != null
-                               && this.selectorName.equals(selectorName)
-                               && this.getPropertyName().equals(propertyName)) {
-                       // Same column as last sort; toggle the direction
-                       setDirection(1 - getDirection());
-               } else {
-                       // New column; do a descending sort
-                       setPropertyType(propertyType);
-                       setPropertyName(propertyName);
-                       this.selectorName = selectorName;
-                       setDirection(NodeViewerComparator.ASCENDING);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java
deleted file mode 100644 (file)
index aa2e337..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.argeo.eclipse.ui.jcr.lists;
-
-import java.text.DateFormat;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.text.SimpleDateFormat;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-
-/** Base implementation of a label provider for controls that display JCR Nodes */
-public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider {
-       private static final long serialVersionUID = -5215787695436221993L;
-
-       private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy";
-       private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0";
-
-       private DateFormat dateFormat;
-       private NumberFormat numberFormat;
-
-       final private String propertyName;
-
-       /**
-        * Default Label provider for a given property of a node. Using default
-        * pattern for date and number formating
-        */
-       public SimpleJcrNodeLabelProvider(String propertyName) {
-               this.propertyName = propertyName;
-               dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
-               numberFormat = DecimalFormat.getInstance();
-               ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT);
-       }
-
-       /**
-        * Label provider for a given property of a node optionally precising date
-        * and/or number format patterns
-        */
-       public SimpleJcrNodeLabelProvider(String propertyName,
-                       String dateFormatPattern, String numberFormatPattern) {
-               this.propertyName = propertyName;
-               dateFormat = new SimpleDateFormat(
-                               dateFormatPattern == null ? DEFAULT_DATE_FORMAT
-                                               : dateFormatPattern);
-               numberFormat = DecimalFormat.getInstance();
-               ((DecimalFormat) numberFormat)
-                               .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT
-                                               : numberFormatPattern);
-       }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       Node currNode = (Node) element;
-
-                       if (currNode.hasProperty(propertyName)) {
-                               if (currNode.getProperty(propertyName).isMultiple()) {
-                                       StringBuilder builder = new StringBuilder();
-                                       for (Value value : currNode.getProperty(propertyName)
-                                                       .getValues()) {
-                                               String currStr = getSingleValueAsString(value);
-                                               if (notEmptyString(currStr))
-                                                       builder.append(currStr).append("; ");
-                                       }
-                                       if (builder.length() > 0)
-                                               builder.deleteCharAt(builder.length() - 2);
-
-                                       return builder.toString();
-                               } else
-                                       return getSingleValueAsString(currNode.getProperty(
-                                                       propertyName).getValue());
-                       } else
-                               return "";
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unable to get text from row", re);
-               }
-       }
-
-       private String getSingleValueAsString(Value value)
-                       throws RepositoryException {
-               switch (value.getType()) {
-               case PropertyType.STRING:
-                       return value.getString();
-               case PropertyType.BOOLEAN:
-                       return "" + value.getBoolean();
-               case PropertyType.DATE:
-                       return dateFormat.format(value.getDate().getTime());
-               case PropertyType.LONG:
-                       return "" + value.getLong();
-               case PropertyType.DECIMAL:
-                       return numberFormat.format(value.getDecimal());
-               case PropertyType.DOUBLE:
-                       return numberFormat.format(value.getDouble());
-               case PropertyType.NAME:
-                       return value.getString();
-               default:
-                       throw new EclipseUiException("Unimplemented label provider "
-                                       + "for property type " + value.getType()
-                                       + " while getting property " + propertyName + " - value: "
-                                       + value.getString());
-
-               }
-       }
-
-       private boolean notEmptyString(String string) {
-               return string != null && !"".equals(string.trim());
-       }
-
-       public void setDateFormat(String dateFormatPattern) {
-               dateFormat = new SimpleDateFormat(dateFormatPattern);
-       }
-
-       public void setNumberFormat(String numberFormatPattern) {
-               ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern);
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java
deleted file mode 100644 (file)
index 5d421f6..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.argeo.eclipse.ui.jcr.lists;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.query.Row;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/**
- * Base implementation of a label provider for widgets that display JCR Rows.
- */
-public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider {
-       private static final long serialVersionUID = -3414654948197181740L;
-
-       final private String selectorName;
-
-       /**
-        * Default Label provider for a given property of a row. Using default
-        * pattern for date and number formating
-        */
-       public SimpleJcrRowLabelProvider(String selectorName, String propertyName) {
-               super(propertyName);
-               this.selectorName = selectorName;
-       }
-
-       /**
-        * Label provider for a given property of a node optionally precising date
-        * and/or number format patterns
-        */
-       public SimpleJcrRowLabelProvider(String selectorName, String propertyName,
-                       String dateFormatPattern, String numberFormatPattern) {
-               super(propertyName, dateFormatPattern, numberFormatPattern);
-               this.selectorName = selectorName;
-       }
-
-       @Override
-       public String getText(Object element) {
-               try {
-                       Row currRow = (Row) element;
-                       Node currNode = currRow.getNode(selectorName);
-                       return super.getText(currNode);
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Unable to get Node " + selectorName
-                                       + " from row " + element, re);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java
deleted file mode 100644 (file)
index 3678aab..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace JCR utilities for lists. */
-package org.argeo.eclipse.ui.jcr.lists;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java
deleted file mode 100644 (file)
index 19e3cc3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace JCR utilities. */
-package org.argeo.eclipse.ui.jcr;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java
deleted file mode 100644 (file)
index c82e666..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.FileProvider;
-
-/**
- * Implements a FileProvider for UI purposes. Note that it might not be very
- * reliable as long as we have not fixed login and multi repository issues that
- * will be addressed in the next version.
- * 
- * NOTE: id used here is the real id of the JCR Node, not the JCR Path
- * 
- * Relies on common approach for JCR file handling implementation.
- * 
- */
-@SuppressWarnings("deprecation")
-public class JcrFileProvider implements FileProvider {
-
-       // private Object[] rootNodes;
-       private Node refNode;
-
-       /**
-        * Must be set in order for the provider to be able to get current session
-        * and thus have the ability to get the file node corresponding to a given
-        * file ID
-        * 
-        * @param refNode
-        */
-       public void setReferenceNode(Node refNode) {
-               // FIXME : this introduces some concurrency ISSUES.
-               this.refNode = refNode;
-       }
-
-       public byte[] getByteArrayFileFromId(String fileId) {
-               InputStream fis = null;
-               byte[] ba = null;
-               Node child = getFileNodeFromId(fileId);
-               try {
-                       fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
-                       ba = IOUtils.toByteArray(fis);
-
-               } catch (Exception e) {
-                       throw new EclipseUiException("Stream error while opening file", e);
-               } finally {
-                       IOUtils.closeQuietly(fis);
-               }
-               return ba;
-       }
-
-       public InputStream getInputStreamFromFileId(String fileId) {
-               try {
-                       InputStream fis = null;
-
-                       Node child = getFileNodeFromId(fileId);
-                       fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream();
-                       return fis;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re);
-               }
-       }
-
-       /**
-        * Throws an exception if the node is not found in the current repository (a
-        * bit like a FileNotFoundException)
-        * 
-        * @param fileId
-        * @return Returns the child node of the nt:file node. It is the child node
-        *         that have the jcr:data property where actual file is stored.
-        *         never null
-        */
-       private Node getFileNodeFromId(String fileId) {
-               try {
-                       Node result = refNode.getSession().getNodeByIdentifier(fileId);
-
-                       // rootNodes: for (int j = 0; j < rootNodes.length; j++) {
-                       // // in case we have a classic JCR Node
-                       // if (rootNodes[j] instanceof Node) {
-                       // Node curNode = (Node) rootNodes[j];
-                       // if (result != null)
-                       // break rootNodes;
-                       // } // Case of a repository Node
-                       // else if (rootNodes[j] instanceof RepositoryNode) {
-                       // Object[] nodes = ((RepositoryNode) rootNodes[j])
-                       // .getChildren();
-                       // for (int i = 0; i < nodes.length; i++) {
-                       // Node node = (Node) nodes[i];
-                       // result = node.getSession().getNodeByIdentifier(fileId);
-                       // if (result != null)
-                       // break rootNodes;
-                       // }
-                       // }
-                       // }
-
-                       // Sanity checks
-                       if (result == null)
-                               throw new EclipseUiException("File node not found for ID" + fileId);
-
-                       Node child = null;
-
-                       boolean isValid = true;
-                       if (!result.isNodeType(NodeType.NT_FILE))
-                               // useless: mandatory child node
-                               // || !result.hasNode(Property.JCR_CONTENT))
-                               isValid = false;
-                       else {
-                               child = result.getNode(Property.JCR_CONTENT);
-                               if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA)))
-                                       isValid = false;
-                       }
-
-                       if (!isValid)
-                               throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE
-                                               + "' file node must have a child node named jcr:content "
-                                               + "that has a BINARY Property named jcr:data " + "where the actual data is stored");
-                       return child;
-
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java
deleted file mode 100644 (file)
index fb12399..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.util.Comparator;
-
-import javax.jcr.Item;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-
-/** Compares two JCR items (node or properties) based on their names. */
-public class JcrItemsComparator implements Comparator<Item> {
-       public int compare(Item o1, Item o2) {
-               try {
-                       // TODO: put folder before files
-                       return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
-               } catch (RepositoryException e) {
-                       throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e);
-               }
-       }
-
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java
deleted file mode 100644 (file)
index 54b795f..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.eclipse.ui.jcr.util;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.eclipse.jface.viewers.IElementComparer;
-
-/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */
-public class NodeViewerComparer implements IElementComparer {
-
-       // force comparison on Node IDs only.
-       public boolean equals(Object elementA, Object elementB) {
-               if (!(elementA instanceof Node) || !(elementB instanceof Node)) {
-                       return elementA == null ? elementB == null : elementA
-                                       .equals(elementB);
-               } else {
-
-                       boolean result = false;
-                       try {
-                               String idA = ((Node) elementA).getIdentifier();
-                               String idB = ((Node) elementB).getIdentifier();
-                               result = idA == null ? idB == null : idA.equals(idB);
-                       } catch (RepositoryException re) {
-                               throw new EclipseUiException("cannot compare nodes", re);
-                       }
-
-                       return result;
-               }
-       }
-
-       public int hashCode(Object element) {
-               // TODO enhanced this method.
-               return element.getClass().toString().hashCode();
-       }
-}
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java
deleted file mode 100644 (file)
index 291d579..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.eclipse.ui.jcr.util;
-
-import java.io.InputStream;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.eclipse.ui.EclipseUiException;
-import org.argeo.eclipse.ui.FileProvider;
-
-/**
- * Implements a FileProvider for UI purposes. Unlike the
- * <code> JcrFileProvider </code>, it relies on a single session and manages
- * nodes with path only.
- * 
- * Note that considered id is the JCR path
- * 
- * Relies on common approach for JCR file handling implementation.
- */
-@SuppressWarnings("deprecation")
-public class SingleSessionFileProvider implements FileProvider {
-
-       private Session session;
-
-       public SingleSessionFileProvider(Session session) {
-               this.session = session;
-       }
-
-       public byte[] getByteArrayFileFromId(String fileId) {
-               InputStream fis = null;
-               byte[] ba = null;
-               Node child = getFileNodeFromId(fileId);
-               try {
-                       fis = (InputStream) child.getProperty(Property.JCR_DATA)
-                                       .getBinary().getStream();
-                       ba = IOUtils.toByteArray(fis);
-
-               } catch (Exception e) {
-                       throw new EclipseUiException("Stream error while opening file", e);
-               } finally {
-                       IOUtils.closeQuietly(fis);
-               }
-               return ba;
-       }
-
-       public InputStream getInputStreamFromFileId(String fileId) {
-               try {
-                       InputStream fis = null;
-
-                       Node child = getFileNodeFromId(fileId);
-                       fis = (InputStream) child.getProperty(Property.JCR_DATA)
-                                       .getBinary().getStream();
-                       return fis;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Cannot get stream from file node for Id "
-                                       + fileId, re);
-               }
-       }
-
-       /**
-        * 
-        * @param fileId
-        * @return Returns the child node of the nt:file node. It is the child node
-        *         that have the jcr:data property where actual file is stored.
-        *         never null
-        */
-       private Node getFileNodeFromId(String fileId) {
-               try {
-                       Node result = null;
-                       result = session.getNode(fileId);
-
-                       // Sanity checks
-                       if (result == null)
-                               throw new EclipseUiException("File node not found for ID" + fileId);
-
-                       // Ensure that the node have the correct type.
-                       if (!result.isNodeType(NodeType.NT_FILE))
-                               throw new EclipseUiException(
-                                               "Cannot open file children Node that are not of "
-                                                               + NodeType.NT_RESOURCE + " type.");
-
-                       Node child = result.getNodes().nextNode();
-                       if (child == null || !child.isNodeType(NodeType.NT_RESOURCE))
-                               throw new EclipseUiException(
-                                               "ERROR: IN the current implemented model, "
-                                                               + NodeType.NT_FILE
-                                                               + "  file node must have one and only one child of the nt:ressource, where actual data is stored");
-                       return child;
-               } catch (RepositoryException re) {
-                       throw new EclipseUiException("Erreur while getting file node of ID "
-                                       + fileId, re);
-               }
-       }
-}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java
deleted file mode 100644 (file)
index 016348c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic SWT/JFace JCR helpers. */
-package org.argeo.eclipse.ui.jcr.util;
\ No newline at end of file
diff --git a/jni/.cproject b/jni/.cproject
deleted file mode 100644 (file)
index 259cf27..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?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
deleted file mode 100644 (file)
index 492a807..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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
deleted file mode 100644 (file)
index e30ee16..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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
deleted file mode 100644 (file)
index c8ec5df..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-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
deleted file mode 100644 (file)
index de2b84c..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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
deleted file mode 100644 (file)
index 40dde44..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-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
deleted file mode 100644 (file)
index 84c048a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/build/
diff --git a/jni/org_argeo_api_uuid_libuuid/Makefile b/jni/org_argeo_api_uuid_libuuid/Makefile
deleted file mode 100644 (file)
index cfeb1db..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644 (file)
index a5aeed0..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#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
deleted file mode 100644 (file)
index 5f18bf7..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/* 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
deleted file mode 100644 (file)
index f97b1f4..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#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
deleted file mode 100644 (file)
index ad0ac5e..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/* 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
index e801ebfb4680123285c15553dc70584276fe0057..81fe078c20c05db46a8281fbb1a72875a5322b45 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index 6e839c070a62350cf0a2767cb99927583464a132..ea0a76ba91794143947c3a6c90865d501d6aa439 100644 (file)
@@ -1,4 +1,5 @@
 Import-Package: \
-javax.security.*
+javax.security.*, \
+javax.xml.*
 
 Export-Package: org.argeo.api.acr.*
\ No newline at end of file
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java b/org.argeo.api.acr/src/org/argeo/api/acr/ArgeoNamespace.java
new file mode 100644 (file)
index 0000000..98131d1
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.api.acr;
+
+/** Namespaces declared by Argeo. */
+public enum ArgeoNamespace {
+       ;
+
+       public final static String CR_NAMESPACE_URI = "http://www.argeo.org/ns/cr";
+       public final static String CR_DEFAULT_PREFIX = "cr";
+       public final static String LDAP_NAMESPACE_URI = "http://www.argeo.org/ns/ldap";
+       public final static String LDAP_DEFAULT_PREFIX = "ldap";
+       public final static String ROLE_NAMESPACE_URI = "http://www.argeo.org/ns/role";
+       public final static String ROLE_DEFAULT_PREFIX = "role";
+
+}
index 41600331a21b756b634280c09ef70ab40aeb7af1..c7023f1a37a4a58b2582fc62e4f9f7d509fb7336 100644 (file)
@@ -4,9 +4,8 @@ package org.argeo.api.acr;
  * An attribute type MUST consistently parse a string to an object so that
  * <code>parse(obj.toString()).equals(obj)</code> is verified.
  * {@link #format(Object)} can be overridden to provide more efficient
- * implementations but the returned
- * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
- * is verified.
+ * implementations but the returned <code>String</code> MUST be the same, that
+ * is <code>format(obj).equals(obj.toString())</code> is verified.
  */
 public interface AttributeFormatter<T> {
        /** Parses a String to a Java object. */
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java b/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java
deleted file mode 100644 (file)
index b83fe30..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.api.acr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringTokenizer;
-
-/** A name that can be expressed with various conventions. */
-public class CompositeString {
-       public final static Character UNDERSCORE = Character.valueOf('_');
-       public final static Character SPACE = Character.valueOf(' ');
-       public final static Character DASH = Character.valueOf('-');
-
-       private final String[] parts;
-
-       // optimisation
-       private final int hashCode;
-
-       public CompositeString(String str) {
-               Objects.requireNonNull(str, "String cannot be null");
-               if ("".equals(str.trim()))
-                       throw new IllegalArgumentException("String cannot be empty");
-               if (!str.equals(str.trim()))
-                       throw new IllegalArgumentException("String must be trimmed");
-               this.parts = toParts(str);
-               hashCode = hashCode(this.parts);
-       }
-
-       public String toString(char separator, boolean upperCase) {
-               StringBuilder sb = null;
-               for (String part : parts) {
-                       if (sb == null) {
-                               sb = new StringBuilder();
-                       } else {
-                               sb.append(separator);
-                       }
-                       sb.append(upperCase ? part.toUpperCase() : part);
-               }
-               return sb.toString();
-       }
-
-       public String toStringCaml(boolean firstCharUpperCase) {
-               StringBuilder sb = null;
-               for (String part : parts) {
-                       if (sb == null) {// first
-                               sb = new StringBuilder();
-                               sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
-                       } else {
-                               sb.append(Character.toUpperCase(part.charAt(0)));
-                       }
-
-                       if (part.length() > 1)
-                               sb.append(part.substring(1));
-               }
-               return sb.toString();
-       }
-
-       @Override
-       public int hashCode() {
-               return hashCode;
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj == null || !(obj instanceof CompositeString))
-                       return false;
-
-               CompositeString other = (CompositeString) obj;
-               return Arrays.equals(parts, other.parts);
-       }
-
-       @Override
-       public String toString() {
-               return toString(DASH, false);
-       }
-
-       public static String[] toParts(String str) {
-               Character separator = null;
-               if (str.indexOf(UNDERSCORE) >= 0) {
-                       checkNo(str, SPACE);
-                       checkNo(str, DASH);
-                       separator = UNDERSCORE;
-               } else if (str.indexOf(DASH) >= 0) {
-                       checkNo(str, SPACE);
-                       checkNo(str, UNDERSCORE);
-                       separator = DASH;
-               } else if (str.indexOf(SPACE) >= 0) {
-                       checkNo(str, DASH);
-                       checkNo(str, UNDERSCORE);
-                       separator = SPACE;
-               }
-
-               List<String> res = new ArrayList<>();
-               if (separator != null) {
-                       StringTokenizer st = new StringTokenizer(str, separator.toString());
-                       while (st.hasMoreTokens()) {
-                               res.add(st.nextToken().toLowerCase());
-                       }
-               } else {
-                       // single
-                       String strLowerCase = str.toLowerCase();
-                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
-                               return new String[] { strLowerCase };
-
-                       // CAML
-                       StringBuilder current = null;
-                       for (char c : str.toCharArray()) {
-                               if (Character.isUpperCase(c)) {
-                                       if (current != null)
-                                               res.add(current.toString());
-                                       current = new StringBuilder();
-                               }
-                               if (current == null)// first char is lower case
-                                       current = new StringBuilder();
-                               current.append(Character.toLowerCase(c));
-                       }
-                       res.add(current.toString());
-               }
-               return res.toArray(new String[res.size()]);
-       }
-
-       private static void checkNo(String str, Character c) {
-               if (str.indexOf(c) >= 0) {
-                       throw new IllegalArgumentException("Only one kind of sperator is allowed");
-               }
-       }
-
-       private static int hashCode(String[] parts) {
-               int hashCode = 0;
-               for (String part : parts) {
-                       hashCode = hashCode + part.hashCode();
-               }
-               return hashCode;
-       }
-
-       static boolean smokeTests() {
-               CompositeString plainName = new CompositeString("NAME");
-               assert "name".equals(plainName.toString());
-               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
-               assert "name".equals(plainName.toString(UNDERSCORE, false));
-               assert "name".equals(plainName.toStringCaml(false));
-               assert "Name".equals(plainName.toStringCaml(true));
-
-               CompositeString camlName = new CompositeString("myComplexName");
-
-               assert new CompositeString("my-complex-name").equals(camlName);
-               assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
-               assert new CompositeString("My complex Name").equals(camlName);
-               assert new CompositeString("MyComplexName").equals(camlName);
-
-               assert "my-complex-name".equals(camlName.toString());
-               assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
-               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
-               assert "myComplexName".equals(camlName.toStringCaml(false));
-               assert "MyComplexName".equals(camlName.toStringCaml(true));
-
-               return CompositeString.class.desiredAssertionStatus();
-       }
-
-       public static void main(String[] args) {
-               System.out.println(smokeTests());
-       }
-}
index edcfaea10de9199dd9b6ffb128216766578c60c2..f52ab31b8f25223e109b4781707502f924cd5ebd 100644 (file)
@@ -1,10 +1,15 @@
 package org.argeo.api.acr;
 
+import static org.argeo.api.acr.NamespaceUtils.unqualified;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
 
-import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 
 /**
@@ -24,30 +29,50 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
 
        <A> Optional<A> get(QName key, Class<A> clss);
 
-       default Object get(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> List<A> getMultiple(QName key, Class<A> clss);
+
+       /*
+        * ATTRIBUTES OPERATION HELPERS
+        */
+       default boolean containsKey(QNamed key) {
+               return containsKey(key.qName());
        }
 
-       default Object put(String key, Object value) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+       default <A> Optional<A> get(QNamed key, Class<A> clss) {
+               return get(key.qName(), clss);
        }
 
-       default Object remove(String key) {
-               if (key.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + key + " has a prefix");
-               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       default Object get(QNamed key) {
+               return get(key.qName());
        }
 
-       Class<?> getType(QName key);
+       default Object put(QNamed key, Object value) {
+               return put(key.qName(), value);
+       }
 
-       boolean isMultiple(QName key);
+       default Object remove(QNamed key) {
+               return remove(key.qName());
+       }
+
+       // TODO do we really need the helpers below?
+
+       default Object get(String key) {
+               return get(unqualified(key));
+       }
+
+       default Object put(String key, Object value) {
+               return put(unqualified(key), value);
+       }
 
-       <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+       default Object remove(String key) {
+               return remove(unqualified(key));
+       }
 
+       @SuppressWarnings("unchecked")
        default <A> List<A> getMultiple(QName key) {
                Class<A> type;
                try {
@@ -55,46 +80,183 @@ public interface Content extends Iterable<Content>, Map<QName, Object> {
                } catch (ClassCastException e) {
                        throw new IllegalArgumentException("Requested type is not the default type");
                }
-               Optional<List<A>> res = getMultiple(key, type);
-               if (res == null)
-                       return null;
-               else {
-                       if (res.isEmpty())
-                               throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
-                       return res.get();
-               }
+               List<A> res = getMultiple(key, type);
+               return res;
+//             if (res == null)
+//                     return null;
+//             else {
+//                     if (res.isEmpty())
+//                             throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+//                     return res.get();
+//             }
        }
 
        /*
         * CONTENT OPERATIONS
         */
+//     default CompletionStage<Content> edit(Consumer<Content> work) {
+//             return CompletableFuture.supplyAsync(() -> {
+//                     work.accept(this);
+//                     return this;
+//             }).minimalCompletionStage();
+//     }
+
        Content add(QName name, QName... classes);
 
        default Content add(String name, QName... classes) {
-               if (name.indexOf(':') >= 0)
-                       throw new IllegalArgumentException("Name " + name + " has a prefix");
-               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+               return add(unqualified(name), classes);
        }
 
        void remove();
 
+       /*
+        * TYPING
+        */
+       List<QName> getContentClasses();
+
+       default void addContentClasses(QName... contentClass) {
+               throw new UnsupportedOperationException("Adding content classes to " + getPath() + " is not supported");
+       }
+
+       /** AND */
+       default boolean isContentClass(QName... contentClass) {
+               List<QName> contentClasses = getContentClasses();
+               for (QName cClass : contentClass) {
+                       if (!contentClasses.contains(cClass))
+                               return false;
+               }
+               return true;
+       }
+
+       /** AND */
+       default boolean isContentClass(QNamed... contentClass) {
+               List<QName> lst = new ArrayList<>();
+               for (QNamed qNamed : contentClass)
+                       lst.add(qNamed.qName());
+               return isContentClass(lst.toArray(new QName[lst.size()]));
+       }
+
+       /** OR */
+       default boolean hasContentClass(QName... contentClass) {
+               List<QName> contentClasses = getContentClasses();
+               for (QName cClass : contentClass) {
+                       if (contentClasses.contains(cClass))
+                               return true;
+               }
+               return false;
+       }
+
+       /** OR */
+       default boolean hasContentClass(QNamed... contentClass) {
+               List<QName> lst = new ArrayList<>();
+               for (QNamed qNamed : contentClass)
+                       lst.add(qNamed.qName());
+               return hasContentClass(lst.toArray(new QName[lst.size()]));
+       }
+
+       /*
+        * SIBLINGS
+        */
+
+       default int getSiblingIndex() {
+               return 1;
+       }
+
        /*
         * DEFAULT METHODS
         */
-       default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+       default <A> A adapt(Class<A> clss) {
+               throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
+       }
+
+       default <C extends Closeable> C open(Class<C> clss) throws IOException {
+               throw new UnsupportedOperationException("Cannot open content " + this + " as " + clss.getName());
        }
 
-       default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
-               throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+       default <A> CompletableFuture<A> write(Class<A> clss) {
+               throw new UnsupportedOperationException("Cannot write content " + this + " as " + clss.getName());
        }
 
        /*
-        * CONVENIENCE METHODS
+        * CHILDREN
         */
-//     default String attr(String key) {
-//             return get(key, String.class);
-//     }
+
+       default boolean hasChild(QName name) {
+               for (Content child : this) {
+                       if (child.getName().equals(name))
+                               return true;
+               }
+               return false;
+       }
+
+       default boolean hasChild(QNamed name) {
+               return hasChild(name.qName());
+       }
+
+       default Content anyOrAddChild(QName name, QName... classes) {
+               Content child = anyChild(name);
+               if (child != null)
+                       return child;
+               return this.add(name, classes);
+       }
+
+       default Content anyOrAddChild(String name, QName... classes) {
+               return anyOrAddChild(unqualified(name), classes);
+       }
+
+       /** Any child with this name, or null if there is none */
+       default Content anyChild(QName name) {
+               for (Content child : this) {
+                       if (child.getName().equals(name))
+                               return child;
+               }
+               return null;
+       }
+
+       default List<Content> children(QName name) {
+               List<Content> res = new ArrayList<>();
+               for (Content child : this) {
+                       if (child.getName().equals(name))
+                               res.add(child);
+               }
+               return res;
+       }
+
+       default Optional<Content> soleChild(QName name) {
+               List<Content> res = children(name);
+               if (res.isEmpty())
+                       return Optional.empty();
+               if (res.size() > 1)
+                       throw new IllegalStateException(this + " has multiple children with name " + name);
+               return Optional.of(res.get(0));
+       }
+
+       default Content child(QName name) {
+               return soleChild(name).orElseThrow();
+       }
+
+       default Content child(QNamed name) {
+               return child(name.qName());
+       }
+
+       /*
+        * ATTR AS STRING
+        */
+       default String attr(QName key) {
+               // TODO check String type?
+               Object obj = get(key);
+               if (obj == null)
+                       return null;
+               return obj.toString();
+       }
+
+       default String attr(QNamed key) {
+               return attr(key.qName());
+       }
+
+       default String attr(String key) {
+               return attr(unqualified(key));
+       }
 //
 //     default String attr(Object key) {
 //             return key != null ? attr(key.toString()) : attr(null);
index 341a3e29710a7d482c2ac46bde2bf0181bdb1965..113f98da0d6350d07279d261e2a99b443b25541a 100644 (file)
@@ -24,7 +24,7 @@ public class ContentName extends QName {
         * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
         * namespace, to be used as a base for the namespaces.
         * 
-        * @see https://www.w3.org/TR/xml-names/#ns-decl
+        * @see "https://www.w3.org/TR/xml-names/#ns-decl"
         */
        // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
        // NOTE : must be declared before default namespaces
@@ -41,6 +41,10 @@ public class ContentName extends QName {
 
 //     private final UUID uuid;
 
+       public ContentName(String namespaceURI, String localPart) {
+               super(namespaceURI, localPart, checkPrefix(RuntimeNamespaceContext.getNamespaceContext(), namespaceURI));
+       }
+
        public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
                super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
        }
@@ -48,7 +52,7 @@ public class ContentName extends QName {
        private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
                Objects.requireNonNull(nsContext, "Namespace context cannot be null");
                Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
-               String prefix = nsContext.getNamespaceURI(namespaceURI);
+               String prefix = nsContext.getPrefix(namespaceURI);
                if (prefix == null)
                        throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
                return prefix;
@@ -80,7 +84,7 @@ public class ContentName extends QName {
 
        @Override
        public String toString() {
-               return toPrefixedString();
+               return toQNameString();
        }
 
        @Override
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java
deleted file mode 100644 (file)
index e3c721f..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.api.acr;
-
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.function.Supplier;
-
-import javax.xml.XMLConstants;
-import javax.xml.namespace.NamespaceContext;
-
-public interface ContentNameSupplier extends Supplier<ContentName>, NamespaceContext {
-       String name();
-
-       String getNamespaceURI();
-
-       String getDefaultPrefix();
-
-       @Override
-       default ContentName get() {
-               return toContentName();
-       }
-
-       default ContentName toContentName() {
-               CompositeString cs = new CompositeString(name());
-               String camlName = cs.toStringCaml(false);
-               return new ContentName(getNamespaceURI(), camlName, this);
-       }
-
-//     default String getNamespaceURI() {
-//             return XMLConstants.NULL_NS_URI;
-//     }
-//
-//     default String getDefaultPrefix() {
-//             return XMLConstants.DEFAULT_NS_PREFIX;
-//     }
-
-//     static ContentName toContentName(String namespaceURI, String localName, String prefix) {
-//             CompositeString cs = new CompositeString(localName);
-//             String camlName = cs.toStringCaml(false);
-//             return new ContentName(namespaceURI, camlName, this);
-//     }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-
-       @Override
-       default String getNamespaceURI(String prefix) {
-               String namespaceURI = getStandardNamespaceURI(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               if (prefix.equals(getDefaultPrefix()))
-                       return getNamespaceURI();
-               return XMLConstants.NULL_NS_URI;
-       }
-
-       @Override
-       default String getPrefix(String namespaceURI) {
-               String prefix = getStandardPrefix(namespaceURI);
-               if (prefix != null)
-                       return prefix;
-               if (namespaceURI.equals(getNamespaceURI()))
-                       return getDefaultPrefix();
-               return null;
-       }
-
-       @Override
-       default Iterator<String> getPrefixes(String namespaceURI) {
-               Iterator<String> it = getStandardPrefixes(namespaceURI);
-               if (it != null)
-                       return it;
-               if (namespaceURI.equals(getNamespaceURI()))
-                       return Collections.singleton(getDefaultPrefix()).iterator();
-               return Collections.emptyIterator();
-       }
-
-       /*
-        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
-        */
-       static String getStandardPrefix(String namespaceURI) {
-               if (namespaceURI == null)
-                       throw new IllegalArgumentException("Namespace URI cannot be null");
-               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
-                       return XMLConstants.XML_NS_PREFIX;
-               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
-                       return XMLConstants.XMLNS_ATTRIBUTE;
-               return null;
-       }
-
-       static Iterator<String> getStandardPrefixes(String namespaceURI) {
-               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
-               if (prefix == null)
-                       return null;
-               return Collections.singleton(prefix).iterator();
-       }
-
-       static String getStandardNamespaceURI(String prefix) {
-               if (prefix == null)
-                       throw new IllegalArgumentException("Prefix cannot be null");
-               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
-                       return XMLConstants.XML_NS_URI;
-               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
-                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
-               return null;
-       }
-
-}
index b86c92c1ed300b1b80ae4217003400c2a7a886de..51efb3860a49d914b22e090be7750a10804ee91c 100644 (file)
@@ -1,16 +1,35 @@
 package org.argeo.api.acr;
 
-/** When a countent was requested which does not exists, equivalent to HTTP code 404.*/
+/**
+ * When a content was requested which does not exists, equivalent to HTTP code
+ * 404.
+ */
 public class ContentNotFoundException extends RuntimeException {
        private static final long serialVersionUID = -8629074900713760886L;
 
-       public ContentNotFoundException(String message, Throwable cause) {
-               super(message, cause);
+       private final String path;
+
+       public ContentNotFoundException(ContentSession session, String path, Throwable cause) {
+               super(message(session, path), cause);
+               this.path = path;
+               // we don't keep reference to the session for security reasons
+       }
+
+       public ContentNotFoundException(ContentSession session, String path) {
+               this(session, path, (String) null);
        }
 
-       public ContentNotFoundException(String message) {
-               super(message);
+       public ContentNotFoundException(ContentSession session, String path, String message) {
+               super(message != null ? message : message(session, path));
+               this.path = path;
+               // we don't keep reference to the session for security reasons
        }
 
-       
+       private static String message(ContentSession session, String path) {
+               return "Content " + path + "cannot be found.";
+       }
+
+       public String getPath() {
+               return path;
+       }
 }
index 215bb9e22f8a70d577673b3601003068364e3273..b7d37dc10a876d38a14bad895c9e0e0a4d3280fc 100644 (file)
@@ -1,12 +1,11 @@
 package org.argeo.api.acr;
 
 import java.util.Locale;
-import java.util.Objects;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
 
 import javax.security.auth.Subject;
-import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
-import javax.xml.namespace.QName;
 
 public interface ContentSession extends NamespaceContext {
        Subject getSubject();
@@ -14,36 +13,8 @@ public interface ContentSession extends NamespaceContext {
        Locale getLocale();
 
        Content get(String path);
+       
+       boolean exists(String path);
 
-       /*
-        * NAMESPACE CONTEXT
-        */
-
-       default ContentName parsePrefixedName(String nameWithPrefix) {
-               Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
-               if (nameWithPrefix.charAt(0) == '{') {
-                       return new ContentName(QName.valueOf(nameWithPrefix), this);
-               }
-               int index = nameWithPrefix.indexOf(':');
-               if (index < 0) {
-                       return new ContentName(nameWithPrefix);
-               }
-               String prefix = nameWithPrefix.substring(0, index);
-               // TODO deal with empty name?
-               String localName = nameWithPrefix.substring(index + 1);
-               String namespaceURI = getNamespaceURI(prefix);
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       throw new IllegalStateException("Prefix " + prefix + " is unbound.");
-               return new ContentName(namespaceURI, localName, prefix);
-       }
-
-       default String toPrefixedName(QName name) {
-               if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
-                       return name.getLocalPart();
-               String prefix = getPrefix(name.getNamespaceURI());
-               if (prefix == null)
-                       throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
-               return prefix + ":" + name.getLocalPart();
-       }
-
+       CompletionStage<ContentSession> edit(Consumer<ContentSession> work);
 }
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java
deleted file mode 100644 (file)
index 2036c86..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.api.acr;
-
-import java.io.PrintStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.List;
-import java.util.function.BiConsumer;
-
-import javax.xml.namespace.QName;
-
-public class ContentUtils {
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
-               traverse(content, doIt, 0);
-       }
-
-       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
-               doIt.accept(content, currentDepth);
-               int nextDepth = currentDepth + 1;
-               for (Content child : content) {
-                       traverse(child, doIt, nextDepth);
-               }
-       }
-
-       public static void print(Content content, PrintStream out, int depth, boolean printText) {
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < depth; i++) {
-                       sb.append("  ");
-               }
-               String prefix = sb.toString();
-               out.println(prefix + content.getName());
-               for (QName key : content.keySet()) {
-                       out.println(prefix + " " + key + "=" + content.get(key));
-               }
-               if (printText) {
-                       if (content.hasText()) {
-                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
-                       }
-               }
-       }
-
-       public static URI bytesToDataURI(byte[] arr) {
-               String base64Str = Base64.getEncoder().encodeToString(arr);
-               try {
-                       final String PREFIX = "data:application/octet-stream;base64,";
-                       return new URI(PREFIX + base64Str);
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
-               }
-
-       }
-
-       public static byte[] bytesFromDataURI(URI uri) {
-               if (!"data".equals(uri.getScheme()))
-                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
-               String schemeSpecificPart = uri.getSchemeSpecificPart();
-               int commaIndex = schemeSpecificPart.indexOf(',');
-               String prefix = schemeSpecificPart.substring(0, commaIndex);
-               List<String> info = Arrays.asList(prefix.split(";"));
-               if (!info.contains("base64"))
-                       throw new IllegalArgumentException("URI must specify base64");
-
-               String base64Str = schemeSpecificPart.substring(commaIndex + 1);
-               return Base64.getDecoder().decode(base64Str);
-
-       }
-
-       public static <T> boolean isString(T t) {
-               return t instanceof String;
-       }
-
-       /** Singleton. */
-       private ContentUtils() {
-
-       }
-}
index ffa28af0aca580a6d358030d9f1e4c038f1aa179..3e12fb1c87067fd188bfa34cac4c44794c9ec5d2 100644 (file)
@@ -1,37 +1,52 @@
 package org.argeo.api.acr;
 
+import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
+
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.time.Instant;
 import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.UUID;
 
-import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
 
 /**
  * Minimal standard attribute types that MUST be supported. All related classes
  * belong to java.base and can be implicitly derived form a given
- * <code>String<code>.
+ * <code>String</code>.
  */
-public enum CrAttributeType implements ContentNameSupplier {
-       BOOLEAN(Boolean.class, new BooleanFormatter()), //
-       INTEGER(Integer.class, new IntegerFormatter()), //
-       LONG(Long.class, new LongFormatter()), //
-       DOUBLE(Double.class, new DoubleFormatter()), //
+public enum CrAttributeType {
+       BOOLEAN(Boolean.class, W3C_XML_SCHEMA_NS_URI, "boolean", new BooleanFormatter()), //
+       INTEGER(Integer.class, W3C_XML_SCHEMA_NS_URI, "integer", new IntegerFormatter()), //
+       LONG(Long.class, W3C_XML_SCHEMA_NS_URI, "long", new LongFormatter()), //
+       DOUBLE(Double.class, W3C_XML_SCHEMA_NS_URI, "double", new DoubleFormatter()), //
        // we do not support short and float, like recent additions to Java
        // (e.g. optional primitives)
-       DATE_TIME(Instant.class, new InstantFormatter()), //
-       UUID(UUID.class, new UuidFormatter()), //
-       ANY_URI(URI.class, new UriFormatter()), //
-       STRING(String.class, new StringFormatter()), //
+       DATE_TIME(Instant.class, W3C_XML_SCHEMA_NS_URI, "dateTime", new InstantFormatter()), //
+       UUID(UUID.class, ArgeoNamespace.CR_NAMESPACE_URI, "uuid", new UuidFormatter()), //
+       ANY_URI(URI.class, W3C_XML_SCHEMA_NS_URI, "anyUri", new UriFormatter()), //
+       STRING(String.class, W3C_XML_SCHEMA_NS_URI, "string", new StringFormatter()), //
        ;
 
        private final Class<?> clss;
        private final AttributeFormatter<?> formatter;
 
-       private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+       private final ContentName qName;
+
+       private <T> CrAttributeType(Class<T> clss, String namespaceUri, String localName, AttributeFormatter<T> formatter) {
                this.clss = clss;
                this.formatter = formatter;
+
+               qName = new ContentName(namespaceUri, localName, RuntimeNamespaceContext.getNamespaceContext());
+       }
+
+       public QName getqName() {
+               return qName;
        }
 
        public Class<?> getClss() {
@@ -42,22 +57,23 @@ public enum CrAttributeType implements ContentNameSupplier {
                return formatter;
        }
 
-       @Override
-       public String getDefaultPrefix() {
-               if (equals(UUID))
-                       return CrName.CR_DEFAULT_PREFIX;
-               else
-                       return "xs";
-       }
-
-       @Override
-       public String getNamespaceURI() {
-               if (equals(UUID))
-                       return CrName.CR_NAMESPACE_URI;
-               else
-                       return XMLConstants.W3C_XML_SCHEMA_NS_URI;
-       }
+//     @Override
+//     public String getDefaultPrefix() {
+//             if (equals(UUID))
+//                     return CrName.CR_DEFAULT_PREFIX;
+//             else
+//                     return "xs";
+//     }
+//
+//     @Override
+//     public String getNamespaceURI() {
+//             if (equals(UUID))
+//                     return CrName.CR_NAMESPACE_URI;
+//             else
+//                     return XMLConstants.W3C_XML_SCHEMA_NS_URI;
+//     }
 
+       /** Default parsing procedure from a String to an object. */
        public static Object parse(String str) {
                if (str == null)
                        throw new IllegalArgumentException("String cannot be null");
@@ -109,10 +125,76 @@ public enum CrAttributeType implements ContentNameSupplier {
                        // silent
                }
 
+               // TODO support QName as a type? It would require a NamespaceContext
+               // see https://www.oreilly.com/library/view/xml-schema/0596002521/re91.html
+
                // default
                return STRING.getFormatter().parse(str);
        }
 
+       /**
+        * Cast well know java types based on {@link Object#toString()} of the provided
+        * object.
+        * 
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> Optional<T> cast(Class<T> clss, Object value) {
+               // TODO Or should we?
+               Objects.requireNonNull(value, "Cannot cast a null value");
+               if (String.class.isAssignableFrom(clss)) {
+                       return Optional.of((T) value.toString());
+               }
+               // Numbers
+               else if (Long.class.isAssignableFrom(clss)) {
+                       if (value instanceof Long)
+                               return Optional.of((T) value);
+                       return Optional.of((T) Long.valueOf(value.toString()));
+               } else if (Integer.class.isAssignableFrom(clss)) {
+                       if (value instanceof Integer)
+                               return Optional.of((T) value);
+                       return Optional.of((T) Integer.valueOf(value.toString()));
+               } else if (Double.class.isAssignableFrom(clss)) {
+                       if (value instanceof Double)
+                               return Optional.of((T) value);
+                       return Optional.of((T) Double.valueOf(value.toString()));
+               }
+               // Numbers
+//             else if (Number.class.isAssignableFrom(clss)) {
+//                     if (value instanceof Number)
+//                             return Optional.of((T) value);
+//                     return Optional.of((T) Number.valueOf(value.toString()));
+//             }
+               return Optional.empty();
+       }
+
+       /** Utility to convert a data: URI to bytes. */
+       public static byte[] bytesFromDataURI(URI uri) {
+               if (!"data".equals(uri.getScheme()))
+                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
+               String schemeSpecificPart = uri.getSchemeSpecificPart();
+               int commaIndex = schemeSpecificPart.indexOf(',');
+               String prefix = schemeSpecificPart.substring(0, commaIndex);
+               List<String> info = Arrays.asList(prefix.split(";"));
+               if (!info.contains("base64"))
+                       throw new IllegalArgumentException("URI must specify base64");
+
+               String base64Str = schemeSpecificPart.substring(commaIndex + 1);
+               return Base64.getDecoder().decode(base64Str);
+
+       }
+
+       /** Utility to convert bytes to a data: URI. */
+       public static URI bytesToDataURI(byte[] arr) {
+               String base64Str = Base64.getEncoder().encodeToString(arr);
+               try {
+                       final String PREFIX = "data:application/octet-stream;base64,";
+                       return new URI(PREFIX + base64Str);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+               }
+
+       }
+
        static class BooleanFormatter implements AttributeFormatter<Boolean> {
 
                /**
index 099be9fdad98a54378ba39d871fb588bddde09fd..ead47377bddff3eb5cd26d179636735d3f578bdc 100644 (file)
@@ -1,58 +1,59 @@
 package org.argeo.api.acr;
 
 /** Standard names. */
-public enum CrName implements ContentNameSupplier {
+public enum CrName implements QNamed {
 
        /*
         * TYPES
         */
-       COLLECTION, // a collection type
+//     collection, // a collection type
 
        /*
         * ATTRIBUTES
         */
-       UUID, // the UUID of a content
+       uuid, // the UUID of a content
+       mount, // a mount point
+//     cc, // content class
 
        /*
         * ATTRIBUTES FROM FILE SEMANTICS
         */
-       CREATION_TIME, //
-       LAST_MODIFIED_TIME, //
-       SIZE, //
-       FILE_KEY, //
-       OWNER, //
-       GROUP, //
-       PERMISSIONS, //
+//     creationTime, //
+//     lastModifiedTime, //
+//     size, //
+       fileKey, //
+//     owner, //
+//     group, //
+       permissions, //
 
        /*
         * CONTENT NAMES
         */
-       ROOT,
+       root,
 
        //
        ;
 
-       public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr";
-       public final static String CR_DEFAULT_PREFIX = "cr";
-       private final ContentName value;
+       
 
-       CrName() {
-               value = toContentName();
-       }
+//     private final ContentName value;
 
-       @Override
-       public ContentName get() {
-               return value;
-       }
+//     CrName() {
+//             value = new ContentName(CR_NAMESPACE_URI, name(), RuntimeNamespaceContext.getNamespaceContext());
+//     }
+//
+//     public QName qName() {
+//             return value;
+//     }
 
        @Override
-       public String getNamespaceURI() {
-               return CR_NAMESPACE_URI;
+       public String getNamespace() {
+               return ArgeoNamespace.CR_NAMESPACE_URI;
        }
 
        @Override
        public String getDefaultPrefix() {
-               return CR_DEFAULT_PREFIX;
+               return ArgeoNamespace.CR_DEFAULT_PREFIX;
        }
 
 }
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/DName.java b/org.argeo.api.acr/src/org/argeo/api/acr/DName.java
new file mode 100644 (file)
index 0000000..be065a8
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.api.acr;
+
+/**
+ * Name for core concepts with the same semantics as defined in the WebDav
+ * standard and extensions.
+ * 
+ * @see "http://www.webdav.org/specs/rfc4918.html"
+ * @see "http://www.webdav.org/specs/rfc3744.html"
+ */
+public enum DName implements QNamed
+
+{
+       // RFC4918 (WebDav) properties used as CR attr
+       creationdate, //
+       displayname, //
+       getcontentlanguage, //
+       getcontentlength, //
+       getcontenttype, //
+       getetag, //
+       getlastmodified, //
+       resourcetype, //
+
+       // RFC4918 (WebDav) value used as CR class
+       collection, //
+
+       // RFC3744 (ACL) properties uase as CR attr
+       owner, //
+       group, //
+       //
+       ;
+
+       public final static String WEBDAV_NAMESPACE_URI = "DAV:";
+       public final static String WEBDAV_DEFAULT_PREFIX = "D";
+
+       @Override
+       public String getNamespace() {
+               return WEBDAV_NAMESPACE_URI;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return WEBDAV_DEFAULT_PREFIX;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/NamespaceUtils.java
new file mode 100644 (file)
index 0000000..df58286
--- /dev/null
@@ -0,0 +1,145 @@
+package org.argeo.api.acr;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+public class NamespaceUtils {
+
+       public static ContentName parsePrefixedName(String nameWithPrefix) {
+               return parsePrefixedName(RuntimeNamespaceContext.getNamespaceContext(), nameWithPrefix);
+       }
+
+       public static ContentName parsePrefixedName(NamespaceContext nameSpaceContext, String nameWithPrefix) {
+               Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
+               if (nameWithPrefix.charAt(0) == '{') {
+                       return new ContentName(QName.valueOf(nameWithPrefix), nameSpaceContext);
+               }
+               int index = nameWithPrefix.indexOf(':');
+               if (index < 0) {
+                       return new ContentName(nameWithPrefix);
+               }
+               String prefix = nameWithPrefix.substring(0, index);
+               // TODO deal with empty name?
+               String localName = nameWithPrefix.substring(index + 1);
+               String namespaceURI = nameSpaceContext.getNamespaceURI(prefix);
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is unbound.");
+               return new ContentName(namespaceURI, localName, prefix);
+       }
+
+       public static String toPrefixedName(QName name) {
+               return toPrefixedName(RuntimeNamespaceContext.getNamespaceContext(), name);
+       }
+
+       public static String toPrefixedName(NamespaceContext nameSpaceContext, QName name) {
+               if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
+                       return name.getLocalPart();
+               String prefix = nameSpaceContext.getPrefix(name.getNamespaceURI());
+               if (prefix == null)
+                       throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
+               return prefix + ":" + name.getLocalPart();
+       }
+
+       public final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+
+               @Override
+               public int compare(QName qn1, QName qn2) {
+                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+                       } else {
+                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+                       }
+               }
+
+       };
+
+       public static boolean hasNamespace(QName qName) {
+               return !qName.getNamespaceURI().equals(XMLConstants.NULL_NS_URI);
+       }
+
+       public static void checkNoPrefix(String unqualified) {
+               if (unqualified.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + unqualified + " has a prefix");
+       }
+
+       public static QName unqualified(String name) {
+               checkNoPrefix(name);
+               return new ContentName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX);
+
+       }
+
+       /*
+        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
+        */
+       public static String getStandardPrefix(String namespaceURI) {
+               if (namespaceURI == null)
+                       throw new IllegalArgumentException("Namespace URI cannot be null");
+               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XML_NS_PREFIX;
+               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XMLNS_ATTRIBUTE;
+               return null;
+       }
+
+       public static Iterator<String> getStandardPrefixes(String namespaceURI) {
+               String prefix = getStandardPrefix(namespaceURI);
+               if (prefix == null)
+                       return null;
+               return Collections.singleton(prefix).iterator();
+       }
+
+       public static String getStandardNamespaceURI(String prefix) {
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix cannot be null");
+               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
+                       return XMLConstants.XML_NS_URI;
+               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
+                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+               return null;
+       }
+
+       public static String getNamespaceURI(Function<String, String> mapping, String prefix) {
+               String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
+                       return XMLConstants.NULL_NS_URI;
+               namespaceURI = mapping.apply(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       public static String getPrefix(Function<String, String> mapping, String namespaceURI) {
+               String prefix = NamespaceUtils.getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               return mapping.apply(namespaceURI);
+       }
+
+       public static Iterator<String> getPrefixes(Function<String, Set<String>> mapping, String namespaceURI) {
+               Iterator<String> standard = NamespaceUtils.getStandardPrefixes(namespaceURI);
+               if (standard != null)
+                       return standard;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
+               Set<String> prefixes = mapping.apply(namespaceURI);
+               assert prefixes != null;
+               return prefixes.iterator();
+       }
+
+       /** singleton */
+       private NamespaceUtils() {
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java b/org.argeo.api.acr/src/org/argeo/api/acr/QNamed.java
new file mode 100644 (file)
index 0000000..063a7d3
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.api.acr;
+
+import java.util.function.Supplier;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/** An optionally qualified name. Primarily meant to be used in enums. */
+public interface QNamed extends Supplier<String> {
+       String name();
+
+       /** To be overridden when XML naming is not compatible with Java naming. */
+       default String localName() {
+               return name();
+       }
+
+       default QName qName() {
+               return new ContentName(getNamespace(), localName(), getDefaultPrefix());
+       }
+
+       default String get(NamespaceContext namespaceContext) {
+               return namespaceContext.getPrefix(getNamespace()) + ":" + localName();
+       }
+
+       default String get() {
+               return getDefaultPrefix() + ":" + localName();
+       }
+
+       String getNamespace();
+
+       String getDefaultPrefix();
+
+       /** To be used by enums without namespace (typically XML attributes). */
+       static interface Unqualified extends QNamed {
+               @Override
+               default String getNamespace() {
+                       return XMLConstants.NULL_NS_URI;
+               }
+
+               @Override
+               default String getDefaultPrefix() {
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               }
+
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java
new file mode 100644 (file)
index 0000000..1c55156
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.api.acr;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+/**
+ * Programmatically defined {@link NamespaceContext}, code contributing
+ * namespaces MUST register here with a single default prefix.
+ */
+public class RuntimeNamespaceContext implements NamespaceContext {
+       public final static String XSD_DEFAULT_PREFIX = "xs";
+       public final static String XSD_INSTANCE_DEFAULT_PREFIX = "xsi";
+
+       private NavigableMap<String, String> prefixes = new TreeMap<>();
+       private NavigableMap<String, String> namespaces = new TreeMap<>();
+
+       @Override
+       public String getPrefix(String namespaceURI) {
+               return NamespaceUtils.getPrefix((ns) -> {
+                       String prefix = namespaces.get(ns);
+                       if (prefix == null)
+                               throw new IllegalStateException("Namespace " + ns + " is not registered.");
+                       return prefix;
+               }, namespaceURI);
+       }
+
+       @Override
+       public String getNamespaceURI(String prefix) {
+               return NamespaceUtils.getNamespaceURI((p) -> {
+                       String ns = prefixes.get(p);
+                       if (ns == null)
+                               throw new IllegalStateException("Prefix " + p + " is not registered.");
+                       return ns;
+               }, prefix);
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               return Collections.singleton(getPrefix(namespaceURI)).iterator();
+       }
+
+       /*
+        * STATIC
+        */
+
+       private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext();
+
+       static {
+               // Standard
+               register(XMLConstants.XML_NS_URI, XMLConstants.XML_NS_PREFIX);
+               register(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE);
+
+               // Common
+               register(XMLConstants.W3C_XML_SCHEMA_NS_URI, XSD_DEFAULT_PREFIX);
+               register(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, XSD_INSTANCE_DEFAULT_PREFIX);
+
+               // Argeo specific
+               register(ArgeoNamespace.CR_NAMESPACE_URI, ArgeoNamespace.CR_DEFAULT_PREFIX);
+               register(ArgeoNamespace.LDAP_NAMESPACE_URI, ArgeoNamespace.LDAP_DEFAULT_PREFIX);
+               register(ArgeoNamespace.ROLE_NAMESPACE_URI, ArgeoNamespace.ROLE_DEFAULT_PREFIX);
+       }
+
+       public static NamespaceContext getNamespaceContext() {
+               return INSTANCE;
+       }
+
+       public static Map<String, String> getPrefixes() {
+               return Collections.unmodifiableNavigableMap(INSTANCE.prefixes);
+       }
+
+       public synchronized static void register(String namespaceURI, String prefix) {
+               NavigableMap<String, String> prefixes = INSTANCE.prefixes;
+               NavigableMap<String, String> namespaces = INSTANCE.namespaces;
+               if (prefixes.containsKey(prefix)) {
+                       String ns = prefixes.get(prefix);
+                       if (ns.equals(namespaceURI))
+                               return; // ignore silently
+                       throw new IllegalStateException("Prefix " + prefix + " is already registered with namespace URI " + ns);
+               }
+               if (namespaces.containsKey(namespaceURI)) {
+                       String p = namespaces.get(namespaceURI);
+                       if (p.equals(prefix))
+                               return; // ignore silently
+                       throw new IllegalStateException("Namespace " + namespaceURI + " is already registered with prefix " + p);
+               }
+               prefixes.put(prefix, namespaceURI);
+               namespaces.put(namespaceURI, prefix);
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/Distinguished.java
new file mode 100644 (file)
index 0000000..52556cf
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.api.acr.ldap;
+
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/**
+ * An object that can be identified with an X.500 distinguished name.
+ * 
+ * @see "https://tools.ietf.org/html/rfc1779"
+ */
+public interface Distinguished {
+       /** The related distinguished name. */
+       String dn();
+
+       /** The related distinguished name as an {@link LdapName}. */
+       default LdapName distinguishedName() {
+               try {
+                       return new LdapName(dn());
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
+               }
+       }
+
+       /** List all DNs of an enumeration as strings. */
+       static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
+               Set<String> res = new TreeSet<>();
+               for (Enum<? extends Distinguished> enm : enumSet) {
+                       res.add(((Distinguished) enm).dn());
+               }
+               return res;
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAcrUtils.java
new file mode 100644 (file)
index 0000000..7ef64c2
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.api.acr.ldap;
+
+import java.util.Locale;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+
+/** Utilities around ACR and LDAP conventions. */
+public class LdapAcrUtils {
+
+       /** singleton */
+       private LdapAcrUtils() {
+       }
+
+       public static Object getLocalized(Content content, QName key, Locale locale) {
+               if (locale == null)
+                       throw new IllegalArgumentException("A locale must be specified");
+               Object value = null;
+               if (locale.getCountry() != null && !locale.getCountry().equals(""))
+                       value = content.get(new ContentName(key.getNamespaceURI(),
+                                       key.getLocalPart() + ";lang-" + locale.getLanguage() + "-" + locale.getCountry()));
+               if (value == null)
+                       value = content
+                                       .get(new ContentName(key.getNamespaceURI(), key.getLocalPart() + ";lang-" + locale.getLanguage()));
+               if (value == null)
+                       value = content.get(key);
+               return value;
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapAttr.java
new file mode 100644 (file)
index 0000000..81b36ec
--- /dev/null
@@ -0,0 +1,368 @@
+package org.argeo.api.acr.ldap;
+
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX;
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.QNamed;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+
+/**
+ * Standard LDAP attributes as per:<br>
+ * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
+ * - <a href=
+ * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
+ * LDAP (partial)</a>
+ */
+public enum LdapAttr implements QNamed, SpecifiedName {
+       /** */
+       uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
+       /** */
+       mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
+       /** */
+       info("0.9.2342.19200300.100.1.4", "RFC 4524"),
+       /** */
+       drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
+       /** */
+       roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
+       /** */
+       photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
+       /** */
+       userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
+       /** */
+       host("0.9.2342.19200300.100.1.9", "RFC 4524"),
+       /** */
+       manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
+       /** */
+       documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
+       /** */
+       documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
+       /** */
+       documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
+       /** */
+       documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
+       /** */
+       documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
+       /** */
+       homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
+       /** */
+       secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
+       /** */
+       dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
+       /** */
+       associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
+       /** */
+       associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
+       /** */
+       homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
+       /** */
+       personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
+       /** */
+       mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
+       /** */
+       pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
+       /** */
+       co("0.9.2342.19200300.100.1.43", "RFC 4524"),
+       /** */
+       uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
+       /** */
+       organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
+       /** */
+       buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
+       /** */
+       audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
+       /** */
+       documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
+       /** */
+       jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
+       /** */
+       vendorName("1.3.6.1.1.4", "RFC 3045"),
+       /** */
+       vendorVersion("1.3.6.1.1.5", "RFC 3045"),
+       /** */
+       entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
+       /** */
+       entryDN("1.3.6.1.1.20", "RFC 5020"),
+       /** */
+       labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
+       /** */
+       numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
+       /** */
+       namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
+       /** */
+       altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
+       /** */
+       supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
+       /** */
+       supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
+       /** */
+       supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
+       /** */
+       supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
+       /** */
+       ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
+       /** */
+       supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
+       /** */
+       authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
+       /** */
+       supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
+       /** */
+       inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
+       /** */
+       blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
+       /** */
+       objectClass("2.5.4.0", "RFC 4512"),
+       /** */
+       aliasedObjectName("2.5.4.1", "RFC 4512"),
+       /** */
+       cn("2.5.4.3", "RFC 4519"),
+       /** */
+       sn("2.5.4.4", "RFC 4519"),
+       /** */
+       serialNumber("2.5.4.5", "RFC 4519"),
+       /** */
+       c("2.5.4.6", "RFC 4519"),
+       /** */
+       l("2.5.4.7", "RFC 4519"),
+       /** */
+       st("2.5.4.8", "RFC 4519"),
+       /** */
+       street("2.5.4.9", "RFC 4519"),
+       /** */
+       o("2.5.4.10", "RFC 4519"),
+       /** */
+       ou("2.5.4.11", "RFC 4519"),
+       /** */
+       title("2.5.4.12", "RFC 4519"),
+       /** */
+       description("2.5.4.13", "RFC 4519"),
+       /** */
+       searchGuide("2.5.4.14", "RFC 4519"),
+       /** */
+       businessCategory("2.5.4.15", "RFC 4519"),
+       /** */
+       postalAddress("2.5.4.16", "RFC 4519"),
+       /** */
+       postalCode("2.5.4.17", "RFC 4519"),
+       /** */
+       postOfficeBox("2.5.4.18", "RFC 4519"),
+       /** */
+       physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
+       /** */
+       telephoneNumber("2.5.4.20", "RFC 4519"),
+       /** */
+       telexNumber("2.5.4.21", "RFC 4519"),
+       /** */
+       teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
+       /** */
+       facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
+       /** */
+       x121Address("2.5.4.24", "RFC 4519"),
+       /** */
+       internationalISDNNumber("2.5.4.25", "RFC 4519"),
+       /** */
+       registeredAddress("2.5.4.26", "RFC 4519"),
+       /** */
+       destinationIndicator("2.5.4.27", "RFC 4519"),
+       /** */
+       preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
+       /** */
+       member("2.5.4.31", "RFC 4519"),
+       /** */
+       owner("2.5.4.32", "RFC 4519"),
+       /** */
+       roleOccupant("2.5.4.33", "RFC 4519"),
+       /** */
+       seeAlso("2.5.4.34", "RFC 4519"),
+       /** */
+       userPassword("2.5.4.35", "RFC 4519"),
+       /** */
+       userCertificate("2.5.4.36", "RFC 4523"),
+       /** */
+       cACertificate("2.5.4.37", "RFC 4523"),
+       /** */
+       authorityRevocationList("2.5.4.38", "RFC 4523"),
+       /** */
+       certificateRevocationList("2.5.4.39", "RFC 4523"),
+       /** */
+       crossCertificatePair("2.5.4.40", "RFC 4523"),
+       /** */
+       name("2.5.4.41", "RFC 4519"),
+       /** */
+       givenName("2.5.4.42", "RFC 4519"),
+       /** */
+       initials("2.5.4.43", "RFC 4519"),
+       /** */
+       generationQualifier("2.5.4.44", "RFC 4519"),
+       /** */
+       x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
+       /** */
+       dnQualifier("2.5.4.46", "RFC 4519"),
+       /** */
+       enhancedSearchGuide("2.5.4.47", "RFC 4519"),
+       /** */
+       distinguishedName("2.5.4.49", "RFC 4519"),
+       /** */
+       uniqueMember("2.5.4.50", "RFC 4519"),
+       /** */
+       houseIdentifier("2.5.4.51", "RFC 4519"),
+       /** */
+       supportedAlgorithms("2.5.4.52", "RFC 4523"),
+       /** */
+       deltaRevocationList("2.5.4.53", "RFC 4523"),
+       /** */
+       createTimestamp("2.5.18.1", "RFC 4512"),
+       /** */
+       modifyTimestamp("2.5.18.2", "RFC 4512"),
+       /** */
+       creatorsName("2.5.18.3", "RFC 4512"),
+       /** */
+       modifiersName("2.5.18.4", "RFC 4512"),
+       /** */
+       subschemaSubentry("2.5.18.10", "RFC 4512"),
+       /** */
+       dITStructureRules("2.5.21.1", "RFC 4512"),
+       /** */
+       dITContentRules("2.5.21.2", "RFC 4512"),
+       /** */
+       matchingRules("2.5.21.4", "RFC 4512"),
+       /** */
+       attributeTypes("2.5.21.5", "RFC 4512"),
+       /** */
+       objectClasses("2.5.21.6", "RFC 4512"),
+       /** */
+       nameForms("2.5.21.7", "RFC 4512"),
+       /** */
+       matchingRuleUse("2.5.21.8", "RFC 4512"),
+       /** */
+       structuralObjectClass("2.5.21.9", "RFC 4512"),
+       /** */
+       governingStructureRule("2.5.21.10", "RFC 4512"),
+       /** */
+       carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
+       /** */
+       departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
+       /** */
+       employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
+       /** */
+       employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
+       /** */
+       changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
+       /** */
+       targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
+       /** */
+       changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
+       /** */
+       changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
+       /** */
+       newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
+       /** */
+       deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
+       /** */
+       newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
+       /** */
+       ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
+       /** */
+       changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
+       /** */
+       preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
+       /** */
+       userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
+       /** */
+       userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
+       /** */
+       displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
+
+       // Sun memberOf
+       memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
+
+       // KERBEROS (partial)
+       krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
+
+       // RFC 2985 and RFC 3039 (partial)
+       dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
+       /** */
+       placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
+       /** */
+       gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
+       /** */
+       countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
+       /** */
+       countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
+
+       // RFC 2307bis (partial)
+       /** */
+       uidNumber("1.3.6.1.1.1.1.0", "RFC 2307bis"),
+       /** */
+       gidNumber("1.3.6.1.1.1.1.1", "RFC 2307bis"),
+       /** */
+       homeDirectory("1.3.6.1.1.1.1.3", "RFC 2307bis"),
+       /** */
+       loginShell("1.3.6.1.1.1.1.4", "RFC 2307bis"),
+       /** */
+       memberUid("1.3.6.1.1.1.1.12", "RFC 2307bis"),
+
+       //
+       ;
+
+       public final static String DN = "dn";
+
+       private final String oid, spec;
+       private final QName value;
+
+       LdapAttr(String oid, String spec) {
+               this.oid = oid;
+               this.spec = spec;
+               this.value = new ContentName(LDAP_NAMESPACE_URI, name());
+       }
+
+       public QName qName() {
+               return value;
+       }
+
+       @Override
+       public String getID() {
+               return oid;
+       }
+
+       @Override
+       public String getSpec() {
+               return spec;
+       }
+
+       @Deprecated
+       public String property() {
+               return get();
+       }
+
+       @Deprecated
+       public String qualified() {
+               return get();
+       }
+
+       /** #deprecated use {@link #qName()} instead. */
+//     @Deprecated
+       public String get() {
+               return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name();
+       }
+
+       @Override
+       public final String toString() {
+               // must return the name
+               return name();
+       }
+
+       @Override
+       public String getNamespace() {
+               return LDAP_NAMESPACE_URI;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return LDAP_DEFAULT_PREFIX;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapObj.java
new file mode 100644 (file)
index 0000000..061d117
--- /dev/null
@@ -0,0 +1,155 @@
+package org.argeo.api.acr.ldap;
+
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_DEFAULT_PREFIX;
+import static org.argeo.api.acr.ArgeoNamespace.LDAP_NAMESPACE_URI;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.QNamed;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+
+/**
+ * Standard LDAP object classes as per
+ * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
+ * oid-reference</a>
+ */
+public enum LdapObj implements QNamed, SpecifiedName {
+       account("0.9.2342.19200300.100.4.5", "RFC 4524"),
+       /** */
+       document("0.9.2342.19200300.100.4.6", "RFC 4524"),
+       /** */
+       room("0.9.2342.19200300.100.4.7", "RFC 4524"),
+       /** */
+       documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
+       /** */
+       domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
+       /** */
+       rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
+       /** */
+       domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
+       /** */
+       friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
+       /** */
+       simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
+       /** */
+       uidObject("1.3.6.1.1.3.1", "RFC 4519"),
+       /** */
+       extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
+       /** */
+       dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
+       /** */
+       authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
+       /** */
+       namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
+       /** */
+       inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
+       /** */
+       top("2.5.6.0", "RFC 4512"),
+       /** */
+       alias("2.5.6.1", "RFC 4512"),
+       /** */
+       country("2.5.6.2", "RFC 4519"),
+       /** */
+       locality("2.5.6.3", "RFC 4519"),
+       /** */
+       organization("2.5.6.4", "RFC 4519"),
+       /** */
+       organizationalUnit("2.5.6.5", "RFC 4519"),
+       /** */
+       person("2.5.6.6", "RFC 4519"),
+       /** */
+       organizationalPerson("2.5.6.7", "RFC 4519"),
+       /** */
+       organizationalRole("2.5.6.8", "RFC 4519"),
+       /** */
+       groupOfNames("2.5.6.9", "RFC 4519"),
+       /** */
+       residentialPerson("2.5.6.10", "RFC 4519"),
+       /** */
+       applicationProcess("2.5.6.11", "RFC 4519"),
+       /** */
+       device("2.5.6.14", "RFC 4519"),
+       /** */
+       strongAuthenticationUser("2.5.6.15", "RFC 4523"),
+       /** */
+       certificationAuthority("2.5.6.16", "RFC 4523"),
+       // /** Should be certificationAuthority-V2 */
+       // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
+       // },
+       /** */
+       groupOfUniqueNames("2.5.6.17", "RFC 4519"),
+       /** */
+       userSecurityInformation("2.5.6.18", "RFC 4523"),
+       /** */
+       cRLDistributionPoint("2.5.6.19", "RFC 4523"),
+       /** */
+       pkiUser("2.5.6.21", "RFC 4523"),
+       /** */
+       pkiCA("2.5.6.22", "RFC 4523"),
+       /** */
+       deltaCRL("2.5.6.23", "RFC 4523"),
+       /** */
+       subschema("2.5.20.1", "RFC 4512"),
+       /** */
+       ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
+       /** */
+       changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
+       /** */
+       inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
+       /** */
+       referral("2.16.840.1.113730.3.2.6", "RFC 3296"),
+
+       // RFC 2307bis (partial)
+       /** */
+       posixAccount("1.3.6.1.1.1.2.0", "RFC 2307bis"),
+       /** */
+       posixGroup("1.3.6.1.1.1.2.2", "RFC 2307bis"),
+
+       //
+       ;
+
+       private final String oid, spec;
+       private final QName value;
+
+       private LdapObj(String oid, String spec) {
+               this.oid = oid;
+               this.spec = spec;
+               this.value = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, name());
+       }
+
+       public QName qName() {
+               return value;
+       }
+
+       public String getOid() {
+               return oid;
+       }
+
+       public String getSpec() {
+               return spec;
+       }
+
+       @Deprecated
+       public String property() {
+               return get();
+       }
+
+       /** #deprecated use {@link #qName()} instead. */
+//     @Deprecated
+       public String get() {
+               return RuntimeNamespaceContext.getNamespaceContext().getPrefix(LDAP_NAMESPACE_URI) + ":" + name();
+       }
+
+       @Override
+       public String getNamespace() {
+               return LDAP_NAMESPACE_URI;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return LDAP_DEFAULT_PREFIX;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NamingUtils.java
new file mode 100644 (file)
index 0000000..88b76ec
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.api.acr.ldap;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class NamingUtils {
+       /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
+       private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
+                       .withZone(ZoneOffset.UTC);
+
+       /** @return null if not parseable */
+       public static Instant ldapDateToInstant(String ldapDate) {
+               try {
+                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
+               } catch (DateTimeParseException e) {
+                       return null;
+               }
+       }
+
+       /** @return null if not parseable */
+       public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
+               try {
+                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
+               } catch (DateTimeParseException e) {
+                       return null;
+               }
+       }
+
+       public static Calendar ldapDateToCalendar(String ldapDate) {
+               OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
+               GregorianCalendar calendar = new GregorianCalendar();
+               calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
+               calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
+               calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
+               return calendar;
+       }
+
+       public static String instantToLdapDate(ZonedDateTime instant) {
+               return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
+       }
+
+       public static String getQueryValue(Map<String, List<String>> query, String key) {
+               if (!query.containsKey(key))
+                       return null;
+               List<String> val = query.get(key);
+               if (val.size() == 1)
+                       return val.get(0);
+               else
+                       throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
+       }
+
+       public static Map<String, List<String>> queryToMap(URI uri) {
+               return queryToMap(uri.getQuery());
+       }
+
+       private static Map<String, List<String>> queryToMap(String queryPart) {
+               try {
+                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+                       if (queryPart == null)
+                               return query_pairs;
+                       final String[] pairs = queryPart.split("&");
+                       for (String pair : pairs) {
+                               final int idx = pair.indexOf("=");
+                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+                                               : pair;
+                               if (!query_pairs.containsKey(key)) {
+                                       query_pairs.put(key, new LinkedList<String>());
+                               }
+                               final String value = idx > 0 && pair.length() > idx + 1
+                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+                                               : null;
+                               query_pairs.get(key).add(value);
+                       }
+                       return query_pairs;
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
+               }
+       }
+
+       private NamingUtils() {
+
+       }
+
+//     public static void main(String args[]) {
+//             ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
+//             String str = utcLdapDate.format(now);
+//             System.out.println(str);
+//             utcLdapDate.parse(str);
+//             utcLdapDate.parse("19520512000000Z");
+//     }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/NodeOID.java
new file mode 100644 (file)
index 0000000..a68b6cb
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.api.acr.ldap;
+
+interface NodeOID {
+       String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
+
+       // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
+       
+       // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
+       
+       // ATTRIBUTE TYPES
+       String ATTRIBUTE_TYPES = BASE + ".4";
+
+       // OBJECT CLASSES
+       String OBJECT_CLASSES = BASE + ".6";
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/SpecifiedName.java
new file mode 100644 (file)
index 0000000..19e7240
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.api.acr.ldap;
+
+/**
+ * A name which has been specified and for which an id has been defined
+ * (typically an OID).
+ */
+interface SpecifiedName {
+       /** The name */
+       String name();
+
+       /** An RFC or the URLof some specification */
+       default String getSpec() {
+               return null;
+       }
+
+       /** Typically an OID */
+       default String getID() {
+               return getClass().getName() + "." + name();
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java
deleted file mode 100644 (file)
index 6bfc7cd..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.api.acr.spi;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.Content;
-import org.argeo.api.acr.CrName;
-
-public abstract class AbstractContent extends AbstractMap<QName, Object> implements Content {
-
-       /*
-        * ATTRIBUTES OPERATIONS
-        */
-       protected abstract Iterable<QName> keys();
-
-       protected abstract void removeAttr(QName key);
-
-       @Override
-       public Set<Entry<QName, Object>> entrySet() {
-               Set<Entry<QName, Object>> result = new AttrSet();
-               return result;
-       }
-
-       @Override
-       public Class<?> getType(QName key) {
-               return String.class;
-       }
-
-       @Override
-       public boolean isMultiple(QName key) {
-               return false;
-       }
-
-       @Override
-       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
-               Object value = get(key);
-               if (value == null)
-                       return null;
-               if (value instanceof List) {
-                       try {
-                               List<A> res = (List<A>) value;
-                               return Optional.of(res);
-                       } catch (ClassCastException e) {
-                               List<A> res = new ArrayList<>();
-                               List<?> lst = (List<?>) value;
-                               try {
-                                       for (Object o : lst) {
-                                               A item = (A) o;
-                                               res.add(item);
-                                       }
-                                       return Optional.of(res);
-                               } catch (ClassCastException e1) {
-                                       return Optional.empty();
-                               }
-                       }
-               } else {// singleton
-                       try {
-                               A res = (A) value;
-                               return Optional.of(Collections.singletonList(res));
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
-                       }
-               }
-       }
-
-       /*
-        * CONTENT OPERATIONS
-        */
-
-       @Override
-       public String getPath() {
-               List<Content> ancestors = new ArrayList<>();
-               collectAncestors(ancestors, this);
-               StringBuilder path = new StringBuilder();
-               for (Content c : ancestors) {
-                       QName name = c.getName();
-                       // FIXME
-                       if (!CrName.ROOT.get().equals(name))
-                               path.append('/').append(name);
-               }
-               return path.toString();
-       }
-
-       private void collectAncestors(List<Content> ancestors, Content content) {
-               if (content == null)
-                       return;
-               ancestors.add(0, content);
-               collectAncestors(ancestors, content.getParent());
-       }
-
-       /*
-        * UTILITIES
-        */
-       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
-               // check whether clss is Object.class
-               return clss.isAssignableFrom(Object.class);
-       }
-
-       @Override
-       public String toString() {
-               return "content " + getPath();
-       }
-
-       /*
-        * SUB CLASSES
-        */
-
-       class AttrSet extends AbstractSet<Entry<QName, Object>> {
-
-               @Override
-               public Iterator<Entry<QName, Object>> iterator() {
-                       final Iterator<QName> keys = keys().iterator();
-                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
-
-                               QName key = null;
-
-                               @Override
-                               public boolean hasNext() {
-                                       return keys.hasNext();
-                               }
-
-                               @Override
-                               public Entry<QName, Object> next() {
-                                       key = keys.next();
-                                       // TODO check type
-                                       Optional<?> value = get(key, Object.class);
-                                       assert !value.isEmpty();
-                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
-                                       return entry;
-                               }
-
-                               @Override
-                               public void remove() {
-                                       if (key != null) {
-                                               AbstractContent.this.removeAttr(key);
-                                       } else {
-                                               throw new IllegalStateException("Iteration has not started");
-                                       }
-                               }
-
-                       };
-                       return it;
-               }
-
-               @Override
-               public int size() {
-                       int count = 0;
-                       for (QName key : keys()) {
-                               count++;
-                       }
-                       return count;
-               }
-
-       }
-}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentNamespace.java
new file mode 100644 (file)
index 0000000..f91a177
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.api.acr.spi;
+
+import java.net.URL;
+
+/** A namespace and its default prefix, possibly with a schema definition. */
+public interface ContentNamespace {
+       String getDefaultPrefix();
+
+       String getNamespaceURI();
+
+       URL getSchemaResource();
+
+}
index d83cf49c95e27e8b828924a1f71547c235f16f83..72aa162b3b59716af8972c256b6a36d06d476053 100644 (file)
@@ -1,9 +1,32 @@
 package org.argeo.api.acr.spi;
 
-import org.argeo.api.acr.Content;
+import java.util.Iterator;
 
-public interface ContentProvider {
+import javax.xml.namespace.NamespaceContext;
 
-       Content get(ProvidedSession session, String mountPath, String relativePath);
+public interface ContentProvider extends NamespaceContext {
+
+       ProvidedContent get(ProvidedSession session, String relativePath);
+
+       boolean exists(ProvidedSession session, String relativePath);
+
+       String getMountPath();
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+       @Override
+       default String getPrefix(String namespaceURI) {
+               Iterator<String> prefixes = getPrefixes(namespaceURI);
+               return prefixes.hasNext() ? prefixes.next() : null;
+       }
+
+//     default ContentName parsePrefixedName(String nameWithPrefix) {
+//             return NamespaceUtils.parsePrefixedName(this, nameWithPrefix);
+//     }
+//
+//     default String toPrefixedName(QName name) {
+//             return NamespaceUtils.toPrefixedName(this, name);
+//     }
 
 }
index d9fc781d0317878d1ba0be628e080e9ac112c30d..e2807c0efd19e746c7a9ab41a2971e36c1fe55e9 100644 (file)
@@ -2,8 +2,34 @@ package org.argeo.api.acr.spi;
 
 import org.argeo.api.acr.Content;
 
+/** A {@link Content} implementation. */
 public interface ProvidedContent extends Content {
+       final static String ROOT_PATH = "/";
+
        ProvidedSession getSession();
 
        ContentProvider getProvider();
+
+       int getDepth();
+
+       /**
+        * An opaque ID which is guaranteed to uniquely identify this content within the
+        * session return by {@link #getSession()}. Typically used for UI.
+        */
+       String getSessionLocalId();
+
+       default ProvidedContent getMountPoint(String relativePath) {
+               throw new UnsupportedOperationException("This content doe not support mount");
+       }
+
+       default ProvidedContent getContent(String path) {
+               Content fileNode;
+               if (path.startsWith(ROOT_PATH)) {// absolute
+                       fileNode = getSession().get(path);
+               } else {// relative
+                       String absolutePath = getPath() + '/' + path;
+                       fileNode = getSession().get(absolutePath);
+               }
+               return (ProvidedContent) fileNode;
+       }
 }
index c3052c37511d03b0b2d985bee78b8ed7252c1f43..06ee43aa74fe00e19ddf24a4810498d1428d2e00 100644 (file)
@@ -1,6 +1,17 @@
 package org.argeo.api.acr.spi;
 
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentRepository;
 
+/** A {@link ContentRepository} implementation. */
 public interface ProvidedRepository extends ContentRepository {
+       void registerTypes(ContentNamespace... namespaces);
+
+       ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types);
+
+       boolean shouldMount(QName... types);
+
+       void addProvider(ContentProvider provider);
 }
index f90d6747565b81aeadee1d74075e7d86c2cd30c9..5a538b57a45ec4593aca8a9267e67570393169b2 100644 (file)
@@ -1,68 +1,45 @@
 package org.argeo.api.acr.spi;
 
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletionStage;
 
-import javax.xml.XMLConstants;
-import javax.xml.namespace.NamespaceContext;
-
-import org.argeo.api.acr.ContentNameSupplier;
+import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.RuntimeNamespaceContext;
 
-public interface ProvidedSession extends ContentSession, NamespaceContext {
+/** A {@link ContentSession} implementation. */
+public interface ProvidedSession extends ContentSession {
        ProvidedRepository getRepository();
 
+       CompletionStage<ProvidedSession> onClose();
+
+       Content getMountPoint(String path);
+
+       boolean isEditing();
+
+       void notifyModification(ProvidedContent content);
+
+       UUID getUuid();
+
+//     Content getSessionRunDir();
+
        /*
         * NAMESPACE CONTEXT
         */
-       /** @return the bound namespace or null if not found */
-       String findNamespace(String prefix);
-
-       // TODO find the default prefix?
-       Set<String> findPrefixes(String namespaceURI);
-
-       /** To be overridden for optimisation, as it will be called a lot */
-       default String findPrefix(String namespaceURI) {
-               Set<String> prefixes = findPrefixes(namespaceURI);
-               if (prefixes.isEmpty())
-                       return null;
-               return prefixes.iterator().next();
-       }
 
        @Override
-       default String getNamespaceURI(String prefix) {
-               String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
-                       return XMLConstants.NULL_NS_URI;
-               namespaceURI = findNamespace(prefix);
-               if (namespaceURI != null)
-                       return namespaceURI;
-               return XMLConstants.NULL_NS_URI;
+       default String getPrefix(String namespaceURI) {
+               return RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
        }
 
        @Override
-       default String getPrefix(String namespaceURI) {
-               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
-               if (prefix != null)
-                       return prefix;
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       return XMLConstants.DEFAULT_NS_PREFIX;
-               return findPrefix(namespaceURI);
+       default String getNamespaceURI(String prefix) {
+               return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix);
        }
 
        @Override
        default Iterator<String> getPrefixes(String namespaceURI) {
-               Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
-               if (standard != null)
-                       return standard;
-               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
-                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
-               Set<String> prefixes = findPrefixes(namespaceURI);
-               assert prefixes != null;
-               return prefixes.iterator();
+               return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI);
        }
-
 }
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/ArrayTabularRow.java
new file mode 100644 (file)
index 0000000..c0efe87
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.api.acr.tabular;
+
+import java.util.List;
+
+/** Minimal tabular row wrapping an {@link Object} array */
+public class ArrayTabularRow implements TabularRow {
+       private final Object[] arr;
+
+       public ArrayTabularRow(List<?> objs) {
+               this.arr = objs.toArray();
+       }
+
+       public Object get(Integer col) {
+               return arr[col];
+       }
+
+       public int size() {
+               return arr.length;
+       }
+
+       public Object[] toArray() {
+               return arr;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularColumn.java
new file mode 100644 (file)
index 0000000..5b9bf23
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.api.acr.tabular;
+
+/** The column in a tabular content */
+public class TabularColumn {
+       private String name;
+       /**
+        * JCR types, see
+        * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
+        * ?javax/jcr/PropertyType.html
+        */
+       private Integer type;
+
+       /** column with default type */
+       public TabularColumn(String name) {
+               super();
+               this.name = name;
+       }
+
+       public TabularColumn(String name, Integer type) {
+               super();
+               this.name = name;
+               this.type = type;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public Integer getType() {
+               return type;
+       }
+
+       public void setType(Integer type) {
+               this.type = type;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularContent.java
new file mode 100644 (file)
index 0000000..ae6eb4e
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.api.acr.tabular;
+
+import java.util.List;
+
+/**
+ * Content organized as a table, possibly with headers. Only JCR types are
+ * supported even though there is not direct dependency on JCR.
+ */
+public interface TabularContent {
+       /** The headers of this table or <code>null</code> is none available. */
+       public List<TabularColumn> getColumns();
+
+       public TabularRowIterator read();
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRow.java
new file mode 100644 (file)
index 0000000..5302cc0
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.api.acr.tabular;
+
+/** A row of tabular data */
+public interface TabularRow {
+       /** The value at this column index */
+       public Object get(Integer col);
+
+       /** The raw objects (direct references) */
+       public Object[] toArray();
+
+       /** Number of columns */
+       public int size();
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularRowIterator.java
new file mode 100644 (file)
index 0000000..768c593
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.api.acr.tabular;
+
+import java.util.Iterator;
+
+/** Navigation of rows */
+public interface TabularRowIterator extends Iterator<TabularRow> {
+       /**
+        * Current row number, has to be incremented by each call to next() ; starts at 0, will
+        * therefore be 1 for the first row returned.
+        */
+       public Long getCurrentRowNumber();
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/TabularWriter.java
new file mode 100644 (file)
index 0000000..f1d555f
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.acr.tabular;
+
+
+/** Write to a tabular content */
+public interface TabularWriter {
+       /** Append a new row of data */
+       public void appendRow(Object[] row);
+
+       /** Finish persisting data and release resources */
+       public void close();
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java b/org.argeo.api.acr/src/org/argeo/api/acr/tabular/package-info.java
new file mode 100644 (file)
index 0000000..06acbc5
--- /dev/null
@@ -0,0 +1,2 @@
+/** Tabular format API. */
+package org.argeo.api.acr.tabular;
\ No newline at end of file
diff --git a/org.argeo.api.cli/.classpath b/org.argeo.api.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/org.argeo.api.cli/.project b/org.argeo.api.cli/.project
new file mode 100644 (file)
index 0000000..8f5cd41
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.cli</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.cli/bnd.bnd b/org.argeo.api.cli/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/org.argeo.api.cli/build.properties b/org.argeo.api.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/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java
new file mode 100644 (file)
index 0000000..935247f
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.api.cli;
+
+/** Exception thrown when the provided arguments are not correct. */
+public class CommandArgsException extends IllegalArgumentException {
+       private static final long serialVersionUID = -7271050747105253935L;
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public CommandArgsException(Exception cause) {
+               super(cause.getMessage(), cause);
+       }
+
+       public CommandArgsException(String message) {
+               super(message);
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public void setCommandName(String commandName) {
+               this.commandName = commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+       public void setCommandsCli(CommandsCli commandsCli) {
+               this.commandsCli = commandsCli;
+       }
+
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java
new file mode 100644 (file)
index 0000000..52c0334
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.api.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+       private static final long serialVersionUID = 5595999301269377128L;
+
+       private final DescribedCommand<?> command;
+       private final List<String> arguments;
+
+       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               this(null, e, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+               this(message, null, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+               this.command = command;
+               this.arguments = arguments;
+       }
+
+       public DescribedCommand<?> getCommand() {
+               return command;
+       }
+
+       public List<String> getArguments() {
+               return arguments;
+       }
+
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java
new file mode 100644 (file)
index 0000000..5bbfcfa
--- /dev/null
@@ -0,0 +1,157 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+       private final String commandName;
+       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+       protected final Options options = new Options();
+
+       public CommandsCli(String commandName) {
+               this.commandName = commandName;
+       }
+
+       @Override
+       public Object apply(List<String> args) {
+               String cmd = null;
+               List<String> newArgs = new ArrayList<>();
+               boolean isHelpOption = false;
+               try {
+                       CommandLineParser clParser = new DefaultParser();
+                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+                       List<String> leftOvers = commonCl.getArgList();
+                       for (String arg : leftOvers) {
+                               if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
+                                       isHelpOption = true;
+                                       // TODO break?
+                               }
+
+                               if (!arg.startsWith("-") && cmd == null) {
+                                       cmd = arg;
+                               } else {
+                                       newArgs.add(arg);
+                               }
+                       }
+               } catch (ParseException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       throw cae;
+               }
+
+               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+
+               // --help option
+               if (!(function instanceof CommandsCli))
+                       if (function instanceof DescribedCommand<?> command)
+                               if (isHelpOption) {
+                                       throw new PrintHelpRequestException(cmd, this);
+//                                     StringWriter out = new StringWriter();
+//                                     HelpCommand.printHelp(command, out);
+//                                     System.out.println(out.toString());
+//                                     return null;
+                               }
+
+               if (function == null)
+                       throw new IllegalArgumentException("Uknown command " + cmd);
+               try {
+                       Object value = function.apply(newArgs);
+                       return value != null ? value.toString() : null;
+               } catch (CommandArgsException e) {
+                       if (e.getCommandName() == null) {
+                               e.setCommandName(cmd);
+                               e.setCommandsCli(this);
+                       }
+                       throw e;
+               } catch (IllegalArgumentException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       cae.setCommandName(cmd);
+                       throw cae;
+               }
+       }
+
+       @Override
+       public Options getOptions() {
+               return options;
+       }
+
+       protected void addCommand(String cmd, Function<List<String>, ?> function) {
+               commands.put(cmd, function);
+
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       protected void addCommandsCli(CommandsCli commandsCli) {
+               addCommand(commandsCli.getCommandName(), commandsCli);
+               commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public Set<String> getSubCommands() {
+               return commands.keySet();
+       }
+
+       public Function<List<String>, ?> getCommand(String command) {
+               return commands.get(command);
+       }
+
+       public HelpCommand getHelpCommand() {
+               return (HelpCommand) getCommand(HelpCommand.HELP);
+       }
+
+       public Function<List<String>, String> getDefaultCommand() {
+               return getHelpCommand();
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(CommandsCli cli, String[] args) {
+               try {
+                       cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
+                       Object output = cli.apply(Arrays.asList(args));
+                       if (output != null)
+                               System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                       System.out.println(out.toString());
+               } catch (CommandArgsException e) {
+                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+                       Throwable cause = e.getCause();
+                       if (!(cause instanceof MissingOptionException))
+                               e.printStackTrace();
+                       if (e.getCommandName() != null) {
+                               StringWriter out = new StringWriter();
+                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                               System.err.println(out.toString());
+                       } else {
+                               e.printStackTrace();
+                       }
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java
new file mode 100644 (file)
index 0000000..51cb2ce
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+       default Options getOptions() {
+               return new Options();
+       }
+
+       String getDescription();
+
+       default String getUsage() {
+               return null;
+       }
+
+       default String getExamples() {
+               return null;
+       }
+
+       default CommandLine toCommandLine(List<String> args) {
+               try {
+                       DefaultParser parser = new DefaultParser();
+                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+               } catch (ParseException e) {
+                       throw new CommandArgsException(e);
+               }
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(DescribedCommand<?> command, String[] args) {
+               try {
+                       Object output = command.apply(Arrays.asList(args));
+                       System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.out.println(out.toString());
+                       System.exit(1);
+               } catch (IllegalArgumentException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.err.println(out.toString());
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java
new file mode 100644 (file)
index 0000000..cd51d76
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.api.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+       /**
+        * System property forcing the root command to this value (typically the name of
+        * a script).
+        */
+       public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
+
+       final static String HELP = "help";
+       final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
+
+       private CommandsCli commandsCli;
+       private CommandsCli parentCommandsCli;
+
+       // Help formatting
+       private static int helpWidth = 80;
+       private static int helpLeftPad = 4;
+       private static int helpDescPad = 20;
+
+       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+               super();
+               this.parentCommandsCli = parentCommandsCli;
+               this.commandsCli = commandsCli;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               StringWriter out = new StringWriter();
+
+               if (args.size() == 0) {// overview
+                       printHelp(commandsCli, out);
+               } else {
+                       String cmd = args.get(0);
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       if (function == null)
+                               return "Command " + cmd + " not found.";
+                       Options options;
+                       String examples;
+                       DescribedCommand<?> command = null;
+                       if (function instanceof DescribedCommand) {
+                               command = (DescribedCommand<?>) function;
+                               options = command.getOptions();
+                               examples = command.getExamples();
+                       } else {
+                               options = new Options();
+                               examples = null;
+                       }
+                       String description = getShortDescription(function);
+                       String commandCall = getCommandUsage(cmd, command);
+                       HelpFormatter formatter = new HelpFormatter();
+                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+                                       helpDescPad, examples, false);
+               }
+               return out.toString();
+       }
+
+       private static String getShortDescription(Function<List<String>, ?> function) {
+               if (function instanceof DescribedCommand) {
+                       return ((DescribedCommand<?>) function).getDescription();
+               } else {
+                       return function.toString();
+               }
+       }
+
+       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+               String commandCall = getCommandCall(commandsCli) + " " + cmd;
+               assert command != null;
+               if (command != null && command.getUsage() != null) {
+                       commandCall = commandCall + " " + command.getUsage();
+               }
+               return commandCall;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Shows this help or describes a command";
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       public CommandsCli getParentCommandsCli() {
+               return parentCommandsCli;
+       }
+
+       protected String getCommandCall(CommandsCli commandsCli) {
+               HelpCommand hc = commandsCli.getHelpCommand();
+               if (hc.getParentCommandsCli() != null) {
+                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+               } else {
+                       String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
+                       if (rootCommand != null)
+                               return rootCommand;
+                       return commandsCli.getCommandName();
+               }
+       }
+
+       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+               String usage = "java " + command.getClass().getName()
+                               + (command.getUsage() != null ? " " + command.getUsage() : "");
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+               if (commandName == null) {
+                       printHelp(commandsCli, out);
+                       return;
+               }
+               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+               out.append(commandsCli.getDescription()).append('\n');
+               String leftPad = spaces(helpLeftPad);
+               for (String cmd : commandsCli.getSubCommands()) {
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       assert function != null;
+                       out.append(leftPad);
+                       out.append(cmd);
+                       // TODO deal with long commands
+                       out.append(spaces(helpDescPad - cmd.length()));
+                       out.append(getShortDescription(function));
+                       out.append('\n');
+               }
+       }
+
+       private static String spaces(int count) {
+               // Java 11
+               // return " ".repeat(count);
+               if (count <= 0)
+                       return "";
+               else {
+                       StringBuilder sb = new StringBuilder(count);
+                       for (int i = 0; i < count; i++)
+                               sb.append(' ');
+                       return sb.toString();
+               }
+       }
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java
new file mode 100644 (file)
index 0000000..017386b
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.api.cli;
+
+/** An exception indicating that help should be printed. */
+class PrintHelpRequestException extends RuntimeException {
+
+       private static final long serialVersionUID = -9029122270660656639L;
+
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
+               super();
+               this.commandName = commandName;
+               this.commandsCli = commandsCli;
+       }
+
+       public static long getSerialversionuid() {
+               return serialVersionUID;
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java b/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java
new file mode 100644 (file)
index 0000000..114fd02
--- /dev/null
@@ -0,0 +1,2 @@
+/** Command line API. */
+package org.argeo.api.cli;
\ No newline at end of file
index e801ebfb4680123285c15553dc70584276fe0057..81fe078c20c05db46a8281fbb1a72875a5322b45 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index 51c4e663c9a1a6e5cc7f1507e230b6dd61e42840..36cde6410b661217d9016bf03cb81b3179082af7 100644 (file)
@@ -1,4 +1,6 @@
 Import-Package: \
-javax.security.*
+javax.transaction.xa,\
+javax.security.*,\
+org.osgi.service.useradmin,\
 
 Export-Package: org.argeo.api.cms.*
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java
deleted file mode 100644 (file)
index 30b3d81..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.api.cms;
-
-/** A 2D size. */
-public class Cms2DSize {
-       private Integer width;
-       private Integer height;
-
-       public Cms2DSize() {
-
-       }
-
-       public Cms2DSize(Integer width, Integer height) {
-               super();
-               this.width = width;
-               this.height = height;
-       }
-
-       public Integer getWidth() {
-               return width;
-       }
-
-       public void setWidth(Integer width) {
-               this.width = width;
-       }
-
-       public Integer getHeight() {
-               return height;
-       }
-
-       public void setHeight(Integer height) {
-               this.height = height;
-       }
-
-}
index 761191e5dd06052842482c054751b71c6b9096ad..b180fff75b67593772f8482c1489c2fad69fc9f7 100644 (file)
@@ -1,9 +1,13 @@
 package org.argeo.api.cms;
 
+import java.util.Map;
 import java.util.Set;
 
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsUi;
+
 /** An extensible user interface base on the CMS backend. */
-public interface CmsApp {
+public interface CmsApp extends CmsEventSubscriber {
        /**
         * If {@link CmsUi#setData(String, Object)} is set with this property, it
         * indicates a different UI (typically with another theming. The {@link CmsApp}
@@ -13,6 +17,8 @@ public interface CmsApp {
         */
        final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
 
+       final static String CONTEXT_NAME_PROPERTY = "argeo.cms.app.contextName";
+
        Set<String> getUiNames();
 
        CmsUi initUi(Object uiParent);
@@ -28,4 +34,12 @@ public interface CmsApp {
        void addCmsAppListener(CmsAppListener listener);
 
        void removeCmsAppListener(CmsAppListener listener);
+
+       CmsContext getCmsContext();
+
+       @Override
+       default void onEvent(String topic, Map<String, Object> properties) {
+       }
+       
+       
 }
index decea35508a4da5db931fc3b44a4a7732d5b68d7..31ec8be5071f63dce2eaa4b0e89bb13dfd0c10e6 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.api.cms;
 
+import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
@@ -21,6 +22,18 @@ public enum CmsAuth {
                return new LoginContext(getLoginContextName(), callbackHandler);
        }
 
+       public LoginContext newLoginContext(Subject subject, CallbackHandler callbackHandler) throws LoginException {
+               return new LoginContext(getLoginContextName(), subject, callbackHandler);
+       }
+
+       public LoginContext newLoginContext(Subject subject) throws LoginException {
+               return new LoginContext(getLoginContextName(), subject);
+       }
+
+       public LoginContext newLoginContext() throws LoginException {
+               return new LoginContext(getLoginContextName());
+       }
+
        /*
         * LOGIN CONTEXTS
         */
index 8fe9846f41f272f1a956ee29fd3099748ea66513..bae874b1652e85f4ac28d372e0aa206b19abaf39 100644 (file)
@@ -35,6 +35,12 @@ public interface CmsConstants {
        String GUESTS_WORKSPACE = "guests";
        String PUBLIC_WORKSPACE = "public";
        String SECURITY_WORKSPACE = "security";
+       String MIGRATION_WORKSPACE = "migration";
+
+       /*
+        * ACR CONVENTIONS
+        */
+       String SRV_BASE = "/srv";
 
        /*
         * BASE DNs
@@ -49,17 +55,18 @@ public interface CmsConstants {
        /*
         * RESERVED ROLES
         */
-       String ROLES_BASEDN = "ou=roles,ou=node";
+       String NODE_BASEDN = "ou=node";
+       String SYSTEM_ROLES_BASEDN = "ou=roles," + NODE_BASEDN;
        String TOKENS_BASEDN = "ou=tokens,ou=node";
-       String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
-       String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
-       String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
+       String ROLE_ADMIN = "cn=admin," + SYSTEM_ROLES_BASEDN;
+       String ROLE_USER_ADMIN = "cn=userAdmin," + SYSTEM_ROLES_BASEDN;
+       String ROLE_DATA_ADMIN = "cn=dataAdmin," + SYSTEM_ROLES_BASEDN;
        // Special system groups that cannot be edited:
        // user U anonymous = everyone
-       String ROLE_USER = "cn=user," + ROLES_BASEDN;
-       String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
+       String ROLE_USER = "cn=user," + SYSTEM_ROLES_BASEDN;
+       String ROLE_ANONYMOUS = "cn=anonymous," + SYSTEM_ROLES_BASEDN;
        // Account lifecycle
-       String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
+       String ROLE_REGISTERING = "cn=registering," + SYSTEM_ROLES_BASEDN;
 
        /*
         * PATHS
@@ -68,6 +75,7 @@ public interface CmsConstants {
        String PATH_JCR = "/jcr";
        String PATH_FILES = "/files";
        // String PATH_JCR_PUB = "/pub";
+       String PATH_API_ACR = "/api/acr";
 
        /*
         * FILE SYSTEMS
@@ -80,33 +88,17 @@ public interface CmsConstants {
        String NODE_SERVICE = NODE;
 
        /*
-        * INIT FRAMEWORK PROPERTIES
+        * COMPONENT PROPERTIES
         */
-       String NODE_INIT = "argeo.node.init";
-       String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
-       String I18N_LOCALES = "argeo.i18n.locales";
-       // Node Security
-       String ROLES_URI = "argeo.node.roles.uri";
-       String TOKENS_URI = "argeo.node.tokens.uri";
-       /** URI to an LDIF file or LDAP server used as initialization or backend */
-       String USERADMIN_URIS = "argeo.node.useradmin.uris";
-       // Transaction manager
-       String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
-       String TRANSACTION_MANAGER_SIMPLE = "simple";
-       String TRANSACTION_MANAGER_BITRONIX = "bitronix";
-       // Node
-       /** Properties configuring the node repository */
-       String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
-       /** Additional standalone repositories, related to data models. */
-       String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
-       // HTTP
-       String HTTP_PORT = "org.osgi.service.http.port";
-       String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
-       /**
-        * The HTTP header used to convey the DN of a client verified by a reverse
-        * proxy. Typically SSL_CLIENT_S_DN for Apache.
+       String CONTEXT_PATH = "context.path";
+       String CONTEXT_PUBLIC = "context.public";
+       String EVENT_TOPICS = "event.topics";
+       String ACR_MOUNT_PATH = "acr.mount.path";
+
+       /*
+        * FILE SYSTEM
         */
-       String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
+       String CMS_FS_SCHEME = "cms";
 
        /*
         * PIDs
index fa26b253a2121f6f67f103966657460729fcb898..6ad0f512cab9180d4dbd1947e584022c672c0edc 100644 (file)
@@ -2,6 +2,9 @@ package org.argeo.api.cms;
 
 import java.util.List;
 import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
 
 /**
  * A logical view on this CMS instance, independently of a particular launch or
@@ -20,7 +23,15 @@ public interface CmsContext {
 
        Long getAvailableSince();
 
-       
        /** Mark this group as a workgroup */
        void createWorkgroup(String groupDn);
+
+       /** Get the CMS session of this subject. */
+       CmsSession getCmsSession(Subject subject);
+
+       CmsEventBus getCmsEventBus();
+
+       /** A new time based {@link UUID} (v1) using the current time */
+       UUID timeUUID();
+
 }
index 5893d2ec52553179bc5ebe4ad32d894d0d99e5e0..d557816cb1a3bc927d449c2ae7232b307fef726b 100644 (file)
@@ -1,11 +1,9 @@
 package org.argeo.api.cms;
 
-import java.util.Dictionary;
-
 /** A configured node deployment. */
 public interface CmsDeployment {
 
-       void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-
-       Dictionary<String, Object> getProps(String factoryPid, String cn);
+//     void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
+//
+//     Dictionary<String, Object> getProps(String factoryPid, String cn);
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java
deleted file mode 100644 (file)
index 2deca01..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.argeo.api.cms;
-
-/** Abstraction of a simple edition life cycle. */
-public interface CmsEditable {
-
-       /** Whether the calling thread can edit, the value is immutable */
-       public Boolean canEdit();
-
-       public Boolean isEditing();
-
-       public void startEditing();
-
-       public void stopEditing();
-
-       public static CmsEditable NON_EDITABLE = new CmsEditable() {
-
-               @Override
-               public void stopEditing() {
-               }
-
-               @Override
-               public void startEditing() {
-               }
-
-               @Override
-               public Boolean isEditing() {
-                       return false;
-               }
-
-               @Override
-               public Boolean canEdit() {
-                       return false;
-               }
-       };
-
-       public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
-
-               @Override
-               public void stopEditing() {
-               }
-
-               @Override
-               public void startEditing() {
-               }
-
-               @Override
-               public Boolean isEditing() {
-                       return true;
-               }
-
-               @Override
-               public Boolean canEdit() {
-                       return true;
-               }
-       };
-
-}
index b5dccbe9cb1b2e2d3ab810d42414c34daff14754..6ec70feba2ba8c184d8eeb1f37772f671d54dc31 100644 (file)
@@ -1,5 +1,7 @@
 package org.argeo.api.cms;
 
+import org.argeo.api.cms.ux.CmsView;
+
 /**
  * Can be applied to {@link Enum}s in order to define events used by
  * {@link CmsView#sendEvent(String, java.util.Map)}.
@@ -8,12 +10,11 @@ public interface CmsEvent {
        String name();
 
        default String topic() {
-               return getTopicBase() + "/" + name();
+               return getTopicBase() + "." + name();
        }
 
-       default         String getTopicBase() {
-               return "argeo/cms";
+       default String getTopicBase() {
+               return "argeo.cms";
        }
 
-
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventBus.java
new file mode 100644 (file)
index 0000000..bb8a782
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.api.cms;
+
+import java.util.Map;
+
+public interface CmsEventBus {
+       void sendEvent(String topic, Map<String, Object> event);
+
+       void addEventSubscriber(String topic, CmsEventSubscriber eventSubscriber);
+
+       void removeEventSubscriber(String topic, CmsEventSubscriber eventSubscriber);
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEventSubscriber.java
new file mode 100644 (file)
index 0000000..9ca5eaa
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.api.cms;
+
+import java.util.Map;
+
+public interface CmsEventSubscriber {
+
+       void onEvent(String topic, Map<String, Object> properties);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java
deleted file mode 100644 (file)
index 8c637b8..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.api.cms;
-
-import java.io.InputStream;
-
-/** Read and write access to images. */
-public interface CmsImageManager<V, M> {
-       /** Load image in control */
-       public Boolean load(M node, V control, Cms2DSize size);
-
-       /** @return (0,0) if not available */
-       public Cms2DSize getImageSize(M node);
-
-       /**
-        * The related &lt;img&gt; tag, with src, width and height set.
-        * 
-        * @return null if not available
-        */
-       public String getImageTag(M node);
-
-       /**
-        * The related &lt;img&gt; tag, with url, width and height set. Caller must
-        * close the tag (or add additional attributes).
-        * 
-        * @return null if not available
-        */
-       public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
-
-       /**
-        * Returns the remotely accessible URL of the image (registering it if
-        * needed) @return null if not available
-        */
-       public String getImageUrl(M node);
-
-//     public Binary getImageBinary(Node node) throws RepositoryException;
-
-//     public Image getSwtImage(Node node) throws RepositoryException;
-
-       /** @return URL */
-       public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
-
-       @Deprecated
-       default String uploadImage(M uploadFolder, String fileName, InputStream in) {
-               System.err.println("Context must be provided to " + CmsImageManager.class.getName());
-               return uploadImage(null, uploadFolder, fileName, in, null);
-       }
-}
index 3454dfc613745936247c23a93653168994b2ad02..96a09a91b1bfdb496cdf857a0ad4af3d2171aca0 100644 (file)
@@ -1,16 +1,45 @@
 package org.argeo.api.cms;
 
 import java.lang.System.Logger;
-import java.lang.System.Logger.Level;
+import java.text.MessageFormat;
 import java.util.Objects;
 import java.util.function.Supplier;
 
 /**
- * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
- * platform {@link Logger}.
+ * A Commons Logging / SLF4J style logging utilities usually wrapping a standard
+ * Java platform {@link Logger}, but which can fallback to other mechanism, if a
+ * system logger is not available.
  */
 public interface CmsLog {
-       Logger getLogger();
+       /*
+        * SYSTEM LOGGER STYLE METHODS
+        */
+       boolean isLoggable(Level level);
+
+       void log(Level level, Supplier<String> msgSupplier, Throwable thrown);
+
+       void log(Level level, String msg, Throwable thrown);
+
+       void log(Level level, String format, Object... params);
+
+       default void log(Level level, String msg) {
+               log(level, msg, (Throwable) null);
+       }
+
+       default void log(Level level, Supplier<String> msgSupplier) {
+               log(level, msgSupplier, (Throwable) null);
+       }
+
+       default void log(Level level, Object obj) {
+               Objects.requireNonNull(obj);
+               log(level, obj.toString());
+       }
+
+       /*
+        * SLF4j / COMMONS LOGGING STYLE METHODS
+        */
+       @Deprecated
+       CmsLog getLogger();
 
        default boolean isDebugEnabled() {
                return getLogger().isLoggable(Level.DEBUG);
@@ -172,6 +201,30 @@ public interface CmsLog {
                getLogger().log(Level.ERROR, format, arguments);
        }
 
+       /**
+        * Exact mapping of ${java.lang.System.Logger.Level}, in case it is not
+        * available.
+        */
+       public static enum Level {
+               ALL(Integer.MIN_VALUE), //
+               TRACE(400), //
+               DEBUG(500), //
+               INFO(800), //
+               WARNING(900), //
+               ERROR(1000), //
+               OFF(Integer.MAX_VALUE); //
+
+               final int severity;
+
+               private Level(int severity) {
+                       this.severity = severity;
+               }
+
+               public final int getSeverity() {
+                       return severity;
+               }
+       }
+
        /*
         * STATIC UTILITIES
         */
@@ -181,23 +234,123 @@ public interface CmsLog {
        }
 
        static CmsLog getLog(String name) {
-               Logger logger = System.getLogger(Objects.requireNonNull(name));
-               return new LoggerWrapper(logger);
+               if (isSystemLoggerAvailable) {
+                       return new SystemCmsLog(name);
+               } else { // typically Android
+                       return new FallBackCmsLog();
+               }
        }
 
-       /** A trivial implementation wrapping a platform logger. */
-       static class LoggerWrapper implements CmsLog {
-               private final Logger logger;
+       static final boolean isSystemLoggerAvailable = isSystemLoggerAvailable();
 
-               LoggerWrapper(Logger logger) {
-                       this.logger = logger;
+       static boolean isSystemLoggerAvailable() {
+               try {
+                       Logger logger = System.getLogger(CmsLog.class.getName());
+                       logger.log(java.lang.System.Logger.Level.TRACE, () -> "System logger is available.");
+                       return true;
+               } catch (NoSuchMethodError | NoClassDefFoundError e) {// Android
+                       return false;
                }
+       }
+}
 
-               @Override
-               public Logger getLogger() {
-                       return logger;
+/**
+ * Uses {@link System.Logger}, should be used on proper implementations of the
+ * Java platform.
+ */
+class SystemCmsLog implements CmsLog {
+       private final Logger logger;
+
+       SystemCmsLog(String name) {
+               logger = System.getLogger(name);
+       }
+
+       @Override
+       public boolean isLoggable(Level level) {
+               return logger.isLoggable(convertSystemLevel(level));
+       }
+
+       @Override
+       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+               logger.log(convertSystemLevel(level), msgSupplier, thrown);
+       }
+
+       @Override
+       public void log(Level level, String msg, Throwable thrown) {
+               logger.log(convertSystemLevel(level), msg, thrown);
+       }
+
+       java.lang.System.Logger.Level convertSystemLevel(Level level) {
+               switch (level.severity) {
+               case Integer.MIN_VALUE:
+                       return java.lang.System.Logger.Level.ALL;
+               case 400:
+                       return java.lang.System.Logger.Level.TRACE;
+               case 500:
+                       return java.lang.System.Logger.Level.DEBUG;
+               case 800:
+                       return java.lang.System.Logger.Level.INFO;
+               case 900:
+                       return java.lang.System.Logger.Level.WARNING;
+               case 1000:
+                       return java.lang.System.Logger.Level.ERROR;
+               case Integer.MAX_VALUE:
+                       return java.lang.System.Logger.Level.OFF;
+               default:
+                       throw new IllegalArgumentException("Unexpected value: " + level.severity);
                }
+       }
+
+       @Override
+       public void log(Level level, String format, Object... params) {
+               logger.log(convertSystemLevel(level), format, params);
+       }
 
+       @Override
+       public CmsLog getLogger() {
+               return this;
        }
+};
 
+/** Dummy fallback for non-standard platforms such as Android. */
+class FallBackCmsLog implements CmsLog {
+       @Override
+       public boolean isLoggable(Level level) {
+               return level.getSeverity() >= 800;// INFO and higher
+       }
+
+       @Override
+       public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+               if (isLoggable(level))
+                       if (thrown != null || level.getSeverity() >= 900) {
+                               System.err.println(msgSupplier.get());
+                               thrown.printStackTrace();
+                       } else {
+                               System.out.println(msgSupplier.get());
+                       }
+       }
+
+       @Override
+       public void log(Level level, String msg, Throwable thrown) {
+               if (isLoggable(level))
+                       if (thrown != null || level.getSeverity() >= 900) {
+                               System.err.println(msg);
+                               thrown.printStackTrace();
+                       } else {
+                               System.out.println(msg);
+                       }
+       }
+
+       @Override
+       public void log(Level level, String format, Object... params) {
+               if (format == null)
+                       return;
+               String msg = MessageFormat.format(format, params);
+               log(level, msg);
+       }
+
+       @Override
+       public CmsLog getLogger() {
+               return this;
+       }
 }
index ea9d10ba24d1573ed74df47bf9049e07969f7ed1..dda1dac1f7fa118ae4247b93e21bf7963c11e689 100644 (file)
@@ -5,7 +5,6 @@ import java.util.Locale;
 import java.util.UUID;
 import java.util.function.Consumer;
 
-import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 
 /** An authenticated user session. */
@@ -18,7 +17,7 @@ public interface CmsSession {
 
        String getUserRole();
 
-       LdapName getUserDn();
+       String getUserDn();
 
        String getLocalId();
 
@@ -40,4 +39,8 @@ public interface CmsSession {
        void registerView(String uid, Object view);
 
        void addOnCloseCallback(Consumer<CmsSession> onClose);
+
+       public static boolean hasCmsSession(Subject subject) {
+               return !subject.getPrivateCredentials(CmsSessionId.class).isEmpty();
+       }
 }
index ed8698fcaccdb2aabe25ad6fd1de360c779aecfc..181e4b9c661f716424f0d183bf97514431680b51 100644 (file)
@@ -1,9 +1,24 @@
 package org.argeo.api.cms;
 
+import java.nio.file.Path;
+import java.util.List;
+import java.util.UUID;
+
 /** A running node process. */
 public interface CmsState {
        String getHostname();
 
        Long getAvailableSince();
 
+       UUID getUuid();
+
+       String getDeployProperty(String property);
+
+       /**
+        * A list of size of the max count for this property, with null values when the
+        * property is not set, or an empty list (size 0) if this property is unknown.
+        */
+       List<String> getDeployProperties(String property);
+
+       Path getDataPath(String relativePath);
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java
deleted file mode 100644 (file)
index 8444e2f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.api.cms;
-
-/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
-public interface CmsStyle {
-       String name();
-
-       /** @deprecated use {@link #style()} instead. */
-       @Deprecated
-       default String toStyleClass() {
-               return style();
-       }
-
-       default String style() {
-               String classPrefix = getClassPrefix();
-               return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
-       }
-
-       default String getClassPrefix() {
-               return "";
-       }
-
-}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java
deleted file mode 100644 (file)
index 50c3b1f..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.argeo.api.cms;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Set;
-
-/** A CMS theme which can be applied to web apps as well as desktop apps. */
-public interface CmsTheme {
-       /** Unique ID of this theme. */
-       String getThemeId();
-
-       /**
-        * Load a resource as an input stream, base don its relative path, or
-        * <code>null</code> if not found
-        */
-       InputStream getResourceAsStream(String resourceName) throws IOException;
-
-       /** Relative paths to standard web CSS. */
-       Set<String> getWebCssPaths();
-
-       /** Relative paths to RAP specific CSS. */
-       Set<String> getRapCssPaths();
-
-       /** Relative paths to SWT specific CSS. */
-       Set<String> getSwtCssPaths();
-
-       /** Relative paths to images such as icons. */
-       Set<String> getImagesPaths();
-
-       /** Relative paths to fonts. */
-       Set<String> getFontsPaths();
-
-       /** Tags to be added to the header section of the HTML page. */
-       String getHtmlHeaders();
-
-       /** The HTML body to use. */
-       String getBodyHtml();
-
-       /** The default icon size (typically the smallest). */
-       Integer getDefaultIconSize();
-
-       /** Loads one of the relative path provided by the other methods. */
-       InputStream loadPath(String path) throws IOException;
-
-}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java
deleted file mode 100644 (file)
index fd91c6e..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.api.cms;
-
-public interface CmsUi {
-       Object getData(String key);
-       void setData(String key, Object value);
-
-}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java
deleted file mode 100644 (file)
index c7ca1e9..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.argeo.api.cms;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.login.LoginContext;
-
-/** Provides interaction with the CMS system. */
-public interface CmsView {
-       final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
-       // String KEY = "org.argeo.cms.ui.view";
-
-       String getUid();
-
-       UxContext getUxContext();
-
-       // NAVIGATION
-       void navigateTo(String state);
-
-       // SECURITY
-       void authChange(LoginContext loginContext);
-
-       void logout();
-
-       // void registerCallbackHandler(CallbackHandler callbackHandler);
-
-       // SERVICES
-       void exception(Throwable e);
-
-       CmsImageManager<?, ?> getImageManager();
-
-       boolean isAnonymous();
-
-       /**
-        * Send an event to this topic. Does nothing by default., but if implemented it
-        * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
-        */
-       default void sendEvent(String topic, Map<String, Object> properties) {
-
-       }
-
-       /**
-        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
-        * one single parameter.
-        */
-       default void sendEvent(String topic, String param, Object value) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(param, value);
-               sendEvent(topic, properties);
-       }
-
-       default void applyStyles(Object widget) {
-
-       }
-
-       default <T> T doAs(PrivilegedAction<T> action) {
-               throw new UnsupportedOperationException();
-       }
-
-       default Void runAs(Runnable runnable) {
-               return doAs(new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               if (runnable != null)
-                                       runnable.run();
-                               return null;
-                       }
-               });
-       }
-
-       default void stateChanged(String state, String title) {
-       }
-
-       default CmsSession getCmsSession() {
-               throw new UnsupportedOperationException();
-       }
-
-       default Object getData(String key) {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings("unchecked")
-       default <T> T getUiContext(Class<T> clss) {
-               return (T) getData(clss.getName());
-       }
-
-       default <T> void setUiContext(Class<T> clss, T instance) {
-               setData(clss.getName(), instance);
-       }
-
-       default void setData(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-}
index bc12bcbe20694c2d735453e9b57b96b5239d5e33..70c50ee9b8eef5ba8f108bcefbdb942ee715855d 100644 (file)
@@ -2,6 +2,8 @@ package org.argeo.api.cms;
 
 import java.security.Principal;
 
+import javax.security.auth.Subject;
+
 /** Allows to modify any data. */
 public final class DataAdminPrincipal implements Principal {
        private final String name = CmsConstants.ROLE_DATA_ADMIN;
@@ -26,4 +28,7 @@ public final class DataAdminPrincipal implements Principal {
                return name.toString();
        }
 
+       public static boolean isDataAdmin(Subject subject) {
+               return !subject.getPrincipals(DataAdminPrincipal.class).isEmpty();
+       }
 }
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java
deleted file mode 100644 (file)
index c1aa600..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.api.cms;
-
-import java.util.function.BiFunction;
-
-/**
- * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
- * order to create a view part (W) which can then be further configured. Such
- * object can be used as services and reference other part of the model which
- * are relevant for all created UI part.
- */
-@FunctionalInterface
-public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
-       W createUiPart(V parent, M context);
-       
-       /**
-        * Whether this parent view is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isViewSupported(V parent) {
-               return true;
-       }
-
-       /**
-        * Whether this context is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isModelSupported(M context) {
-               return true;
-       }
-
-       default W apply(V parent, M context) {
-               if (!isViewSupported(parent))
-                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
-               if (!isModelSupported(context))
-                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
-               return createUiPart(parent, context);
-       }
-
-       default W createUiPart(V parent) {
-               return createUiPart(parent, null);
-       }
-}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java
deleted file mode 100644 (file)
index fb99178..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.api.cms;
-
-public interface UxContext {
-       boolean isPortrait();
-
-       boolean isLandscape();
-
-       boolean isSquare();
-
-       boolean isSmall();
-
-       /**
-        * Is a production environment (must be false by default, and be explicitly
-        * set during the CMS deployment). When false, it can activate additional UI
-        * capabilities in order to facilitate QA.
-        */
-       boolean isMasterData();
-}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsAuthorization.java
new file mode 100644 (file)
index 0000000..5d3a695
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Authorization;
+
+/** An authorisation to a CMS system. */
+public interface CmsAuthorization extends Authorization {
+       /** The role which did imply this role, <code>null</code> if a direct role. */
+       default String getImplyingRole(String role) {
+               return null;
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsDirectory.java
new file mode 100644 (file)
index 0000000..f5b78ac
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.api.cms.directory;
+
+import java.util.Optional;
+
+import org.argeo.api.cms.transaction.WorkControl;
+
+/** An information directory (typically LDAP). */
+public interface CmsDirectory extends HierarchyUnit {
+       String getName();
+
+       /** Whether this directory is read only. */
+       boolean isReadOnly();
+
+       /** Whether this directory is disabled. */
+       boolean isDisabled();
+
+       /** The realm (typically Kerberos) of this directory. */
+       Optional<String> getRealm();
+
+       /** Sets the transaction control used by this directory when editing. */
+       void setTransactionControl(WorkControl transactionControl);
+
+       /*
+        * HIERARCHY
+        */
+
+       /** The hierarchy unit at this path. */
+       HierarchyUnit getHierarchyUnit(String path);
+
+       /** Create a new hierarchy unit. */
+       HierarchyUnit createHierarchyUnit(String path);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsGroup.java
new file mode 100644 (file)
index 0000000..410d391
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Group;
+
+/** A group in a user directroy. */
+public interface CmsGroup extends Group, CmsUser {
+//     List<LdapName> getMemberNames();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUser.java
new file mode 100644 (file)
index 0000000..f8f40a1
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * An entity with credentials which can log in to a system. Can be a real person
+ * or not.
+ */
+public interface CmsUser extends User {
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/CmsUserManager.java
new file mode 100644 (file)
index 0000000..7693f67
--- /dev/null
@@ -0,0 +1,125 @@
+package org.argeo.api.cms.directory;
+
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/**
+ * Provide method interfaces to manage user concepts without accessing directly
+ * the userAdmin.
+ */
+public interface CmsUserManager {
+       Map<String, String> getKnownBaseDns(boolean onlyWritable);
+
+       Set<UserDirectory> getUserDirectories();
+
+       // CurrentUser
+       /** Returns the e-mail of the current logged in user */
+       String getMyMail();
+
+       // Other users
+       /** Returns a {@link User} given a username */
+       CmsUser getUser(String username);
+
+       /** Can be a group or a user */
+       String getUserDisplayName(String dn);
+
+       /** Can be a group or a user */
+       String getUserMail(String dn);
+
+       /** Lists all roles of the given user */
+       String[] getUserRoles(String dn);
+
+       /** Checks if the passed user belongs to the passed role */
+       boolean isUserInRole(String userDn, String roleDn);
+
+       // Search
+       /** Returns a filtered list of roles */
+       Role[] getRoles(String filter);
+
+       /** Recursively lists users in a given group. */
+       Set<CmsUser> listUsersInGroup(String groupDn, String filter);
+
+       /** Search among groups including system roles and users if needed */
+       List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
+
+//     /**
+//      * Lists functional accounts, that is users with regular access to the system
+//      * under this functional hierarchy unit (which probably have technical direct
+//      * sub hierarchy units), excluding groups which are not explicitly users.
+//      */
+//     Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep);
+
+       /*
+        * EDITION
+        */
+       /** Creates a new user. */
+       CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials);
+
+       /** Created a new group. */
+       CmsGroup createGroup(String dn);
+
+       /** Creates a group. */
+       CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName);
+
+       /** Creates a new system role. */
+       CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole);
+
+       /** Add additional object classes to this role. */
+       void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties);
+
+       /** Add additional object classes to this hierarchy unit. */
+       void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+                       Map<String, Object> additionalProperties);
+
+       /** Add a member to this group. */
+       void addMember(CmsGroup group, Role role);
+
+       /** Remove a member from this group. */
+       void removeMember(CmsGroup group, Role role);
+       
+       void edit(Runnable action);
+
+       /* MISCELLANEOUS */
+       /** Returns the dn of a role given its local ID */
+       String buildDefaultDN(String localId, int type);
+
+       /** Exposes the main default domain name for this instance */
+       String getDefaultDomainName();
+
+       /**
+        * Search for a {@link User} (might also be a group) whose uid or cn is equals
+        * to localId within the various user repositories defined in the current
+        * context.
+        */
+       CmsUser getUserFromLocalId(String localId);
+
+       void changeOwnPassword(char[] oldPassword, char[] newPassword);
+
+       void resetPassword(String username, char[] newPassword);
+
+       @Deprecated
+       String addSharedSecret(String username, int hours);
+
+//     String addSharedSecret(String username, String authInfo, String authToken);
+
+       void addAuthToken(String userDn, String token, Integer hours, String... roles);
+
+       void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
+
+       void expireAuthToken(String token);
+
+       void expireAuthTokens(Subject subject);
+
+       UserDirectory getDirectory(Role role);
+
+       /** Create a new hierarchy unit. Does nothing if it already exists. */
+       HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path);
+}
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/DirectoryDigestUtils.java
new file mode 100644 (file)
index 0000000..dabcfe8
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.api.cms.directory;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/** Utilities around digests, mostly those related to passwords. */
+public class DirectoryDigestUtils {
+       public final static String PASSWORD_SCHEME_SHA = "SHA";
+       public final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
+
+       public static byte[] sha1(byte[] bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance("SHA1");
+                       digest.update(bytes);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalStateException("Cannot SHA1 digest", e);
+               }
+       }
+
+       public static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
+                       Integer keyLength) {
+               try {
+                       if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+                               MessageDigest digest = MessageDigest.getInstance("SHA1");
+                               byte[] bytes = charsToBytes(password);
+                               digest.update(bytes);
+                               return digest.digest();
+                       } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+                               KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
+
+                               SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+                               final int ITERATION_LENGTH = 4;
+                               byte[] key = f.generateSecret(spec).getEncoded();
+                               byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
+                               byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
+                               if (iterationsArr.length < ITERATION_LENGTH) {
+                                       Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
+                                       System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
+                                                       iterationsArr.length);
+                               } else {
+                                       System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
+                               }
+                               System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
+                               System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
+                               return result;
+                       } else {
+                               throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
+                       }
+               } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+                       throw new IllegalStateException("Cannot digest", e);
+               }
+       }
+
+       public static char[] bytesToChars(Object obj) {
+               if (obj instanceof char[])
+                       return (char[]) obj;
+               if (!(obj instanceof byte[]))
+                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+               // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+               // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+               // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+               return res;
+       }
+
+       public static byte[] charsToBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
+       public static String sha1str(String str) {
+               byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
+               return encodeHexString(hash);
+       }
+
+       final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+       /**
+        * From
+        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+        * -a-hex-string-in-java
+        */
+       public static String encodeHexString(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 2];
+               for (int j = 0; j < bytes.length; j++) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 2] = hexArray[v >>> 4];
+                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+               }
+               return new String(hexChars);
+       }
+
+       /** singleton */
+       private DirectoryDigestUtils() {
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/HierarchyUnit.java
new file mode 100644 (file)
index 0000000..52509e8
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.api.cms.directory;
+
+import java.util.Dictionary;
+import java.util.Locale;
+
+/** A unit within the high-level organisational structure of a directory. */
+public interface HierarchyUnit {
+       /** Name to use in paths. */
+       String getHierarchyUnitName();
+
+       /** Name to use in UI. */
+       String getHierarchyUnitLabel(Locale locale);
+
+       /**
+        * The parent {@link HierarchyUnit}, or <code>null</code> if a
+        * {@link CmsDirectory}.
+        */
+       HierarchyUnit getParent();
+
+       /** Direct children {@link HierarchyUnit}s. */
+       Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly);
+
+       /**
+        * Whether this is an arbitrary named and placed {@link HierarchyUnit}.
+        * 
+        * @return <code>true</code> if functional, <code>false</code> is technical
+        *         (e.g. People, Groups, etc.)
+        */
+       default boolean isFunctional() {
+               return isType(Type.FUNCTIONAL);
+       }
+
+       boolean isType(Type type);
+
+       /** A technical direct child. */
+       HierarchyUnit getDirectChild(Type type);
+
+       /**
+        * The base of this organisational unit within the hierarchy. This would
+        * typically be an LDAP base DN.
+        */
+       String getBase();
+
+       /** The related {@link CmsDirectory}. */
+       CmsDirectory getDirectory();
+
+       /** Its metadata (typically LDAP attributes). */
+       Dictionary<String, Object> getProperties();
+
+       enum Type {
+               PEOPLE, //
+               GROUPS, //
+               ROLES, //
+               FUNCTIONAL;
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java b/org.argeo.api.cms/src/org/argeo/api/cms/directory/UserDirectory.java
new file mode 100644 (file)
index 0000000..1f0ecdf
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.api.cms.directory;
+
+import org.osgi.service.useradmin.Role;
+
+/** Information about a user directory. */
+public interface UserDirectory extends CmsDirectory {
+
+       HierarchyUnit getHierarchyUnit(Role role);
+
+       Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep);
+
+       String getRolePath(Role role);
+
+       String getRoleSimpleName(Role role);
+
+       Role getRoleByPath(String path);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/CryptoKeyring.java
new file mode 100644 (file)
index 0000000..454f8b4
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.api.cms.keyring;
+
+/**
+ * Marker interface for an advanced keyring based on cryptography.
+ */
+public interface CryptoKeyring extends Keyring {
+       public void changePassword(char[] oldPassword, char[] newPassword);
+
+       public void unlock(char[] password);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/Keyring.java
new file mode 100644 (file)
index 0000000..efc9455
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.api.cms.keyring;
+
+import java.io.InputStream;
+
+/**
+ * Access to private (typically encrypted) data. The keyring is responsible for
+ * retrieving the necessary credentials. <b>Experimental. This API may
+ * change.</b>
+ */
+public interface Keyring {
+       /**
+        * Returns the confidential information as chars. Must ask for it if it is
+        * not stored.
+        */
+       public char[] getAsChars(String path);
+
+       /**
+        * Returns the confidential information as a stream. Must ask for it if it
+        * is not stored.
+        */
+       public InputStream getAsStream(String path);
+
+       public void set(String path, char[] arr);
+
+       public void set(String path, InputStream in);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/PBEKeySpecCallback.java
new file mode 100644 (file)
index 0000000..6a7ce19
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.api.cms.keyring;
+
+import javax.crypto.spec.PBEKeySpec;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.PasswordCallback;
+
+/**
+ * All information required to set up a {@link PBEKeySpec} bar the password
+ * itself (use a {@link PasswordCallback})
+ */
+public class PBEKeySpecCallback implements Callback {
+       private String secretKeyFactory;
+       private byte[] salt;
+       private Integer iterationCount;
+       /** Can be null for some algorithms */
+       private Integer keyLength;
+       /** Can be null, will trigger secret key encryption if not */
+       private String secretKeyEncryption;
+
+       private String encryptedPasswordHashCipher;
+       private byte[] encryptedPasswordHash;
+
+       public void set(String secretKeyFactory, byte[] salt,
+                       Integer iterationCount, Integer keyLength,
+                       String secretKeyEncryption) {
+               this.secretKeyFactory = secretKeyFactory;
+               this.salt = salt;
+               this.iterationCount = iterationCount;
+               this.keyLength = keyLength;
+               this.secretKeyEncryption = secretKeyEncryption;
+//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
+//             this.encryptedPasswordHash = encryptedPasswordHash;
+       }
+
+       public String getSecretKeyFactory() {
+               return secretKeyFactory;
+       }
+
+       public byte[] getSalt() {
+               return salt;
+       }
+
+       public Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       public Integer getKeyLength() {
+               return keyLength;
+       }
+
+       public String getSecretKeyEncryption() {
+               return secretKeyEncryption;
+       }
+
+       public String getEncryptedPasswordHashCipher() {
+               return encryptedPasswordHashCipher;
+       }
+
+       public byte[] getEncryptedPasswordHash() {
+               return encryptedPasswordHash;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java b/org.argeo.api.cms/src/org/argeo/api/cms/keyring/package-info.java
new file mode 100644 (file)
index 0000000..7f61e09
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS reusable security components. */
+package org.argeo.api.cms.keyring;
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/AbstractWorkingCopy.java
new file mode 100644 (file)
index 0000000..928acad
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractWorkingCopy<DATA, ATTR, ID> implements WorkingCopy<DATA, ATTR, ID> {
+       private Map<ID, DATA> newData = new HashMap<ID, DATA>();
+       private Map<ID, ATTR> modifiedData = new HashMap<ID, ATTR>();
+       private Map<ID, DATA> deletedData = new HashMap<ID, DATA>();
+
+       protected abstract ID getId(DATA data);
+
+       protected abstract ATTR cloneAttributes(DATA data);
+
+       public void cleanUp() {
+               // clean collections
+               newData.clear();
+               newData = null;
+               modifiedData.clear();
+               modifiedData = null;
+               deletedData.clear();
+               deletedData = null;
+       }
+
+       public boolean noModifications() {
+               return newData.size() == 0 && modifiedData.size() == 0 && deletedData.size() == 0;
+       }
+
+       public void startEditing(DATA user) {
+               ID id = getId(user);
+               if (modifiedData.containsKey(id))
+                       throw new IllegalStateException("Already editing " + id);
+               modifiedData.put(id, cloneAttributes(user));
+       }
+
+       public Map<ID, DATA> getNewData() {
+               return newData;
+       }
+
+       public Map<ID, DATA> getDeletedData() {
+               return deletedData;
+       }
+
+       public Map<ID, ATTR> getModifiedData() {
+               return modifiedData;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/JtaStatusAdapter.java
new file mode 100644 (file)
index 0000000..2ba6c0d
--- /dev/null
@@ -0,0 +1,61 @@
+package org.argeo.api.cms.transaction;
+
+/** JTA transaction status. */
+public class JtaStatusAdapter implements TransactionStatusAdapter<Integer> {
+       private static final Integer STATUS_ACTIVE = 0;
+       private static final Integer STATUS_COMMITTED = 3;
+       private static final Integer STATUS_COMMITTING = 8;
+       private static final Integer STATUS_MARKED_ROLLBACK = 1;
+       private static final Integer STATUS_NO_TRANSACTION = 6;
+       private static final Integer STATUS_PREPARED = 2;
+       private static final Integer STATUS_PREPARING = 7;
+       private static final Integer STATUS_ROLLEDBACK = 4;
+       private static final Integer STATUS_ROLLING_BACK = 9;
+//     private static final Integer STATUS_UNKNOWN = 5;
+
+       @Override
+       public Integer getActiveStatus() {
+               return STATUS_ACTIVE;
+       }
+
+       @Override
+       public Integer getPreparingStatus() {
+               return STATUS_PREPARING;
+       }
+
+       @Override
+       public Integer getMarkedRollbackStatus() {
+               return STATUS_MARKED_ROLLBACK;
+       }
+
+       @Override
+       public Integer getPreparedStatus() {
+               return STATUS_PREPARED;
+       }
+
+       @Override
+       public Integer getCommittingStatus() {
+               return STATUS_COMMITTING;
+       }
+
+       @Override
+       public Integer getCommittedStatus() {
+               return STATUS_COMMITTED;
+       }
+
+       @Override
+       public Integer getRollingBackStatus() {
+               return STATUS_ROLLING_BACK;
+       }
+
+       @Override
+       public Integer getRolledBackStatus() {
+               return STATUS_ROLLEDBACK;
+       }
+
+       @Override
+       public Integer getNoTransactionStatus() {
+               return STATUS_NO_TRANSACTION;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleRollbackException.java
new file mode 100644 (file)
index 0000000..39ed9b9
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.cms.transaction;
+
+/** Internal unchecked rollback exception. */
+class SimpleRollbackException extends RuntimeException {
+       private static final long serialVersionUID = 8055601819719780566L;
+
+       public SimpleRollbackException() {
+               super();
+       }
+
+       public SimpleRollbackException(Throwable cause) {
+               super(cause);
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransaction.java
new file mode 100644 (file)
index 0000000..f2bb907
--- /dev/null
@@ -0,0 +1,160 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** Simple implementation of an XA transaction. */
+class SimpleTransaction<T>
+//implements Transaction, Status 
+{
+       private final Xid xid;
+       private T status;
+       private final List<XAResource> xaResources = new ArrayList<XAResource>();
+
+       private final SimpleTransactionManager transactionManager;
+       private TransactionStatusAdapter<T> tsa;
+
+       public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter<T> tsa) {
+               this.tsa = tsa;
+               this.status = tsa.getActiveStatus();
+               this.xid = new UuidXid();
+               this.transactionManager = transactionManager;
+       }
+
+       public synchronized void commit()
+//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+//                     SecurityException, IllegalStateException, SystemException 
+       {
+               status = tsa.getPreparingStatus();
+               for (XAResource xaRes : xaResources) {
+                       if (status.equals(tsa.getMarkedRollbackStatus()))
+                               break;
+                       try {
+                               xaRes.prepare(xid);
+                       } catch (XAException e) {
+                               status = tsa.getMarkedRollbackStatus();
+                               error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status.equals(tsa.getMarkedRollbackStatus())) {
+                       rollback();
+                       throw new SimpleRollbackException();
+               }
+               status = tsa.getPreparedStatus();
+
+               status = tsa.getCommittingStatus();
+               for (XAResource xaRes : xaResources) {
+                       if (status.equals(tsa.getMarkedRollbackStatus()))
+                               break;
+                       try {
+                               xaRes.commit(xid, false);
+                       } catch (XAException e) {
+                               status = tsa.getMarkedRollbackStatus();
+                               error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status.equals(tsa.getMarkedRollbackStatus())) {
+                       rollback();
+                       throw new SimpleRollbackException();
+               }
+
+               // complete
+               status = tsa.getCommittedStatus();
+               clearResources(XAResource.TMSUCCESS);
+               transactionManager.unregister(xid);
+       }
+
+       public synchronized void rollback()
+//                     throws IllegalStateException, SystemException 
+       {
+               status = tsa.getRollingBackStatus();
+               for (XAResource xaRes : xaResources) {
+                       try {
+                               xaRes.rollback(xid);
+                       } catch (XAException e) {
+                               error("Cannot rollback " + xaRes + " for " + xid, e);
+                       }
+               }
+
+               // complete
+               status = tsa.getRolledBackStatus();
+               clearResources(XAResource.TMFAIL);
+               transactionManager.unregister(xid);
+       }
+
+       public synchronized boolean enlistResource(XAResource xaRes)
+//                     throws RollbackException, IllegalStateException, SystemException 
+       {
+               if (xaResources.add(xaRes)) {
+                       try {
+                               xaRes.start(getXid(), XAResource.TMNOFLAGS);
+                               return true;
+                       } catch (XAException e) {
+                               error("Cannot enlist " + xaRes, e);
+                               return false;
+                       }
+               } else
+                       return false;
+       }
+
+       public synchronized boolean delistResource(XAResource xaRes, int flag)
+//                     throws IllegalStateException, SystemException 
+       {
+               if (xaResources.remove(xaRes)) {
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               error("Cannot delist " + xaRes, e);
+                               return false;
+                       }
+                       return true;
+               } else
+                       return false;
+       }
+
+       protected void clearResources(int flag) {
+               for (XAResource xaRes : xaResources)
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               error("Cannot end " + xaRes, e);
+                       }
+               xaResources.clear();
+       }
+
+       protected void error(Object obj, Exception e) {
+               System.err.println(obj);
+               e.printStackTrace();
+       }
+
+       public synchronized T getStatus()
+//                     throws SystemException 
+       {
+               return status;
+       }
+
+//     public void registerSynchronization(Synchronization sync)
+//                     throws RollbackException, IllegalStateException, SystemException {
+//             throw new UnsupportedOperationException();
+//     }
+
+       public void setRollbackOnly()
+//                     throws IllegalStateException, SystemException 
+       {
+               status = tsa.getMarkedRollbackStatus();
+       }
+
+       @Override
+       public int hashCode() {
+               return xid.hashCode();
+       }
+
+       Xid getXid() {
+               return xid;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/SimpleTransactionManager.java
new file mode 100644 (file)
index 0000000..ee99ccb
--- /dev/null
@@ -0,0 +1,214 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * Simple implementation of an XA transaction manager.
+ */
+public class SimpleTransactionManager
+// implements TransactionManager, UserTransaction 
+               implements WorkControl, WorkTransaction {
+       private ThreadLocal<SimpleTransaction<Integer>> current = new ThreadLocal<SimpleTransaction<Integer>>();
+
+       private Map<Xid, SimpleTransaction<Integer>> knownTransactions = Collections
+                       .synchronizedMap(new HashMap<Xid, SimpleTransaction<Integer>>());
+       private TransactionStatusAdapter<Integer> tsa = new JtaStatusAdapter();
+//     private SyncRegistry syncRegistry = new SyncRegistry();
+
+       /*
+        * WORK IMPLEMENTATION
+        */
+       @Override
+       public <T> T required(Callable<T> work) {
+               T res;
+               begin();
+               try {
+                       res = work.call();
+                       commit();
+               } catch (Exception e) {
+                       rollback();
+                       throw new SimpleRollbackException(e);
+               }
+               return res;
+       }
+
+       @Override
+       public WorkContext getWorkContext() {
+               return new WorkContext() {
+
+                       @Override
+                       public void registerXAResource(XAResource resource, String recoveryId) {
+                               getTransaction().enlistResource(resource);
+                       }
+               };
+       }
+
+       /*
+        * WORK TRANSACTION IMPLEMENTATION
+        */
+
+       @Override
+       public boolean isNoTransactionStatus() {
+               return tsa.getNoTransactionStatus().equals(getStatus());
+       }
+
+       /*
+        * JTA IMPLEMENTATION
+        */
+
+       public void begin()
+//                     throws NotSupportedException, SystemException 
+       {
+               if (getCurrent() != null)
+                       throw new UnsupportedOperationException("Nested transactions are not supported");
+               SimpleTransaction<Integer> transaction = new SimpleTransaction<Integer>(this, tsa);
+               knownTransactions.put(transaction.getXid(), transaction);
+               current.set(transaction);
+       }
+
+       public void commit()
+//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+//                     SecurityException, IllegalStateException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().commit();
+       }
+
+       public int getStatus()
+//                     throws SystemException
+       {
+               if (getCurrent() == null)
+                       return tsa.getNoTransactionStatus();
+               return getTransaction().getStatus();
+       }
+
+       public SimpleTransaction<Integer> getTransaction()
+//                     throws SystemException 
+       {
+               return getCurrent();
+       }
+
+       protected SimpleTransaction<Integer> getCurrent()
+//                     throws SystemException 
+       {
+               SimpleTransaction<Integer> transaction = current.get();
+               if (transaction == null)
+                       return null;
+               Integer status = transaction.getStatus();
+               if (status.equals(tsa.getCommittedStatus()) || status.equals(tsa.getRolledBackStatus())) {
+                       current.remove();
+                       return null;
+               }
+               return transaction;
+       }
+
+       void unregister(Xid xid) {
+               knownTransactions.remove(xid);
+       }
+
+       public void resume(SimpleTransaction<Integer> tobj)
+//                     throws InvalidTransactionException, IllegalStateException, SystemException 
+       {
+               if (getCurrent() != null)
+                       throw new IllegalStateException("Transaction " + current.get() + " already registered");
+               current.set(tobj);
+       }
+
+       public void rollback()
+//                     throws IllegalStateException, SecurityException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().rollback();
+       }
+
+       public void setRollbackOnly()
+//                     throws IllegalStateException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().setRollbackOnly();
+       }
+
+       public void setTransactionTimeout(int seconds)
+//                     throws SystemException
+       {
+               throw new UnsupportedOperationException();
+       }
+
+       public SimpleTransaction<Integer> suspend()
+//                     throws SystemException
+       {
+               SimpleTransaction<Integer> transaction = getCurrent();
+               current.remove();
+               return transaction;
+       }
+
+//     public TransactionSynchronizationRegistry getTsr() {
+//             return syncRegistry;
+//     }
+//
+//     private class SyncRegistry implements TransactionSynchronizationRegistry {
+//             @Override
+//             public Object getTransactionKey() {
+//                     try {
+//                             SimpleTransaction transaction = getCurrent();
+//                             if (transaction == null)
+//                                     return null;
+//                             return getCurrent().getXid();
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get transaction key", e);
+//                     }
+//             }
+//
+//             @Override
+//             public void putResource(Object key, Object value) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public Object getResource(Object key) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public void registerInterposedSynchronization(Synchronization sync) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public int getTransactionStatus() {
+//                     try {
+//                             return getStatus();
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get status", e);
+//                     }
+//             }
+//
+//             @Override
+//             public boolean getRollbackOnly() {
+//                     try {
+//                             return getStatus() == Status.STATUS_MARKED_ROLLBACK;
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get status", e);
+//                     }
+//             }
+//
+//             @Override
+//             public void setRollbackOnly() {
+//                     try {
+//                             getCurrent().setRollbackOnly();
+//                     } catch (Exception e) {
+//                             throw new IllegalStateException("Cannot set rollback only", e);
+//                     }
+//             }
+//
+//     }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/TransactionStatusAdapter.java
new file mode 100644 (file)
index 0000000..ab4effd
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.api.cms.transaction;
+
+/** Abstract the various approaches to represent transaction status. */
+public interface TransactionStatusAdapter<T> {
+       T getActiveStatus();
+
+       T getPreparingStatus();
+
+       T getMarkedRollbackStatus();
+
+       T getPreparedStatus();
+
+       T getCommittingStatus();
+
+       T getCommittedStatus();
+
+       T getRollingBackStatus();
+
+       T getRolledBackStatus();
+
+       T getNoTransactionStatus();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/UuidXid.java
new file mode 100644 (file)
index 0000000..83358a5
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.api.cms.transaction;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Implementation of {@link Xid} based on {@link UUID}, using max significant
+ * bits as global transaction id, and least significant bits as branch
+ * qualifier.
+ */
+public class UuidXid implements Xid, Serializable {
+       private static final long serialVersionUID = -5380531989917886819L;
+       public final static int FORMAT = (int) serialVersionUID;
+
+       private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
+
+       private final int format;
+       private final byte[] globalTransactionId;
+       private final byte[] branchQualifier;
+       private final String uuid;
+       private final int hashCode;
+
+       public UuidXid() {
+               this(UUID.randomUUID());
+       }
+
+       public UuidXid(UUID uuid) {
+               this.format = FORMAT;
+               this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
+               this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
+               this.uuid = uuid.toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       public UuidXid(Xid xid) {
+               this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
+                               .getBranchQualifier());
+       }
+
+       private UuidXid(int format, byte[] globalTransactionId,
+                       byte[] branchQualifier) {
+               this.format = format;
+               this.globalTransactionId = globalTransactionId;
+               this.branchQualifier = branchQualifier;
+               this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
+                               .toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       @Override
+       public int getFormatId() {
+               return format;
+       }
+
+       @Override
+       public byte[] getGlobalTransactionId() {
+               return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
+       }
+
+       @Override
+       public byte[] getBranchQualifier() {
+               return Arrays.copyOf(branchQualifier, branchQualifier.length);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj instanceof UuidXid) {
+                       UuidXid that = (UuidXid) obj;
+                       return Arrays.equals(globalTransactionId, that.globalTransactionId)
+                                       && Arrays.equals(branchQualifier, that.branchQualifier);
+               }
+               if (obj instanceof Xid) {
+                       Xid that = (Xid) obj;
+                       return Arrays.equals(globalTransactionId,
+                                       that.getGlobalTransactionId())
+                                       && Arrays
+                                                       .equals(branchQualifier, that.getBranchQualifier());
+               }
+               return uuid.equals(obj.toString());
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new UuidXid(format, globalTransactionId, branchQualifier);
+       }
+
+       @Override
+       public String toString() {
+               return uuid;
+       }
+
+       public UUID asUuid() {
+               return bytesToUUID(globalTransactionId, branchQualifier);
+       }
+
+       public static byte[] uuidToBytes(long bits) {
+               ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
+               buffer.putLong(0, bits);
+               return buffer.array();
+       }
+
+       public static UUID bytesToUUID(byte[] most, byte[] least) {
+               if (most.length < BYTES_PER_LONG)
+                       most = Arrays.copyOf(most, BYTES_PER_LONG);
+               if (least.length < BYTES_PER_LONG)
+                       least = Arrays.copyOf(least, BYTES_PER_LONG);
+               ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
+               buffer.put(most, 0, BYTES_PER_LONG);
+               buffer.put(least, 0, BYTES_PER_LONG);
+               buffer.flip();
+               return new UUID(buffer.getLong(), buffer.getLong());
+       }
+
+       // public static void main(String[] args) {
+       // UUID uuid = UUID.randomUUID();
+       // System.out.println(uuid);
+       // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
+       // uuidToBytes(uuid.getLeastSignificantBits()));
+       // System.out.println(uuid);
+       // }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkContext.java
new file mode 100644 (file)
index 0000000..5493dde
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.cms.transaction;
+
+import javax.transaction.xa.XAResource;
+
+/**
+ * A minimalistic interface similar to OSGi transaction context in order to
+ * register XA resources.
+ */
+public interface WorkContext {
+       void registerXAResource(XAResource resource, String recoveryId);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkControl.java
new file mode 100644 (file)
index 0000000..de03150
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A minimalistic interface inspired by OSGi transaction control in order to
+ * commit units of work externally.
+ */
+public interface WorkControl {
+       <T> T required(Callable<T> work);
+
+       void setRollbackOnly();
+
+       WorkContext getWorkContext();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkTransaction.java
new file mode 100644 (file)
index 0000000..39c188d
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.cms.transaction;
+
+/**
+ * A minimalistic interface inspired by JTA user transaction in order to commit
+ * units of work externally.
+ */
+public interface WorkTransaction {
+       void begin();
+
+       void commit();
+
+       void rollback();
+
+       boolean isNoTransactionStatus();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopy.java
new file mode 100644 (file)
index 0000000..c79423c
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.Map;
+
+public interface WorkingCopy<DATA, ATTR, ID> {
+       void startEditing(DATA user);
+
+       boolean noModifications();
+
+       void cleanUp();
+
+       Map<ID, DATA> getNewData();
+
+       Map<ID, DATA> getDeletedData();
+
+       Map<ID, ATTR> getModifiedData();
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyProcessor.java
new file mode 100644 (file)
index 0000000..9e7c9e1
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.cms.transaction;
+
+public interface WorkingCopyProcessor<WC extends WorkingCopy<?, ?, ?>> {
+       void prepare(WC wc);
+
+       void commit(WC wc);
+
+       void rollback(WC wc);
+       
+       WC newWorkingCopy();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/WorkingCopyXaResource.java
new file mode 100644 (file)
index 0000000..16b08c2
--- /dev/null
@@ -0,0 +1,138 @@
+package org.argeo.api.cms.transaction;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** {@link XAResource} for a user directory being edited. */
+public class WorkingCopyXaResource<WC extends WorkingCopy<?, ?, ?>> implements XAResource {
+       private final WorkingCopyProcessor<WC> processor;
+
+       private Map<Xid, WC> workingCopies = new HashMap<Xid, WC>();
+       private Xid editingXid = null;
+       private int transactionTimeout = 0;
+
+       public WorkingCopyXaResource(WorkingCopyProcessor<WC> processor) {
+               this.processor = processor;
+       }
+
+       @Override
+       public synchronized void start(Xid xid, int flags) throws XAException {
+               if (editingXid != null)
+                       throw new IllegalStateException("Already editing " + editingXid);
+               WC wc = workingCopies.put(xid, processor.newWorkingCopy());
+               if (wc != null)
+                       throw new IllegalStateException("There is already a working copy for " + xid);
+               this.editingXid = xid;
+       }
+
+       @Override
+       public void end(Xid xid, int flags) throws XAException {
+               checkXid(xid);
+       }
+
+       private WC wc(Xid xid) {
+               return workingCopies.get(xid);
+       }
+
+       public synchronized WC wc() {
+               if (editingXid == null)
+                       return null;
+               WC wc = workingCopies.get(editingXid);
+               if (wc == null)
+                       throw new IllegalStateException("No working copy found for " + editingXid);
+               return wc;
+       }
+
+       private synchronized void cleanUp(Xid xid) {
+               WC wc = workingCopies.get(xid);
+               if (wc != null) {
+                       wc.cleanUp();
+                       workingCopies.remove(xid);
+               }
+               editingXid = null;
+       }
+
+       @Override
+       public int prepare(Xid xid) throws XAException {
+               checkXid(xid);
+               WC wc = wc(xid);
+               if (wc.noModifications())
+                       return XA_RDONLY;
+               try {
+                       processor.prepare(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               }
+               return XA_OK;
+       }
+
+       @Override
+       public void commit(Xid xid, boolean onePhase) throws XAException {
+               try {
+                       checkXid(xid);
+                       WC wc = wc(xid);
+                       if (wc.noModifications())
+                               return;
+                       if (onePhase)
+                               processor.prepare(wc);
+                       processor.commit(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void rollback(Xid xid) throws XAException {
+               try {
+                       checkXid(xid);
+                       processor.rollback(wc(xid));
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void forget(Xid xid) throws XAException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean isSameRM(XAResource xares) throws XAException {
+               return xares == this;
+       }
+
+       @Override
+       public Xid[] recover(int flag) throws XAException {
+               return new Xid[0];
+       }
+
+       @Override
+       public int getTransactionTimeout() throws XAException {
+               return transactionTimeout;
+       }
+
+       @Override
+       public boolean setTransactionTimeout(int seconds) throws XAException {
+               transactionTimeout = seconds;
+               return true;
+       }
+
+       private void checkXid(Xid xid) throws XAException {
+               if (xid == null)
+                       throw new XAException(XAException.XAER_OUTSIDE);
+               if (!xid.equals(xid))
+                       throw new XAException(XAException.XAER_NOTA);
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/XAResourceProvider.java
new file mode 100644 (file)
index 0000000..904fb5f
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.cms.transaction;
+
+import javax.transaction.xa.XAResource;
+
+public interface XAResourceProvider {
+       XAResource getXaResource();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java b/org.argeo.api.cms/src/org/argeo/api/cms/transaction/package-info.java
new file mode 100644 (file)
index 0000000..bbb9212
--- /dev/null
@@ -0,0 +1,2 @@
+/** Minimalistic and partial XA transaction manager implementation. */
+package org.argeo.api.cms.transaction;
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/Cms2DSize.java
new file mode 100644 (file)
index 0000000..1ec753a
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.api.cms.ux;
+
+/** A 2D size. */
+public class Cms2DSize {
+       private Integer width;
+       private Integer height;
+
+       public Cms2DSize() {
+       }
+
+       public Cms2DSize(Integer width, Integer height) {
+               super();
+               this.width = width;
+               this.height = height;
+       }
+
+       public Integer getWidth() {
+               return width;
+       }
+
+       public void setWidth(Integer width) {
+               this.width = width;
+       }
+
+       public Integer getHeight() {
+               return height;
+       }
+
+       public void setHeight(Integer height) {
+               this.height = height;
+       }
+
+       @Override
+       public String toString() {
+               return Cms2DSize.class.getSimpleName() + "[" + width + "," + height + "]";
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditable.java
new file mode 100644 (file)
index 0000000..94ed24e
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.api.cms.ux;
+
+/** Abstraction of a simple edition life cycle. */
+public interface CmsEditable {
+
+       /** Whether the calling thread can edit, the value is immutable */
+       Boolean canEdit();
+
+       Boolean isEditing();
+
+       void startEditing();
+
+       void stopEditing();
+
+       void addCmsEditionListener(CmsEditionListener listener);
+
+       void removeCmsEditionListener(CmsEditionListener listener);
+
+       static CmsEditable NON_EDITABLE = new CmsEditable() {
+
+               @Override
+               public void stopEditing() {
+               }
+
+               @Override
+               public void startEditing() {
+               }
+
+               @Override
+               public Boolean isEditing() {
+                       return false;
+               }
+
+               @Override
+               public Boolean canEdit() {
+                       return false;
+               }
+
+               @Override
+               public void addCmsEditionListener(CmsEditionListener listener) {
+               }
+
+               @Override
+               public void removeCmsEditionListener(CmsEditionListener listener) {
+               }
+
+       };
+
+       static CmsEditable ALWAYS_EDITING = new CmsEditable() {
+
+               @Override
+               public void stopEditing() {
+               }
+
+               @Override
+               public void startEditing() {
+               }
+
+               @Override
+               public Boolean isEditing() {
+                       return true;
+               }
+
+               @Override
+               public Boolean canEdit() {
+                       return true;
+               }
+
+               @Override
+               public void addCmsEditionListener(CmsEditionListener listener) {
+               }
+
+               @Override
+               public void removeCmsEditionListener(CmsEditionListener listener) {
+               }
+
+       };
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionEvent.java
new file mode 100644 (file)
index 0000000..e1765ab
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.api.cms.ux;
+
+import java.util.EventObject;
+
+/** Notify of the edition lifecycle */
+public class CmsEditionEvent extends EventObject {
+       private static final long serialVersionUID = 950914736016693110L;
+
+       public final static Integer START_EDITING = 0;
+       public final static Integer STOP_EDITING = 1;
+
+       private final Integer type;
+       private final CmsEditable cmsEditable;
+
+       public CmsEditionEvent(Object source, Integer type, CmsEditable cmsEditable) {
+               super(source);
+               this.type = type;
+               this.cmsEditable = cmsEditable;
+       }
+
+       public Integer getType() {
+               return type;
+       }
+
+       public CmsEditable getCmsEditable() {
+               return cmsEditable;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsEditionListener.java
new file mode 100644 (file)
index 0000000..e05c168
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.cms.ux;
+
+public interface CmsEditionListener {
+       void editionStarted(CmsEditionEvent e);
+
+       void editionStopped(CmsEditionEvent e);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsIcon.java
new file mode 100644 (file)
index 0000000..d4f86b2
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.api.cms.ux;
+
+/**
+ * Marker interface to be applied to {@link Enum}s in order to find or generate
+ * icons.
+ */
+public interface CmsIcon {
+       String name();
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsImageManager.java
new file mode 100644 (file)
index 0000000..1ec54a9
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.api.cms.ux;
+
+import java.io.InputStream;
+
+/** Read and write access to images. */
+public interface CmsImageManager<V, M> {
+       /** Load image in control */
+       public Boolean load(M node, V control, Cms2DSize size);
+
+       /** @return (0,0) if not available */
+       public Cms2DSize getImageSize(M node);
+
+       /**
+        * The related &lt;img&gt; tag, with src, width and height set.
+        * 
+        * @return null if not available
+        */
+       public String getImageTag(M node);
+
+       /**
+        * The related &lt;img&gt; tag, with url, width and height set. Caller must
+        * close the tag (or add additional attributes).
+        * 
+        * @return null if not available
+        */
+       public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
+
+       /**
+        * Returns the remotely accessible URL of the image (registering it if
+        * needed) @return null if not available
+        */
+       public String getImageUrl(M node);
+
+//     public Binary getImageBinary(Node node) throws RepositoryException;
+
+//     public Image getSwtImage(Node node) throws RepositoryException;
+
+       /** @return URL */
+       public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
+
+       @Deprecated
+       default String uploadImage(M uploadFolder, String fileName, InputStream in) {
+               System.err.println("Context must be provided to " + CmsImageManager.class.getName());
+               return uploadImage(null, uploadFolder, fileName, in, null);
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsStyle.java
new file mode 100644 (file)
index 0000000..e3f2e77
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.api.cms.ux;
+
+/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
+public interface CmsStyle {
+       String name();
+
+       /** @deprecated use {@link #style()} instead. */
+       @Deprecated
+       default String toStyleClass() {
+               return style();
+       }
+
+       default String style() {
+               String classPrefix = getClassPrefix();
+               return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
+       }
+
+       default String getClassPrefix() {
+               return "";
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsTheme.java
new file mode 100644 (file)
index 0000000..3a4a78e
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.api.cms.ux;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+/** A CMS theme which can be applied to web apps as well as desktop apps. */
+public interface CmsTheme {
+       /** Unique ID of this theme. */
+       String getThemeId();
+
+       /**
+        * Load a resource as an input stream, base don its relative path, or
+        * <code>null</code> if not found
+        */
+       InputStream getResourceAsStream(String resourceName) throws IOException;
+
+       /** Relative paths to standard web CSS. */
+       Set<String> getWebCssPaths();
+
+       /** Relative paths to RAP specific CSS. */
+       Set<String> getRapCssPaths();
+
+       /** Relative paths to SWT specific CSS. */
+       Set<String> getSwtCssPaths();
+
+       /** Relative paths to images such as icons. */
+       Set<String> getImagesPaths();
+
+       /** Relative paths to fonts. */
+       Set<String> getFontsPaths();
+
+       /** Tags to be added to the header section of the HTML page. */
+       String getHtmlHeaders();
+
+       /** The HTML body to use. */
+       String getBodyHtml();
+
+       /** The default icon size (typically the smallest). */
+       default int getDefaultIconSize() {
+               return getSmallIconSize();
+       }
+
+       int getSmallIconSize();
+
+       int getBigIconSize();
+
+       /** Loads one of the relative path provided by the other methods. */
+       InputStream loadPath(String path) throws IOException;
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsUi.java
new file mode 100644 (file)
index 0000000..2103e49
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.api.cms.ux;
+
+/** The actual implementation of a user interface, using a given technology. */
+public interface CmsUi {
+       Object getData(String key);
+
+       void setData(String key, Object value);
+       
+       CmsView getCmsView();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/CmsView.java
new file mode 100644 (file)
index 0000000..15b6a5d
--- /dev/null
@@ -0,0 +1,94 @@
+package org.argeo.api.cms.ux;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.api.cms.CmsSession;
+
+/** Provides interaction with the CMS system. */
+public interface CmsView {
+       final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
+       // String KEY = "org.argeo.cms.ui.view";
+
+       String getUid();
+
+       UxContext getUxContext();
+
+       // NAVIGATION
+       void navigateTo(String state);
+
+       // SECURITY
+       void authChange(LoginContext loginContext);
+
+       void logout();
+
+       // void registerCallbackHandler(CallbackHandler callbackHandler);
+
+       // SERVICES
+       void exception(Throwable e);
+
+       CmsImageManager<?, ?> getImageManager();
+
+       boolean isAnonymous();
+
+       /**
+        * Send an event to this topic. Does nothing by default., but if implemented it
+        * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
+        */
+       default void sendEvent(String topic, Map<String, Object> properties) {
+
+       }
+
+       /**
+        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
+        * one single parameter.
+        */
+       default void sendEvent(String topic, String param, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(param, value);
+               sendEvent(topic, properties);
+       }
+
+       default void applyStyles(Object widget) {
+
+       }
+
+       /**
+        * Make sure that this action is executed with the proper subject and in a
+        * proper thread.
+        */
+       <T> T doAs(Callable<T> action);
+
+       default void runAs(Runnable runnable) {
+               doAs(Executors.callable(runnable));
+       }
+
+       default void stateChanged(String state, String title) {
+       }
+
+       default CmsSession getCmsSession() {
+               throw new UnsupportedOperationException();
+       }
+
+       default Object getData(String key) {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings("unchecked")
+       default <T> T getUiContext(Class<T> clss) {
+               return (T) getData(clss.getName());
+       }
+
+       default <T> void setUiContext(Class<T> clss, T instance) {
+               setData(clss.getName(), instance);
+       }
+
+       default void setData(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/MvcProvider.java
new file mode 100644 (file)
index 0000000..3556ede
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.api.cms.ux;
+
+import java.util.function.BiFunction;
+
+/**
+ * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
+ * order to create a view part (W) which can then be further configured. Such
+ * object can be used as services and reference other part of the model which
+ * are relevant for all created UI part.
+ */
+@FunctionalInterface
+@Deprecated
+public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
+       W createUiPart(V parent, M context);
+       
+       /**
+        * Whether this parent view is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isViewSupported(V parent) {
+               return true;
+       }
+
+       /**
+        * Whether this context is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isModelSupported(M context) {
+               return true;
+       }
+
+       default W apply(V parent, M context) {
+               if (!isViewSupported(parent))
+                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
+               if (!isModelSupported(context))
+                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
+               return createUiPart(parent, context);
+       }
+
+       default W createUiPart(V parent) {
+               return createUiPart(parent, null);
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/ux/UxContext.java
new file mode 100644 (file)
index 0000000..adb41d3
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.api.cms.ux;
+
+public interface UxContext {
+       boolean isPortrait();
+
+       boolean isLandscape();
+
+       boolean isSquare();
+
+       boolean isSmall();
+
+       /**
+        * Is a production environment (must be false by default, and be explicitly
+        * set during the CMS deployment). When false, it can activate additional UI
+        * capabilities in order to facilitate QA.
+        */
+       boolean isMasterData();
+}
diff --git a/org.argeo.api.register/.classpath b/org.argeo.api.register/.classpath
new file mode 100644 (file)
index 0000000..4199cd3
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
+               <attributes>
+                       <attribute name="module" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.api.register/.project b/org.argeo.api.register/.project
new file mode 100644 (file)
index 0000000..a3164c1
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.register</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.register/META-INF/.gitignore b/org.argeo.api.register/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/org.argeo.api.register/bnd.bnd b/org.argeo.api.register/bnd.bnd
new file mode 100644 (file)
index 0000000..bcebf37
--- /dev/null
@@ -0,0 +1,3 @@
+Import-Package:        org.osgi.*;version=0.0.0,\
+!org.apache.commons.logging,\
+*                              
diff --git a/org.argeo.api.register/build.properties b/org.argeo.api.register/build.properties
new file mode 100644 (file)
index 0000000..ae2abc5
--- /dev/null
@@ -0,0 +1 @@
+source.. = src/
\ No newline at end of file
diff --git a/org.argeo.api.register/src/org/argeo/api/register/Component.java b/org.argeo.api.register/src/org/argeo/api/register/Component.java
new file mode 100644 (file)
index 0000000..f4676d9
--- /dev/null
@@ -0,0 +1,333 @@
+package org.argeo.api.register;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A wrapper for an object, whose dependencies and life cycle can be managed.
+ */
+public class Component<I> implements Supplier<I>, Comparable<Component<?>> {
+
+       private final I instance;
+
+       private final Runnable init;
+       private final Runnable close;
+
+       private final Map<Class<? super I>, PublishedType<? super I>> types;
+       private final Set<Dependency<?>> dependencies;
+       private final Map<String, Object> properties;
+
+       private CompletableFuture<Void> activationStarted = null;
+       private CompletableFuture<Void> activated = null;
+
+       private CompletableFuture<Void> deactivationStarted = null;
+       private CompletableFuture<Void> deactivated = null;
+
+       // internal
+       private Set<Dependency<?>> dependants = new HashSet<>();
+
+       private RankingKey rankingKey;
+
+       Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
+                       Set<Class<? super I>> classes, Map<String, Object> properties) {
+               assert instance != null;
+               assert init != null;
+               assert close != null;
+               assert dependencies != null;
+               assert classes != null;
+
+               this.instance = instance;
+               this.init = init;
+               this.close = close;
+
+               // types
+               Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
+               for (Class<? super I> clss : classes) {
+//                     if (!clss.isAssignableFrom(instance.getClass()))
+//                             throw new IllegalArgumentException(
+//                                             "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
+                       types.put(clss, new PublishedType<>(this, clss));
+               }
+               this.types = Collections.unmodifiableMap(types);
+
+               // dependencies
+               this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+               for (Dependency<?> dependency : this.dependencies) {
+                       dependency.setDependantComponent(this);
+               }
+
+               // deactivated by default
+               deactivated = CompletableFuture.completedFuture(null);
+               deactivationStarted = CompletableFuture.completedFuture(null);
+
+               // TODO check whether context is active, so that we start right away
+               prepareNextActivation();
+
+               long serviceId = register.register(this);
+               Map<String, Object> props = new HashMap<>(properties);
+               props.put(RankingKey.SERVICE_ID, serviceId);
+               this.properties = Collections.unmodifiableMap(props);
+               rankingKey = new RankingKey(properties);
+       }
+
+       private void prepareNextActivation() {
+               activationStarted = new CompletableFuture<Void>();
+               activated = activationStarted //
+                               .thenComposeAsync(this::dependenciesActivated) //
+                               .thenRun(this.init) //
+                               .thenRun(() -> prepareNextDeactivation());
+       }
+
+       private void prepareNextDeactivation() {
+               deactivationStarted = new CompletableFuture<Void>();
+               deactivated = deactivationStarted //
+                               .thenComposeAsync(this::dependantsDeactivated) //
+                               .thenRun(this.close) //
+                               .thenRun(() -> prepareNextActivation());
+       }
+
+       CompletableFuture<Void> getActivated() {
+               return activated;
+       }
+
+       CompletableFuture<Void> getDeactivated() {
+               return deactivated;
+       }
+
+       void startActivating() {
+               if (activated.isDone() || activationStarted.isDone())
+                       return;
+               activationStarted.complete(null);
+       }
+
+       void startDeactivating() {
+               if (deactivated.isDone() || deactivationStarted.isDone())
+                       return;
+               deactivationStarted.complete(null);
+       }
+
+       CompletableFuture<Void> dependenciesActivated(Void v) {
+               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
+               for (Dependency<?> dependency : this.dependencies) {
+                       CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
+                                       .thenCompose(dependency::set);
+                       constraints.add(dependencyActivated);
+               }
+               return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+       }
+
+       CompletableFuture<Void> dependantsDeactivated(Void v) {
+               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
+               for (Dependency<?> dependant : this.dependants) {
+                       CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
+                                       .thenCompose(dependant::unset);
+                       constraints.add(dependantDeactivated);
+               }
+               CompletableFuture<Void> dependantsDeactivated = CompletableFuture
+                               .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+               return dependantsDeactivated;
+
+       }
+
+       void addDependant(Dependency<?> dependant) {
+               dependants.add(dependant);
+       }
+
+       @Override
+       public I get() {
+               return instance;
+       }
+
+       @SuppressWarnings("unchecked")
+       public <T> PublishedType<T> getType(Class<T> clss) {
+               if (!types.containsKey(clss))
+                       throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
+               return (PublishedType<T>) types.get(clss);
+       }
+
+       public <T> boolean isPublishedType(Class<T> clss) {
+               return types.containsKey(clss);
+       }
+
+       public Map<String, Object> getProperties() {
+               return properties;
+       }
+
+       @Override
+       public int compareTo(Component<?> o) {
+               return rankingKey.compareTo(rankingKey);
+       }
+
+       @Override
+       public int hashCode() {
+               Long serviceId = (Long) properties.get(RankingKey.SERVICE_ID);
+               if (serviceId != null)
+                       return serviceId.intValue();
+               else
+                       return super.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               List<String> classes = new ArrayList<>();
+               for (Class<?> clss : types.keySet()) {
+                       classes.add(clss.getName());
+               }
+               return "Component " + classes + " " + properties + "";
+       }
+
+       /** A type which has been explicitly exposed by a component. */
+       public static class PublishedType<T> {
+               private Component<? extends T> component;
+               private Class<T> clss;
+
+               private CompletableFuture<T> value;
+
+               public PublishedType(Component<? extends T> component, Class<T> clss) {
+                       this.clss = clss;
+                       this.component = component;
+                       value = CompletableFuture.completedFuture((T) component.instance);
+               }
+
+               public Component<?> getPublisher() {
+                       return component;
+               }
+
+               public Class<T> getType() {
+                       return clss;
+               }
+
+               public CompletionStage<T> getValue() {
+                       return value.minimalCompletionStage();
+               }
+       }
+
+       /** Builds a {@link Component}. */
+       public static class Builder<I> implements Supplier<I> {
+               private final I instance;
+
+               private Runnable init;
+               private Runnable close;
+
+               private Set<Dependency<?>> dependencies = new HashSet<>();
+               private Set<Class<? super I>> types = new HashSet<>();
+               private final Map<String, Object> properties = new HashMap<>();
+
+               public Builder(I instance) {
+                       this.instance = instance;
+               }
+
+               public Component<I> build(ComponentRegister register) {
+                       // default values
+                       if (types.isEmpty()) {
+                               types.add(getInstanceClass());
+                       }
+
+                       if (init == null)
+                               init = () -> {
+                               };
+                       if (close == null)
+                               close = () -> {
+                               };
+
+                       // instantiation
+                       Component<I> component = new Component<I>(register, instance, init, close, dependencies, types, properties);
+                       for (Dependency<?> dependency : dependencies) {
+                               dependency.type.getPublisher().addDependant(dependency);
+                       }
+                       return component;
+               }
+
+               public Builder<I> addType(Class<? super I> clss) {
+                       types.add(clss);
+                       return this;
+               }
+
+               public Builder<I> addActivation(Runnable init) {
+                       if (this.init != null)
+                               throw new IllegalArgumentException("init method is already set");
+                       this.init = init;
+                       return this;
+               }
+
+               public Builder<I> addDeactivation(Runnable close) {
+                       if (this.close != null)
+                               throw new IllegalArgumentException("close method is already set");
+                       this.close = close;
+                       return this;
+               }
+
+               public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
+                       dependencies.add(new Dependency<D>(type, set, unset));
+                       return this;
+               }
+
+               public void addProperty(String key, Object value) {
+                       if (properties.containsKey(key))
+                               throw new IllegalStateException("Key " + key + " is already set.");
+                       properties.put(key, value);
+               }
+
+               @Override
+               public I get() {
+                       return instance;
+               }
+
+               @SuppressWarnings("unchecked")
+               private Class<I> getInstanceClass() {
+                       return (Class<I>) instance.getClass();
+               }
+
+       }
+
+       static class Dependency<D> {
+               private PublishedType<D> type;
+               private Consumer<D> set;
+               private Consumer<D> unset;
+
+               // live
+               Component<?> dependantComponent;
+               CompletableFuture<Void> setStage;
+               CompletableFuture<Void> unsetStage;
+
+               public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
+                       super();
+                       this.type = types;
+                       this.set = set != null ? set : t -> {
+                       };
+                       this.unset = unset != null ? unset : t -> {
+                       };
+               }
+
+               // live
+               void setDependantComponent(Component<?> component) {
+                       this.dependantComponent = component;
+               }
+
+               CompletableFuture<Void> publisherActivated() {
+                       return type.getPublisher().activated.copy();
+               }
+
+               CompletableFuture<Void> dependantDeactivated() {
+                       return dependantComponent.deactivated.copy();
+               }
+
+               CompletableFuture<Void> set(Void v) {
+                       return type.value.thenAccept(set);
+               }
+
+               CompletableFuture<Void> unset(Void v) {
+                       return type.value.thenAccept(unset);
+               }
+
+       }
+}
diff --git a/org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java b/org.argeo.api.register/src/org/argeo/api/register/ComponentRegister.java
new file mode 100644 (file)
index 0000000..1bb9036
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.api.register;
+
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.function.Predicate;
+
+/** A register of components which can coordinate their activation. */
+public interface ComponentRegister {
+       long register(Component<?> component);
+
+       <T> SortedSet<Component<? extends T>> find(Class<T> clss, Predicate<Map<String, Object>> filter);
+
+       default <T> Component.PublishedType<T> getSingleton(Class<T> type) {
+               SortedSet<Component<? extends T>> found = find(type, null);
+               if (found.size() == 0)
+                       throw new IllegalStateException("No component found for " + type);
+               return found.first().getType(type);
+       }
+
+       default <T> T getObject(Class<T> clss) {
+               SortedSet<Component<? extends T>> found = find(clss, null);
+               if (found.size() == 0)
+                       return null;
+               return found.first().get();
+       }
+
+       Component<?> get(Object instance);
+
+//     default <T> PublishedType<T> getType(Class<T> clss) {
+//             SortedSet<Component<? extends T>> components = find(clss, null);
+//             if (components.size() == 0)
+//                     return null;
+//             return components.first().getType(clss);
+//     }
+
+       void activate();
+
+       void deactivate();
+
+       boolean isActive();
+
+       void clear();
+}
diff --git a/org.argeo.api.register/src/org/argeo/api/register/RankingKey.java b/org.argeo.api.register/src/org/argeo/api/register/RankingKey.java
new file mode 100644 (file)
index 0000000..3886afe
--- /dev/null
@@ -0,0 +1,105 @@
+package org.argeo.api.register;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Key used to classify and filter available components.
+ */
+public class RankingKey implements Comparable<RankingKey> {
+       public final static String SERVICE_PID = "service.pid";
+       public final static String SERVICE_ID = "service.id";
+       public final static String SERVICE_RANKING = "service.ranking";
+
+       private String pid;
+       private Integer ranking = 0;
+       private Long id = 0l;
+
+       public RankingKey(String pid, Integer ranking, Long id) {
+               super();
+               this.pid = pid;
+               this.ranking = ranking;
+               this.id = id;
+       }
+
+       public RankingKey(Map<String, Object> properties) {
+               this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null;
+               this.ranking = properties.containsKey(SERVICE_RANKING)
+                               ? Integer.parseInt(properties.get(SERVICE_RANKING).toString())
+                               : 0;
+               this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null;
+       }
+
+       @Override
+       public int hashCode() {
+               Integer result = 0;
+               if (pid != null)
+                       result = +pid.hashCode();
+               if (ranking != null)
+                       result = +ranking;
+               return result;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new RankingKey(pid, ranking, id);
+       }
+
+       @Override
+       public String toString() {
+               StringBuilder sb = new StringBuilder("");
+               if (pid != null)
+                       sb.append(pid);
+               if (ranking != null && ranking != 0)
+                       sb.append(' ').append(ranking);
+               return sb.toString();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof RankingKey))
+                       return false;
+               RankingKey other = (RankingKey) obj;
+               return Objects.equals(pid, other.pid) && Objects.equals(ranking, other.ranking) && Objects.equals(id, other.id);
+       }
+
+       @Override
+       public int compareTo(RankingKey o) {
+               if (pid != null && o.pid != null) {
+                       if (pid.equals(o.pid)) {
+                               if (ranking.equals(o.ranking))
+                                       if (id != null && o.id != null)
+                                               return id.compareTo(o.id);
+                                       else
+                                               return 0;
+                               else
+                                       return ranking.compareTo(o.ranking);
+                       } else {
+                               return pid.compareTo(o.pid);
+                       }
+
+               } else {
+               }
+               return -1;
+       }
+
+       public String getPid() {
+               return pid;
+       }
+
+       public Integer getRanking() {
+               return ranking;
+       }
+
+       public Long getId() {
+               return id;
+       }
+
+       public static RankingKey minPid(String pid) {
+               return new RankingKey(pid, Integer.MIN_VALUE, null);
+       }
+
+       public static RankingKey maxPid(String pid) {
+               return new RankingKey(pid, Integer.MAX_VALUE, null);
+       }
+}
diff --git a/org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java b/org.argeo.api.register/src/org/argeo/api/register/SimpleRegister.java
new file mode 100644 (file)
index 0000000..9ed7e76
--- /dev/null
@@ -0,0 +1,124 @@
+package org.argeo.api.register;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Predicate;
+
+/** A minimal component register. */
+public class SimpleRegister implements ComponentRegister {
+       private final AtomicBoolean started = new AtomicBoolean(false);
+       private final IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
+       private final AtomicLong nextServiceId = new AtomicLong(0l);
+
+       @Override
+       public long register(Component<?> component) {
+               return registerComponent(component);
+       }
+
+       @SuppressWarnings({ "unchecked" })
+       @Override
+       public synchronized <T> SortedSet<Component<? extends T>> find(Class<T> clss,
+                       Predicate<Map<String, Object>> filter) {
+               SortedSet<Component<? extends T>> result = new TreeSet<>();
+               instances: for (Object instance : components.keySet()) {
+                       if (!clss.isAssignableFrom(instance.getClass()))
+                               continue instances;
+                       Component<? extends T> component = (Component<? extends T>) components.get(instance);
+
+                       if (component.isPublishedType(clss)) {
+                               if (filter != null) {
+                                       filter.test(component.getProperties());
+                               }
+                               result.add(component);
+                       }
+               }
+               if (result.isEmpty())
+                       return null;
+               return result;
+
+       }
+
+       synchronized long registerComponent(Component<?> component) {
+               if (started.get()) // TODO make it really dynamic
+                       throw new IllegalStateException("Already activated");
+               if (components.containsKey(component.get()))
+                       throw new IllegalArgumentException("Already registered as component");
+               components.put(component.get(), component);
+               return nextServiceId.incrementAndGet();
+       }
+
+       @Override
+       public synchronized Component<?> get(Object instance) {
+               if (!components.containsKey(instance))
+                       throw new IllegalArgumentException("Not registered as component");
+               return components.get(instance);
+       }
+
+       @Override
+       public synchronized void activate() {
+               if (started.get())
+                       throw new IllegalStateException("Already activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startActivating();
+                       constraints.add(component.getActivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
+                                       .get();
+               } catch (InterruptedException e) {
+                       throw new RuntimeException("Register activation has been interrupted", e);
+               } catch (ExecutionException e) {
+                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+                               throw (RuntimeException) e.getCause();
+                       } else {
+                               throw new IllegalStateException("Cannot activate register", e.getCause());
+                       }
+               }
+       }
+
+       @Override
+       public synchronized void deactivate() {
+               if (!started.get())
+                       throw new IllegalStateException("Not activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startDeactivating();
+                       constraints.add(component.getDeactivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
+                                       .get();
+               } catch (InterruptedException e) {
+                       throw new RuntimeException("Register deactivation has been interrupted", e);
+               } catch (ExecutionException e) {
+                       if (RuntimeException.class.isAssignableFrom(e.getCause().getClass())) {
+                               throw (RuntimeException) e.getCause();
+                       } else {
+                               throw new IllegalStateException("Cannot deactivate register", e.getCause());
+                       }
+               }
+       }
+
+       @Override
+       public synchronized boolean isActive() {
+               return started.get();
+       }
+
+       @Override
+       public synchronized void clear() {
+               components.clear();
+       }
+}
index e801ebfb4680123285c15553dc70584276fe0057..81fe078c20c05db46a8281fbb1a72875a5322b45 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="output" path="bin"/>
index 52becc85ac0946a018e692b1065e3b20c999c83a..fc1c931ff6b4945a09dadec175811bf9854ace73 100644 (file)
@@ -63,7 +63,7 @@ public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory imple
        }
 
        /**
-        * If positive, only clock_hi is taken from the argument (range & 0x3F00), if
+        * If positive, only clock_hi is taken from the argument (range amp; 0x3F00), if
         * negative, the full range of possible values is used.
         */
        public void setCurrentClockSequenceRange(long range) {
index 1bbc4391f248b599293ac4df74c97dccca675ab6..4f2cf3765dcb69d6d912a7491511bee1350742f5 100644 (file)
@@ -12,7 +12,7 @@ import java.util.UUID;
 /**
  * Implementation of the basic RFC4122 algorithms.
  * 
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
  */
 public abstract class AbstractUuidFactory implements UuidFactory {
 
@@ -127,7 +127,7 @@ public abstract class AbstractUuidFactory implements UuidFactory {
        /**
         * Force this node id to be identified as no MAC address.
         * 
-        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+        * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
         */
        protected static void forceToNoMacAddress(byte[] nodeId, int offset) {
                assert nodeId != null && offset < nodeId.length;
index b883e0dcb62f6dda23d6f17ae858b5329369d990..c9f130df96a49a157905451ff1fe02a2b708d4c3 100644 (file)
@@ -13,7 +13,7 @@ public class BasicNameUuid extends TypedUuid {
        }
 
        /**
-        * Always returns <code>true</true> since it is unknown from which values it was
+        * Always returns <code>true</code> since it is unknown from which values it was
         * constructed..
         */
        @Override
index 5bab35980370474e2a30663f8795a405be9e3c80..fd158fb8315db647b8d0b07e7790d1ec57aa2b3f 100644 (file)
@@ -1,9 +1,5 @@
 package org.argeo.api.uuid;
 
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.WARNING;
-
-import java.lang.System.Logger;
 import java.security.SecureRandom;
 import java.time.Clock;
 import java.time.Duration;
@@ -13,6 +9,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.argeo.api.uuid.UuidFactory.TimeUuidState;
@@ -25,11 +22,11 @@ import org.argeo.api.uuid.UuidFactory.TimeUuidState;
  * sequences. If that limit is reached, the clock sequence which has not be used
  * for the most time is reallocated to the new thread. It is assumed that the
  * context where time uUIDs will be generated will often be using thread pools
- * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.)
+ * (e.g. {@link ForkJoinPool#commonPool()}, http server, database access, etc.)
  * and that such reallocation won't have to happen too often.
  */
 public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
-       private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
+//     private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
 
        /** The maximum possible value of the clocksequence. */
        private final static int MAX_CLOCKSEQUENCE = 0x3F00;
@@ -146,9 +143,7 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
        @Override
        public long getMostSignificantBits() {
                long timestamp = useTimestamp();
-               long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
-                               | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
-                               | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+               long mostSig = TimeUuid.toMostSignificantBits(timestamp);
                return mostSig;
        }
 
@@ -275,11 +270,11 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
 
                                }
                                assert holderToRemove != null;
-                               long oldClockSequence = holderToRemove.clockSequence;
+//                             long oldClockSequence = holderToRemove.clockSequence;
                                holderToRemove.clockSequence = -1;
                                activeHolders.remove(holderToRemove);
-                               if (logger.isLoggable(WARNING))
-                                       logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
+//                             if (logger.isLoggable(WARNING))
+//                                     logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
                        }
 
                        long newClockSequence = -1;
@@ -300,13 +295,13 @@ public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
                        // TODO use an iterator to check the values
                        holder.setClockSequence(newClockSequence);
                        activeHolders.put(holder, newClockSequence);
-                       if (logger.isLoggable(DEBUG)) {
-                               String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
-                                               : Long.toHexString(newClockSequence | 0x8000);
-                               String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
-                               logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
-                                               + " (in range " + rangeDesc + ")");
-                       }
+//                     if (logger.isLoggable(DEBUG)) {
+//                             String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
+//                                             : Long.toHexString(newClockSequence | 0x8000);
+//                             String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
+//                             logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
+//                                             + " (in range " + rangeDesc + ")");
+//                     }
                }
 
                private synchronized int getRangeSize() {
index 264e047063ac0872af99f1fc8b0adeba0530aaa3..130a90a84bfb49be449539bd939fc2cc696bd742 100644 (file)
@@ -1,9 +1,5 @@
 package org.argeo.api.uuid;
 
-import static java.lang.System.Logger.Level.DEBUG;
-import static java.lang.System.Logger.Level.WARNING;
-
-import java.lang.System.Logger;
 import java.security.DrbgParameters;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -14,15 +10,20 @@ import java.util.UUID;
  * A configurable implementation of an {@link AsyncUuidFactory}, which can be
  * used as a base class for more optimised implementations.
  * 
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
  */
 public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory {
-       private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
+//     private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
 
        public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId) {
                this(initialClockRange, nodeId, 0);
        }
 
+       /** With a random node id. */
+       public ConcurrentUuidFactory(long initialClockRange) {
+               this(initialClockRange, NodeIdSupplier.randomNodeId());
+       }
+
        public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId, int offset) {
                Objects.requireNonNull(nodeId);
                if (offset + 6 > nodeId.length)
@@ -67,12 +68,14 @@ public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements T
                                        DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
                } catch (NoSuchAlgorithmException e) {
                        try {
-                               logger.log(DEBUG, "DRBG secure random not found, using strong");
+//                             logger.log(DEBUG, "DRBG secure random not found, using strong");
                                secureRandom = SecureRandom.getInstanceStrong();
                        } catch (NoSuchAlgorithmException e1) {
-                               logger.log(WARNING, "No strong secure random was found, using default");
+//                             logger.log(WARNING, "No strong secure random was found, using default");
                                secureRandom = new SecureRandom();
                        }
+               } catch (java.lang.NoClassDefFoundError e) {// Android
+                       secureRandom = new SecureRandom();
                }
                return secureRandom;
        }
index a9a5af17d098ddd9c8bb0cb46692c0b75d1d0e28..0775cbf75e7e9419911e63215b2686b512a7ce9c 100644 (file)
@@ -5,7 +5,7 @@ import java.util.UUID;
 /**
  * A variant 6 {@link UUID}.
  * 
- * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1"
  */
 public class GUID extends TypedUuid {
        private static final long serialVersionUID = APM.SERIAL;
@@ -26,7 +26,7 @@ public class GUID extends TypedUuid {
         * <li>P: (1db31359-bdd8-5a0f-b672-30c247d582c5)</li>
         * </ul>
         * 
-        * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring
+        * @see "https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring"
         */
        public static String toString(UUID uuid, char format, boolean upperCase) {
                String str;
index fa68df1dbe2777328a87b008e932039b23e56a5b..31fe3783112661e23f28074d31669870c0db0d42 100644 (file)
@@ -4,45 +4,66 @@ import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.util.Enumeration;
 import java.util.UUID;
 
 /**
  * An {@link UUID} factory whose node id (for time based UUIDs) is the hardware
  * MAC address as specified in RFC4122.
  * 
- * @see https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6"
  */
 public class MacAddressUuidFactory extends ConcurrentUuidFactory {
-       public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
-
        public MacAddressUuidFactory() {
-               this(0);
+               this(0, localHardwareAddressAsNodeId());
        }
 
        public MacAddressUuidFactory(long initialClockRange) {
-               super(initialClockRange, localHardwareAddressAsNodeId());
+               this(initialClockRange, localHardwareAddressAsNodeId());
+       }
+
+       public MacAddressUuidFactory(byte[] hardwareAddress) {
+               this(0, hardwareAddress);
        }
 
-       public static byte[] localHardwareAddressAsNodeId() {
+       public MacAddressUuidFactory(long initialClockRange, byte[] hardwareAddress) {
+               super(initialClockRange, hardwareAddress);
+       }
+
+       private static byte[] localHardwareAddressAsNodeId() {
                InetAddress localHost;
                try {
                        localHost = InetAddress.getLocalHost();
                        NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
-                       return hardwareAddressToNodeId(nic);
+                       if (nic != null)
+                               return hardwareAddressToNodeId(nic);
+                       Enumeration<NetworkInterface> netInterfaces = null;
+                       try {
+                               netInterfaces = NetworkInterface.getNetworkInterfaces();
+                       } catch (SocketException e) {
+                               throw new IllegalStateException(e);
+                       }
+                       if (netInterfaces == null || !netInterfaces.hasMoreElements())
+                               throw new IllegalStateException("No interfaces");
+                       return hardwareAddressToNodeId(netInterfaces.nextElement());
                } catch (UnknownHostException | SocketException e) {
                        throw new IllegalStateException(e);
                }
 
        }
 
-       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException {
-               byte[] hardwareAddress = nic.getHardwareAddress();
-               final int length = 6;
-               byte[] arr = new byte[length];
-               for (int i = 0; i < length; i++) {
-                       arr[i] = hardwareAddress[length - 1 - i];
+       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) {
+               try {
+                       byte[] hardwareAddress = nic.getHardwareAddress();
+                       final int length = 6;
+                       byte[] arr = new byte[length];
+                       for (int i = 0; i < length; i++) {
+                               arr[i] = hardwareAddress[length - 1 - i];
+                       }
+                       return arr;
+               } catch (SocketException e) {
+                       throw new IllegalStateException("Cannot retrieve hardware address from NIC", e);
                }
-               return arr;
        }
 
 }
index 94ec50da4b9ff76d3eb86fe94425d6e1ec9dddce..81d368d2c8c651c37ea9ab48f7f048db98351f16 100644 (file)
@@ -1,13 +1,13 @@
 package org.argeo.api.uuid;
 
+import java.security.SecureRandom;
 import java.util.function.Supplier;
 
 /** A factory for node id base */
 public interface NodeIdSupplier extends Supplier<Long> {
        static long toNodeIdBase(byte[] node) {
                assert node.length == 6;
-               return UuidFactory.LEAST_SIG_RFC4122_VARIANT
-                               | (node[0] & 0xFFL) //
+               return UuidFactory.LEAST_SIG_RFC4122_VARIANT | (node[0] & 0xFFL) //
                                | ((node[1] & 0xFFL) << 8) //
                                | ((node[2] & 0xFFL) << 16) //
                                | ((node[3] & 0xFFL) << 24) //
@@ -19,4 +19,10 @@ public interface NodeIdSupplier extends Supplier<Long> {
                return (nodeId[0] & 1) != 0;
        }
 
+       static byte[] randomNodeId() {
+               SecureRandom random = new SecureRandom();
+               byte[] nodeId = new byte[6];
+               random.nextBytes(nodeId);
+               return nodeId;
+       }
 }
index b65772e9b234dbcf565fb5e8217f5526076f92d9..79b2b7032620ffcb5f8441546c727b7b31ab6dec 100644 (file)
@@ -14,8 +14,7 @@ public final class RandomUuid extends TypedUuid {
        }
 
        /**
-        * Always returns <code>true</code> since random UUIDs are by definition not
-        * opaque.
+        * Always returns <code>true</code> since random UUIDs are by definition opaque.
         */
        @Override
        public final boolean isOpaque() {
index 2276cd26831976344acb80e53b708aca902da854..2e0587d12748128445ee3b81b64439fa8b6a59f2 100644 (file)
@@ -76,4 +76,23 @@ public class TimeUuid extends TypedUuid {
                Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, instant);
                return durationToTimestamp(duration);
        }
+
+       /**
+        * Crate a time UUID with this instant as timestamp and clock and node id set to
+        * zero.
+        */
+       public static UUID fromInstant(Instant instant) {
+               long timestamp = instantToTimestamp(instant);
+               long mostSig = toMostSignificantBits(timestamp);
+               UUID uuid = new UUID(mostSig, UuidFactory.LEAST_SIG_RFC4122_VARIANT);
+               return uuid;
+       }
+
+       /** Convert timestamp in UUID format to most significant bits of a time UUID. */
+       static long toMostSignificantBits(long timestamp) {
+               long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
+                               | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
+                               | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+               return mostSig;
+       }
 }
index 4ab6c2e4b754d7818be29a24fece361373b5f6e9..ee5aa699472659127a4d40f1c3dbedaf6100490d 100644 (file)
@@ -14,7 +14,7 @@ import java.util.function.Supplier;
  * {@link Supplier#get()} method MUST be a v4 UUID (random).
  * 
  * @see UUID
- * @see https://datatracker.ietf.org/doc/html/rfc4122
+ * @see "https://datatracker.ietf.org/doc/html/rfc4122"
  */
 public interface UuidFactory extends Supplier<UUID> {
 
@@ -184,7 +184,7 @@ public interface UuidFactory extends Supplier<UUID> {
         * Whether this UUID is time based but was not generated from an IEEE 802
         * address, as per Section 4.5 of RFC4122.
         * 
-        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+        * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.5"
         */
        static boolean isTimeBasedWithMacAddress(UUID uuid) {
                if (uuid.version() == 1) {
@@ -202,7 +202,7 @@ public interface UuidFactory extends Supplier<UUID> {
         * The state of a time based UUID generator, as described and discussed in
         * section 4.2.1 of RFC4122.
         * 
-        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1
+        * @see "https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1"
         */
        interface TimeUuidState {
                /** Current node id and clock sequence for this thread. */
diff --git a/org.argeo.cms.cli/.classpath b/org.argeo.cms.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/org.argeo.cms.cli/.project b/org.argeo.cms.cli/.project
new file mode 100644 (file)
index 0000000..1514c53
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.cli</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.cli/bnd.bnd b/org.argeo.cms.cli/bnd.bnd
new file mode 100644 (file)
index 0000000..07d35f2
--- /dev/null
@@ -0,0 +1,58 @@
+Main-Class: org.argeo.cms.cli.ArgeoCli
+
+Class-Path: \
+org.argeo.api.acr.2.3.jar \
+org.argeo.api.cli.2.3.jar \
+org.argeo.api.cms.2.3.jar \
+org.argeo.api.uuid.2.3.jar \
+org.argeo.cms.2.3.jar \
+org.argeo.cms.ee.2.3.jar \
+../osgi/equinox/org.argeo.cms/org.argeo.cms.lib.equinox.2.3.jar \
+org.argeo.cms.lib.jetty.2.3.jar \
+org.argeo.cms.lib.sshd.2.3.jar \
+org.argeo.cms.ux.2.3.jar \
+org.argeo.init.2.3.jar \
+org.argeo.util.2.3.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-annotations.2.13.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-core.2.13.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson-databind.2.13.jar \
+../org.argeo.tp/com.googlecode.javaewah.JavaEWAH.1.1.jar \
+../org.argeo.tp/de.thjom.java.systemd.2.1.jar \
+../org.argeo.tp/javax.servlet.4.0.jar \
+../org.argeo.tp/javax.websocket.1.1.jar \
+../org.argeo.tp/org.apache.batik.1.16.jar \
+../org.argeo.tp/org.apache.batik.constants.1.16.jar \
+../org.argeo.tp/org.apache.batik.css.1.16.jar \
+../org.argeo.tp/org.apache.batik.i18n.1.16.jar \
+../org.argeo.tp/org.apache.batik.util.1.16.jar \
+../org.argeo.tp/org.apache.commons.cli.1.5.jar \
+../org.argeo.tp/org.apache.commons.fileupload.1.4.jar \
+../org.argeo.tp/org.apache.commons.io.2.11.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpclient.4.5.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpcore.4.4.jar \
+../org.argeo.tp/org.apache.httpcomponents.httpmime.4.5.jar \
+../org.argeo.tp/org.apache.xalan.2.7.jar \
+../org.argeo.tp/org.apache.xerces.2.12.jar \
+../org.argeo.tp/org.apache.xmlgraphics.2.7.jar \
+../org.argeo.tp/org.apache.xml.resolver.1.2.jar \
+../org.argeo.tp/org.argeo.ext.slf4j.2.3.jar \
+../org.argeo.tp/org.eclipse.jgit.6.3.jar \
+../org.argeo.tp/org.freeedesktop.dbus.4.2.jar \
+../org.argeo.tp/org.slf4j.api.1.7.jar \
+../org.argeo.tp/org.slf4j.commons.logging.1.7.jar \
+../org.argeo.tp/org.w3c.css.sac.1.3.jar \
+../org.argeo.tp/org.w3c.dom.smil.1.0.jar \
+../org.argeo.tp/org.w3c.dom.svg.1.1.jar \
+../org.argeo.tp.crypto/bcmail.1.72.jar \
+../org.argeo.tp.crypto/bcpg.1.72.jar \
+../org.argeo.tp.crypto/bcpkix.1.72.jar \
+../org.argeo.tp.crypto/bcprov.1.72.jar \
+../org.argeo.tp.crypto/bcutil.1.72.jar \
+../org.argeo.tp.crypto/net.i2p.crypto.eddsa.0.3.jar \
+../org.argeo.tp.crypto/org.apache.sshd.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.cli.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.git.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.putty.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.scp.2.9.jar \
+../org.argeo.tp.crypto/org.apache.sshd.sftp.2.9.jar \
+../org.argeo.tp.crypto/org.apache.tomcat.jni.9.0.jar \
diff --git a/org.argeo.cms.cli/build.properties b/org.argeo.cms.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/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/ArgeoCli.java
new file mode 100644 (file)
index 0000000..b55f9d6
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.cli;
+
+import org.apache.commons.cli.Option;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.cms.cli.posix.PosixCommands;
+import org.argeo.cms.ssh.cli.SshCli;
+
+/** Argeo command line tools. */
+public class ArgeoCli extends CommandsCli {
+       public ArgeoCli(String commandName) {
+               super(commandName);
+               // Common options
+               options.addOption(Option.builder("v").hasArg().argName("verbose").desc("verbosity").build());
+               options.addOption(
+                               Option.builder("D").hasArgs().argName("property=value").desc("use value for given property").build());
+
+               // common
+               addCommandsCli(new CmsCommands("cms"));
+               addCommandsCli(new SshCli("ssh"));
+               addCommandsCli(new PosixCommands("posix"));
+               addCommandsCli(new FsCommands("fs"));
+       }
+
+       @Override
+       public String getDescription() {
+               return "Argeo CMS utilities";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new ArgeoCli("argeo"), args);
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCli.java
new file mode 100644 (file)
index 0000000..516c768
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+public class CmsCli extends CommandsCli {
+
+       public CmsCli(String commandName) {
+               super(commandName);
+               addCommand("launch", new StaticCmsLaunch());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Static CMS utilities.";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new CmsCli("cms"), args);
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/CmsCommands.java
new file mode 100644 (file)
index 0000000..50977d1
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.client.CmsClient;
+import org.argeo.cms.client.WebSocketPing;
+
+/** Commands dealing with CMS. */
+public class CmsCommands extends CommandsCli {
+       final static Option connectOption = Option.builder().option("c").longOpt("connect").desc("server to connect to")
+                       .hasArg(true).build();
+
+       public CmsCommands(String commandName) {
+               super(commandName);
+               addCommand("ping", new Ping());
+               addCommand("get", new Get());
+               addCommand("status", new Status());
+               addCommand("event", new EventCommands("event"));
+       }
+
+       @Override
+       public String getDescription() {
+               return "Utilities related to an Argeo CMS";
+       }
+
+       class Ping implements DescribedCommand<Void> {
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(connectOption);
+                       return options;
+               }
+
+               @Override
+               public Void apply(List<String> t) {
+                       CommandLine line = toCommandLine(t);
+                       String uriArg = line.getOptionValue(connectOption);
+                       // TODO make it more robust (trailing /, etc.)
+                       URI uri = URI.create(uriArg);
+                       if ("".equals(uri.getPath())) {
+                               uri = URI.create(uri.toString() + "/cms/status/ping");
+                       }
+                       new WebSocketPing(uri).run();
+                       return null;
+               }
+
+               @Override
+               public String getUsage() {
+                       return "[ws|wss]://host:port/";
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Test whether an Argeo CMS is available, without auhtentication";
+               }
+
+       }
+
+       class Get implements DescribedCommand<String> {
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(connectOption);
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> t) {
+                       CommandLine line = toCommandLine(t);
+                       List<String> remaining = line.getArgList();
+                       String additionalUri = null;
+                       if (remaining.size() != 0) {
+                               additionalUri = remaining.get(0);
+                       }
+
+                       String connectUri = line.getOptionValue(connectOption);
+                       CmsClient cmsClient = new CmsClient(URI.create(connectUri));
+                       return additionalUri != null ? cmsClient.getAsString(URI.create(additionalUri)) : cmsClient.getAsString();
+               }
+
+               @Override
+               public String getUsage() {
+                       return "[URI]";
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Retrieve this URI as a string";
+               }
+
+       }
+
+       class Status implements DescribedCommand<String> {
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(connectOption);
+                       return options;
+               }
+
+               @Override
+               public String apply(List<String> t) {
+                       CommandLine line = toCommandLine(t);
+                       String connectUri = line.getOptionValue(connectOption);
+                       CmsClient cmsClient = new CmsClient(URI.create(connectUri));
+                       return cmsClient.getAsString(URI.create("/cms/status"));
+               }
+
+               @Override
+               public String getUsage() {
+                       return "[URI]";
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Retrieve the CMS status as a string";
+               }
+
+       }
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/EventCommands.java
new file mode 100644 (file)
index 0000000..009ad45
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.CommandsCli;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.client.WebSocketEventClient;
+
+/** Commands dealing with CMS events. */
+public class EventCommands extends CommandsCli {
+       public EventCommands(String commandName) {
+               super(commandName);
+               addCommand("listen", new EventListent());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Utilities related to an Argeo CMS";
+       }
+
+       class EventListent implements DescribedCommand<Void> {
+
+               @Override
+               public Options getOptions() {
+                       Options options = new Options();
+                       options.addOption(CmsCommands.connectOption);
+                       return options;
+               }
+
+               @Override
+               public Void apply(List<String> t) {
+                       CommandLine line = toCommandLine(t);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               throw new CommandArgsException("There must be at least one argument");
+                       }
+                       String topic = remaining.get(0);
+
+                       String uriArg = line.getOptionValue(CmsCommands.connectOption);
+                       // TODO make it more robust (trailing /, etc.)
+                       URI uri = URI.create(uriArg);
+                       if ("".equals(uri.getPath())) {
+                               uri = URI.create(uri.toString() + "/cms/status/event/" + topic);
+                       }
+                       new WebSocketEventClient(uri).run();
+                       return null;
+               }
+
+               @Override
+               public String getUsage() {
+                       return "TOPIC";
+               }
+
+               @Override
+               public String getDescription() {
+                       return "Listen to events on a topic";
+               }
+
+       }
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/FileSync.java
new file mode 100644 (file)
index 0000000..dc4c689
--- /dev/null
@@ -0,0 +1,103 @@
+package org.argeo.cms.cli;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.file.PathSync;
+import org.argeo.cms.file.SyncResult;
+
+public class FileSync implements DescribedCommand<SyncResult<Path>> {
+       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
+                       .build();
+       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
+                       .build();
+
+       @Override
+       public SyncResult<Path> apply(List<String> t) {
+               try {
+                       CommandLine line = toCommandLine(t);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               throw new CommandArgsException("There must be at least one argument");
+                       }
+                       URI sourceUri = new URI(remaining.get(0));
+                       URI targetUri;
+                       if (remaining.size() == 1) {
+                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+                       } else {
+                               targetUri = new URI(remaining.get(1));
+                       }
+                       boolean delete = line.hasOption(deleteOption.getLongOpt());
+                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
+                       PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
+                       return pathSync.call();
+               } catch (URISyntaxException e) {
+                       throw new CommandArgsException(e);
+               }
+       }
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               options.addOption(recursiveOption);
+               options.addOption(deleteOption);
+               options.addOption(progressOption);
+               return options;
+       }
+
+       @Override
+       public String getUsage() {
+               return "[source URI] [target URI]";
+       }
+
+       public static void main(String[] args) {
+               DescribedCommand.mainImpl(new FileSync(), args);
+//             Options options = new Options();
+//             options.addOption("r", "recursive", false, "recurse into directories");
+//             options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
+//
+//             CommandLineParser parser = new DefaultParser();
+//             try {
+//                     CommandLine line = parser.parse(options, args);
+//                     List<String> remaining = line.getArgList();
+//                     if (remaining.size() == 0) {
+//                             System.err.println("There must be at least one argument");
+//                             printHelp(options);
+//                             System.exit(1);
+//                     }
+//                     URI sourceUri = new URI(remaining.get(0));
+//                     URI targetUri;
+//                     if (remaining.size() == 1) {
+//                             targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+//                     } else {
+//                             targetUri = new URI(remaining.get(1));
+//                     }
+//                     PathSync pathSync = new PathSync(sourceUri, targetUri);
+//                     pathSync.run();
+//             } catch (Exception exp) {
+//                     exp.printStackTrace();
+//                     printHelp(options);
+//                     System.exit(1);
+//             }
+       }
+
+//     public static void printHelp(Options options) {
+//             HelpFormatter formatter = new HelpFormatter();
+//             formatter.printHelp("sync SRC [DEST]", options, true);
+//     }
+
+       @Override
+       public String getDescription() {
+               return "Synchronises files";
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/FsCommands.java
new file mode 100644 (file)
index 0000000..46ec142
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** File utilities. */
+public class FsCommands extends CommandsCli {
+
+       public FsCommands(String commandName) {
+               super(commandName);
+               addCommand("sync", new FileSync());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Utilities around files and file systems";
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/StaticCmsLaunch.java
new file mode 100644 (file)
index 0000000..9b53598
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.cli;
+
+import java.lang.management.ManagementFactory;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.runtime.StaticCms;
+
+public class StaticCmsLaunch implements DescribedCommand<String> {
+       private Option dataOption;
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               dataOption = Option.builder().longOpt("data").hasArg().required()
+                               .desc("path to the writable data area (mandatory)").build();
+               options.addOption(dataOption);
+               return options;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               CommandLine cl = toCommandLine(args);
+               String dataPath = cl.getOptionValue(dataOption);
+
+               Path instancePath = Paths.get(dataPath);
+               System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+               System.setProperty("argeo.http.port", "0");
+
+               StaticCms staticCms = new StaticCms();
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
+               staticCms.start();
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("Static CMS available in " + jvmUptime + " ms.");
+
+               staticCms.waitForStop();
+
+               return null;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Launch a static CMS";
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/Echo.java
new file mode 100644 (file)
index 0000000..28e6446
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.cli.posix;
+
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.api.cli.DescribedCommand;
+
+public class Echo implements DescribedCommand<String> {
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               options.addOption(Option.builder("n").desc("do not output the trailing newline").build());
+               return options;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Display a line of text";
+       }
+
+       @Override
+       public String getUsage() {
+               return "[STRING]...";
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               CommandLine cl = toCommandLine(args);
+
+               StringBuffer sb = new StringBuffer();
+               for (String s : cl.getArgList()) {
+                       sb.append(s).append(' ');
+               }
+
+               if (cl.hasOption('n')) {
+                       sb.deleteCharAt(sb.length() - 1);
+               } else {
+                       sb.setCharAt(sb.length() - 1, '\n');
+               }
+               return sb.toString();
+       }
+
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/PosixCommands.java
new file mode 100644 (file)
index 0000000..9782429
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.cli.posix;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** POSIX commands. */
+public class PosixCommands extends CommandsCli {
+
+       public PosixCommands(String commandName) {
+               super(commandName);
+               addCommand("echo", new Echo());
+       }
+
+       @Override
+       public String getDescription() {
+               return "Reimplementation of some POSIX commands in plain Java";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new PosixCommands("argeo-posix"), args);
+       }
+}
diff --git a/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java b/org.argeo.cms.cli/src/org/argeo/cms/cli/posix/package-info.java
new file mode 100644 (file)
index 0000000..8a6785c
--- /dev/null
@@ -0,0 +1,2 @@
+/** Posix CLI commands. */
+package org.argeo.cms.cli.posix;
\ No newline at end of file
diff --git a/org.argeo.cms.ee/.classpath b/org.argeo.cms.ee/.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.cms.ee/.project b/org.argeo.cms.ee/.project
new file mode 100644 (file)
index 0000000..4b68cdd
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.ee</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml b/org.argeo.cms.ee/OSGI-INF/pkgServlet.xml
new file mode 100644 (file)
index 0000000..00fcaff
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.argeo.cms.pkgServlet">
+   <implementation class="org.argeo.cms.servlet.internal.PkgServlet"/>
+   <service>
+      <provide interface="javax.servlet.Servlet"/>
+   </service>
+   <property name="osgi.http.whiteboard.servlet.pattern" type="String" value="/*"/>
+   <property name="osgi.http.whiteboard.context.select" type="String" value="(osgi.http.whiteboard.context.name=pkgServletContext)"/>
+</scr:component>
diff --git a/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.ee/OSGI-INF/pkgServletContext.xml
new file mode 100644 (file)
index 0000000..7540a2c
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="org.argeo.cms.pkgServletContext">
+   <implementation class="org.argeo.cms.servlet.CmsServletContext"/>
+   <service>
+      <provide interface="org.osgi.service.http.context.ServletContextHelper"/>
+   </service>
+   <property name="osgi.http.whiteboard.context.name" type="String" value="pkgServletContext"/>
+   <property name="osgi.http.whiteboard.context.path" type="String" value="/pkg"/>
+</scr:component>
diff --git a/org.argeo.cms.ee/OSGI-INF/statusHandler.xml b/org.argeo.cms.ee/OSGI-INF/statusHandler.xml
new file mode 100644 (file)
index 0000000..b6e9bfd
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" immmediate="true" name="Status Handler">
+   <implementation class="org.argeo.cms.websocket.server.StatusHandler"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpHandler"/>
+   </service>
+   <property name="context.path" type="String" value="/cms/status/"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms.ee/bnd.bnd b/org.argeo.cms.ee/bnd.bnd
new file mode 100644 (file)
index 0000000..f09995c
--- /dev/null
@@ -0,0 +1,12 @@
+Import-Package:\
+org.osgi.service.http;version=0.0.0,\
+org.osgi.service.http.whiteboard;version=0.0.0,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.argeo.cms.osgi,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component:\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml,\
+OSGI-INF/statusHandler.xml,\
diff --git a/org.argeo.cms.ee/build.properties b/org.argeo.cms.ee/build.properties
new file mode 100644 (file)
index 0000000..eb170c9
--- /dev/null
@@ -0,0 +1,6 @@
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/jettyServiceFactory.xml,\
+               OSGI-INF/statusHandler.xml
+source.. = src/
+output.. = bin/
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsExceptionsChain.java
new file mode 100644 (file)
index 0000000..6727229
--- /dev/null
@@ -0,0 +1,80 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.util.ExceptionsChain;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Serialisable wrapper of a {@link Throwable}. */
+public class CmsExceptionsChain extends ExceptionsChain {
+       public final static CmsLog log = CmsLog.getLog(CmsExceptionsChain.class);
+
+       public CmsExceptionsChain() {
+               super();
+       }
+
+       public CmsExceptionsChain(Throwable exception) {
+               super(exception);
+               if (log.isDebugEnabled())
+                       log.error("Exception chain", exception);
+       }
+
+       public String toJsonString(ObjectMapper objectMapper) {
+               try {
+                       return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(this);
+               } catch (JsonProcessingException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+       public void writeAsJson(ObjectMapper objectMapper, Writer writer) {
+               try {
+                       JsonGenerator jg = objectMapper.writerWithDefaultPrettyPrinter().getFactory().createGenerator(writer);
+                       jg.writeObject(this);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+       public void writeAsJson(ObjectMapper objectMapper, HttpServletResponse resp) {
+               try {
+                       resp.setContentType("application/json");
+                       resp.setStatus(500);
+                       writeAsJson(objectMapper, resp.getWriter());
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot write system exceptions " + toString(), e);
+               }
+       }
+
+//     public static void main(String[] args) throws Exception {
+//             try {
+//                     try {
+//                             try {
+//                                     testDeeper();
+//                             } catch (Exception e) {
+//                                     throw new Exception("Less deep exception", e);
+//                             }
+//                     } catch (Exception e) {
+//                             throw new RuntimeException("Top exception", e);
+//                     }
+//             } catch (Exception e) {
+//                     CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
+//                     ObjectMapper objectMapper = new ObjectMapper();
+//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(systemErrors));
+//                     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(e));
+//                     e.printStackTrace();
+//             }
+//     }
+//
+//     static void testDeeper() throws Exception {
+//             throw new IllegalStateException("Deep exception");
+//     }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLoginServlet.java
new file mode 100644 (file)
index 0000000..29a3137
--- /dev/null
@@ -0,0 +1,112 @@
+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/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsLogoutServlet.java
new file mode 100644 (file)
index 0000000..d18637d
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+
+/** Externally authenticate an http session. */
+public class CmsLogoutServlet extends HttpServlet {
+       private static final long serialVersionUID = 2478080654328751539L;
+
+       @Override
+       protected void doGet(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               doPost(request, response);
+       }
+
+       @Override
+       protected void doPost(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               ServletHttpRequest httpRequest = new ServletHttpRequest(request);
+               ServletHttpResponse httpResponse = new ServletHttpResponse(response);
+               LoginContext lc = null;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+                                       new RemoteAuthCallbackHandler(httpRequest, httpResponse) {
+                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                                       for (Callback callback : callbacks) {
+                                                               if (callback instanceof RemoteAuthCallback) {
+                                                                       ((RemoteAuthCallback) callback).setRequest(httpRequest);
+                                                                       ((RemoteAuthCallback) callback).setResponse(httpResponse);
+                                                               }
+                                                       }
+                                               }
+                                       });
+                       lc.login();
+
+                       Subject subject = lc.getSubject();
+                       CmsSessionId cmsSessionId = extractFrom(subject.getPrivateCredentials(CmsSessionId.class));
+                       if (cmsSessionId != null) {// logged in
+                               CurrentUser.logoutCmsSession(subject);
+                       }
+
+               } catch (LoginException e) {
+                       // ignore
+               }
+
+               String redirectTo = redirectTo(request);
+               if (redirectTo != null)
+                       response.sendRedirect(redirectTo);
+       }
+
+       protected <T> T extractFrom(Set<T> creds) {
+               if (creds.size() > 0)
+                       return creds.iterator().next();
+               else
+                       return null;
+       }
+
+       protected String redirectTo(HttpServletRequest request) {
+               return null;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsPrivateServletContext.java
new file mode 100644 (file)
index 0000000..09f17ae
--- /dev/null
@@ -0,0 +1,80 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.util.Map;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/** Manages security access to servlets. */
+public class CmsPrivateServletContext extends ServletContextHelper {
+       public final static String LOGIN_PAGE = "argeo.cms.integration.loginPage";
+       public final static String LOGIN_SERVLET = "argeo.cms.integration.loginServlet";
+       private String loginPage;
+       private String loginServlet;
+
+       public void init(Map<String, String> properties) {
+               loginPage = properties.get(LOGIN_PAGE);
+               loginServlet = properties.get(LOGIN_SERVLET);
+       }
+
+       /**
+        * Add the {@link AccessControlContext} as a request attribute, or redirect to
+        * the login page.
+        */
+       @Override
+       public boolean handleSecurity(final HttpServletRequest req, HttpServletResponse resp) throws IOException {
+               LoginContext lc = null;
+               ServletHttpRequest request = new ServletHttpRequest(req);
+               ServletHttpResponse response = new ServletHttpResponse(resp);
+
+               String pathInfo = req.getPathInfo();
+               String servletPath = req.getServletPath();
+               if ((pathInfo != null && (servletPath + pathInfo).equals(loginPage)) || servletPath.contentEquals(loginServlet))
+                       return true;
+               try {
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(request, response));
+                       lc.login();
+               } catch (LoginException e) {
+                       lc = processUnauthorized(req, resp);
+                       if (lc == null)
+                               return false;
+               }
+//             Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(request);
+//                             return null;
+//                     }
+//
+//             });
+
+               return true;
+       }
+
+//     @Override
+//     public void finishSecurity(HttpServletRequest req, HttpServletResponse resp) {
+//             RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(req));
+//     }
+
+       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+               try {
+                       response.sendRedirect(loginPage);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot redirect to login page", e);
+               }
+               return null;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsSessionDescriptor.java
new file mode 100644 (file)
index 0000000..30de616
--- /dev/null
@@ -0,0 +1,96 @@
+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/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/CmsTokenServlet.java
new file mode 100644 (file)
index 0000000..c355ecd
--- /dev/null
@@ -0,0 +1,117 @@
+package org.argeo.cms.integration;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.osgi.service.useradmin.Authorization;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides access to tokens. */
+public class CmsTokenServlet extends HttpServlet {
+       private static final long serialVersionUID = 302918711430864140L;
+
+       public final static String PARAM_EXPIRY_DATE = "expiryDate";
+       public final static String PARAM_TOKEN = "token";
+
+       private final static int DEFAULT_HOURS = 24;
+
+       private CmsUserManager userManager;
+       private ObjectMapper objectMapper = new ObjectMapper();
+
+       @Override
+       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               ServletHttpRequest request = new ServletHttpRequest(req);
+               ServletHttpResponse response = new ServletHttpResponse(resp);
+               LoginContext lc = null;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(request, response) {
+                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                       for (Callback callback : callbacks) {
+                                               if (callback instanceof RemoteAuthCallback) {
+                                                       ((RemoteAuthCallback) callback).setRequest(request);
+                                                       ((RemoteAuthCallback) callback).setResponse(response);
+                                               }
+                                       }
+                               }
+                       });
+                       lc.login();
+               } catch (LoginException e) {
+                       // ignore
+               }
+
+               try {
+                       Subject subject = lc.getSubject();
+                       Authorization authorization = extractFrom(subject.getPrivateCredentials(Authorization.class));
+                       String token = UUID.randomUUID().toString();
+                       String expiryDateStr = req.getParameter(PARAM_EXPIRY_DATE);
+                       ZonedDateTime expiryDate;
+                       if (expiryDateStr != null) {
+                               expiryDate = NamingUtils.ldapDateToZonedDateTime(expiryDateStr);
+                       } else {
+                               expiryDate = ZonedDateTime.now().plusHours(DEFAULT_HOURS);
+                               expiryDateStr = NamingUtils.instantToLdapDate(expiryDate);
+                       }
+                       userManager.addAuthToken(authorization.getName(), token, expiryDate);
+
+                       TokenDescriptor tokenDescriptor = new TokenDescriptor();
+                       tokenDescriptor.setUsername(authorization.getName());
+                       tokenDescriptor.setToken(token);
+                       tokenDescriptor.setExpiryDate(expiryDateStr);
+//                     tokenDescriptor.setRoles(Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(roles))));
+
+                       resp.setContentType("application/json");
+                       JsonGenerator jg = objectMapper.getFactory().createGenerator(resp.getWriter());
+                       jg.writeObject(tokenDescriptor);
+               } catch (Exception e) {
+                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+               }
+       }
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               // temporarily wrap POST for ease of testing
+               doPost(req, resp);
+       }
+
+       @Override
+       protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               try {
+                       String token = req.getParameter(PARAM_TOKEN);
+                       userManager.expireAuthToken(token);
+               } catch (Exception e) {
+                       new CmsExceptionsChain(e).writeAsJson(objectMapper, resp);
+               }
+       }
+
+       protected <T> T extractFrom(Set<T> creds) {
+               if (creds.size() > 0)
+                       return creds.iterator().next();
+               else
+                       return null;
+       }
+
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/TokenDescriptor.java
new file mode 100644 (file)
index 0000000..1541b4f
--- /dev/null
@@ -0,0 +1,49 @@
+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/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/integration/package-info.java
new file mode 100644 (file)
index 0000000..1405737
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS integration (JSON, web services). */
+package org.argeo.cms.integration;
\ No newline at end of file
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java
new file mode 100644 (file)
index 0000000..d3c0eb5
--- /dev/null
@@ -0,0 +1,120 @@
+package org.argeo.cms.servlet;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.internal.HttpUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.http.context.ServletContextHelper;
+
+/**
+ * Default servlet context degrading to anonymous if the the session is not
+ * pre-authenticated.
+ */
+public class CmsServletContext extends ServletContextHelper {
+       private final static CmsLog log = CmsLog.getLog(CmsServletContext.class);
+       // use CMS bundle for resources
+       private Bundle bundle = FrameworkUtil.getBundle(getClass());
+
+       private final String httpAuthRealm = "Argeo";
+       private final boolean forceBasic = false;
+
+       public void init(Map<String, String> properties) {
+
+       }
+
+       public void destroy() {
+
+       }
+
+       @Override
+       public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
+               if (log.isTraceEnabled())
+                       HttpUtils.logRequestHeaders(log, request);
+               RemoteAuthRequest remoteAuthRequest = new ServletHttpRequest(request);
+               RemoteAuthResponse remoteAuthResponse = new ServletHttpResponse(response);
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+               LoginContext lc;
+               try {
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+                       lc.login();
+               } catch (LoginException e) {
+                       if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest,
+                                               remoteAuthResponse, httpAuthRealm,
+                                               forceBasic);
+                               response.setStatus(statusCode);
+                               return false;
+
+                       } else {
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+                       }
+                       if (lc == null)
+                               return false;
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
+
+//             Subject subject = lc.getSubject();
+//             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+//                             return null;
+//                     }
+//
+//             });
+               return true;
+       }
+
+//     @Override
+//     public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
+//             RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
+//     }
+
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return false;
+       }
+
+//     protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+//             // anonymous
+//             ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+//             try {
+//                     Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+//                     LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
+//                                     new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+//                     lc.login();
+//                     return lc;
+//             } catch (LoginException e1) {
+//                     if (log.isDebugEnabled())
+//                             log.error("Cannot log in as anonymous", e1);
+//                     return null;
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+//             }
+//     }
+
+       @Override
+       public URL getResource(String name) {
+               // TODO make it more robust and versatile
+               // if used directly it can only load from within this bundle
+               return bundle.getResource(name);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
new file mode 100644 (file)
index 0000000..cd28b6e
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.servlet;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+/** Servlet context forcing authentication. */
+public class PrivateWwwAuthServletContext extends CmsServletContext {
+       // TODO make it configurable
+//     private final String httpAuthRealm = "Argeo";
+//     private final boolean forceBasic = false;
+       
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest,
+                       RemoteAuthResponse remoteAuthResponse) {
+               return true;
+       }
+
+
+//     @Override
+//     protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+//             askForWwwAuth(request, response);
+//             return null;
+//     }
+//
+//     protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
+//             // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+//             // realm=\"" + httpAuthRealm + "\"");
+//             if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
+//                     response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
+//             else
+//                     response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+//                                     HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + httpAuthRealm + "\"");
+//
+//             // response.setDateHeader("Date", System.currentTimeMillis());
+//             // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
+//             // 60 * 60 * 1000));
+//             // response.setHeader("Accept-Ranges", "bytes");
+//             // response.setHeader("Connection", "Keep-Alive");
+//             // response.setHeader("Keep-Alive", "timeout=5, max=97");
+//             // response.setContentType("text/html; charset=UTF-8");
+//             response.setStatus(401);
+//     }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpRequest.java
new file mode 100644 (file)
index 0000000..54c8804
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.cms.servlet;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpRequest implements RemoteAuthRequest {
+       private final HttpServletRequest request;
+
+       public ServletHttpRequest(HttpServletRequest request) {
+               Objects.requireNonNull(request);
+               this.request = request;
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               HttpSession httpSession = request.getSession(false);
+               if (httpSession == null)
+                       return null;
+               return new ServletHttpSession(httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               return new ServletHttpSession(request.getSession(true));
+       }
+
+       @Override
+       public Locale getLocale() {
+               return request.getLocale();
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return request.getAttribute(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               request.setAttribute(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               return request.getHeader(key);
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               return request.getRemoteAddr();
+       }
+
+       @Override
+       public int getLocalPort() {
+               return request.getLocalPort();
+       }
+
+       @Override
+       public int getRemotePort() {
+               return request.getRemotePort();
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpResponse.java
new file mode 100644 (file)
index 0000000..0c600e5
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.cms.servlet;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class ServletHttpResponse implements RemoteAuthResponse {
+       private final HttpServletResponse response;
+
+       public ServletHttpResponse(HttpServletResponse response) {
+               Objects.requireNonNull(response);
+               this.response = response;
+       }
+
+       @Override
+       public void setHeader(String headerName, String value) {
+               response.setHeader(headerName, value);
+       }
+
+       @Override
+       public void addHeader(String headerName, String value) {
+               response.addHeader(headerName, value);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletHttpSession.java
new file mode 100644 (file)
index 0000000..8d087da
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.cms.servlet;
+
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpSession implements RemoteAuthSession {
+       private javax.servlet.http.HttpSession session;
+
+       public ServletHttpSession(javax.servlet.http.HttpSession session) {
+               super();
+               this.session = session;
+       }
+
+       @Override
+       public boolean isValid() {
+               try {// test http session
+                       session.getCreationTime();
+                       return true;
+               } catch (IllegalStateException ise) {
+                       return false;
+               }
+       }
+
+       @Override
+       public String getId() {
+               return session.getId();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/ServletUtils.java
new file mode 100644 (file)
index 0000000..072417a
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.servlet;
+
+import static org.argeo.cms.http.HttpHeader.VIA;
+import static org.argeo.cms.http.HttpHeader.X_FORWARDED_HOST;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+/** Servlet utilities. */
+public class ServletUtils {
+
+       /**
+        * The base URL for this query (without any path component (not even an ending
+        * '/'), taking into account reverse proxies.
+        */
+       public static StringBuilder getRequestUrlBase(HttpServletRequest req) {
+               List<String> viaHosts = new ArrayList<>();
+               for (Enumeration<String> it = req.getHeaders(VIA.getHeaderName()); it.hasMoreElements();) {
+                       String[] arr = it.nextElement().split(" ");
+                       viaHosts.add(arr[1]);
+               }
+
+               String outerHost = viaHosts.isEmpty() ? null : viaHosts.get(0);
+               if (outerHost == null) {
+                       // Try non-standard header
+                       String forwardedHost = req.getHeader(X_FORWARDED_HOST.getHeaderName());
+                       if (forwardedHost != null) {
+                               String[] arr = forwardedHost.split(",");
+                               outerHost = arr[0];
+                       }
+               }
+
+               URI requestUrl = URI.create(req.getRequestURL().toString());
+
+               boolean isReverseProxy = outerHost != null && !outerHost.equals(requestUrl.getHost());
+               if (isReverseProxy) {
+                       String protocol = req.isSecure() ? "https" : "http";
+                       return new StringBuilder(protocol + "://" + outerHost);
+               } else {
+                       return new StringBuilder(requestUrl.getScheme() + "://" + requestUrl.getHost()
+                                       + (requestUrl.getPort() > 0 ? ":" + requestUrl.getPort() : ""));
+               }
+       }
+
+       /** singleton */
+       private ServletUtils() {
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/HttpContextServlet.java
new file mode 100644 (file)
index 0000000..63d59a8
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpPrincipal;
+
+/**
+ * An {@link HttpServlet} which integrates an {@link HttpContext} and its
+ * {@link Authenticator} in a servlet container.
+ */
+public class HttpContextServlet extends HttpServlet {
+       private static final long serialVersionUID = 2321612280413662738L;
+
+       private final HttpContext httpContext;
+
+       public HttpContextServlet(HttpContext httpContext) {
+               this.httpContext = httpContext;
+       }
+
+       @Override
+       protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               try (ServletHttpExchange httpExchange = new ServletHttpExchange(httpContext, req, resp)) {
+                       ServletHttpSession httpSession = new ServletHttpSession(req.getSession());
+                       httpExchange.setAttribute(RemoteAuthSession.class.getName(), httpSession);
+                       Authenticator authenticator = httpContext.getAuthenticator();
+                       if (authenticator != null) {
+                               Authenticator.Result authenticationResult = authenticator.authenticate(httpExchange);
+                               if (authenticationResult instanceof Authenticator.Success) {
+                                       HttpPrincipal httpPrincipal = ((Authenticator.Success) authenticationResult).getPrincipal();
+                                       httpExchange.setPrincipal(httpPrincipal);
+                               } else if (authenticationResult instanceof Authenticator.Retry) {
+                                       httpExchange.sendResponseHeaders((((Authenticator.Retry) authenticationResult).getResponseCode()),
+                                                       -1);
+                                       resp.flushBuffer();
+                                       return;
+                               } else if (authenticationResult instanceof Authenticator.Failure) {
+                                       httpExchange.sendResponseHeaders(((Authenticator.Failure) authenticationResult).getResponseCode(),
+                                                       -1);
+                                       resp.flushBuffer();
+                                       return;
+                               } else {
+                                       throw new UnsupportedOperationException(
+                                                       "Authentication result " + authenticationResult.getClass().getName() + " is not supported");
+                               }
+                       }
+
+                       HttpHandler httpHandler = httpContext.getHandler();
+                       httpHandler.handle(httpExchange);
+               }
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/httpserver/ServletHttpExchange.java
new file mode 100644 (file)
index 0000000..f5e9c03
--- /dev/null
@@ -0,0 +1,188 @@
+package org.argeo.cms.servlet.httpserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.net.ssl.SSLSession;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpPrincipal;
+import com.sun.net.httpserver.HttpsExchange;
+
+/** Integrates {@link HttpsExchange} in a servlet container. */
+class ServletHttpExchange extends HttpsExchange {
+       private final HttpContext httpContext;
+       private final HttpServletRequest httpServletRequest;
+       private final HttpServletResponse httpServletResponse;
+
+       private final Headers requestHeaders;
+       private final Headers responseHeaders;
+
+       private InputStream filteredIn;
+       private OutputStream filteredOut;
+
+       private HttpPrincipal principal;
+
+       public ServletHttpExchange(HttpContext httpContext, HttpServletRequest httpServletRequest,
+                       HttpServletResponse httpServletResponse) {
+               this.httpContext = httpContext;
+               this.httpServletRequest = httpServletRequest;
+               this.httpServletResponse = httpServletResponse;
+
+               // request headers
+               requestHeaders = new Headers();
+               for (Enumeration<String> headerNames = httpServletRequest.getHeaderNames(); headerNames.hasMoreElements();) {
+                       String headerName = headerNames.nextElement();
+                       List<String> values = new ArrayList<>();
+                       for (Enumeration<String> headerValues = httpServletRequest.getHeaders(headerName); headerValues
+                                       .hasMoreElements();)
+                               values.add(headerValues.nextElement());
+                       requestHeaders.put(headerName, values);
+               }
+
+               responseHeaders = new Headers();
+       }
+
+       @Override
+       public SSLSession getSSLSession() {
+               Object obj = httpServletRequest.getAttribute("javax.net.ssl.session");
+               if (obj == null || !(obj instanceof SSLSession))
+                       throw new IllegalStateException("SSL session not found");
+               return (SSLSession) obj;
+       }
+
+       @Override
+       public Headers getRequestHeaders() {
+               return requestHeaders;
+       }
+
+       @Override
+       public Headers getResponseHeaders() {
+               return responseHeaders;
+       }
+
+       @Override
+       public URI getRequestURI() {
+               return URI.create(httpServletRequest.getRequestURI());
+       }
+
+       @Override
+       public String getRequestMethod() {
+               return httpServletRequest.getMethod();
+       }
+
+       @Override
+       public HttpContext getHttpContext() {
+               return httpContext;
+       }
+
+       @Override
+       public void close() {
+               try {
+                       httpServletRequest.getInputStream().close();
+               } catch (IOException e) {
+                       // TODO use proper logging
+                       e.printStackTrace();
+               }
+               try {
+                       httpServletResponse.getOutputStream().close();
+               } catch (IOException e) {
+                       // TODO use proper logging
+                       e.printStackTrace();
+               }
+
+       }
+
+       @Override
+       public InputStream getRequestBody() {
+               try {
+                       if (filteredIn != null)
+                               return filteredIn;
+                       else
+                               return httpServletRequest.getInputStream();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot get request body", e);
+               }
+       }
+
+       @Override
+       public OutputStream getResponseBody() {
+               try {
+                       if (filteredOut != null)
+                               return filteredOut;
+                       else
+                               return httpServletResponse.getOutputStream();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot get response body", e);
+               }
+       }
+
+       @Override
+       public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+               for (String headerName : responseHeaders.keySet()) {
+                       for (String headerValue : responseHeaders.get(headerName)) {
+                               httpServletResponse.addHeader(headerName, headerValue);
+                       }
+               }
+               // TODO deal with content length etc.
+               httpServletResponse.setStatus(rCode);
+       }
+
+       @Override
+       public InetSocketAddress getRemoteAddress() {
+               return new InetSocketAddress(httpServletRequest.getRemoteHost(), httpServletRequest.getRemotePort());
+       }
+
+       @Override
+       public int getResponseCode() {
+               return httpServletResponse.getStatus();
+       }
+
+       @Override
+       public InetSocketAddress getLocalAddress() {
+               return new InetSocketAddress(httpServletRequest.getLocalName(), httpServletRequest.getLocalPort());
+       }
+
+       @Override
+       public String getProtocol() {
+               return httpServletRequest.getProtocol();
+       }
+
+       @Override
+       public Object getAttribute(String name) {
+               return httpServletRequest.getAttribute(name);
+       }
+
+       @Override
+       public void setAttribute(String name, Object value) {
+               httpServletRequest.setAttribute(name, value);
+       }
+
+       @Override
+       public void setStreams(InputStream i, OutputStream o) {
+               if (i != null)
+                       filteredIn = i;
+               if (o != null)
+                       filteredOut = o;
+
+       }
+
+       @Override
+       public HttpPrincipal getPrincipal() {
+               return principal;
+       }
+
+       void setPrincipal(HttpPrincipal principal) {
+               this.principal = principal;
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/HttpUtils.java
new file mode 100644 (file)
index 0000000..f0e11f8
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.cms.servlet.internal;
+
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsLog;
+
+public class HttpUtils {
+//     public final static String HEADER_AUTHORIZATION = "Authorization";
+//     public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+
+       static boolean isBrowser(String userAgent) {
+               return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox")
+                               || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium")
+                               || userAgent.contains("opera") || userAgent.contains("browser");
+       }
+
+       public static void logResponseHeaders(CmsLog log, HttpServletResponse response) {
+               if (!log.isDebugEnabled())
+                       return;
+               for (String headerName : response.getHeaderNames()) {
+                       Object headerValue = response.getHeader(headerName);
+                       log.debug(headerName + ": " + headerValue);
+               }
+       }
+
+       public static void logRequestHeaders(CmsLog log, HttpServletRequest request) {
+               if (!log.isDebugEnabled())
+                       return;
+               for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) {
+                       String headerName = headerNames.nextElement();
+                       Object headerValue = request.getHeader(headerName);
+                       log.debug(headerName + ": " + headerValue);
+               }
+               log.debug(request.getRequestURI() + "\n");
+       }
+
+       public static void logRequest(CmsLog log, HttpServletRequest request) {
+               log.debug("contextPath=" + request.getContextPath());
+               log.debug("servletPath=" + request.getServletPath());
+               log.debug("requestURI=" + request.getRequestURI());
+               log.debug("queryString=" + request.getQueryString());
+               StringBuilder buf = new StringBuilder();
+               // headers
+               Enumeration<String> en = request.getHeaderNames();
+               while (en.hasMoreElements()) {
+                       String header = en.nextElement();
+                       Enumeration<String> values = request.getHeaders(header);
+                       while (values.hasMoreElements())
+                               buf.append("  " + header + ": " + values.nextElement());
+                       buf.append('\n');
+               }
+
+               // attributed
+               Enumeration<String> an = request.getAttributeNames();
+               while (an.hasMoreElements()) {
+                       String attr = an.nextElement();
+                       Object value = request.getAttribute(attr);
+                       buf.append("  " + attr + ": " + value);
+                       buf.append('\n');
+               }
+               log.debug("\n" + buf);
+       }
+
+       private HttpUtils() {
+
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/PkgServlet.java
new file mode 100644 (file)
index 0000000..2b2ffcb
--- /dev/null
@@ -0,0 +1,133 @@
+package org.argeo.cms.servlet.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collection;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.osgi.FilterRequirement;
+import org.argeo.cms.osgi.PublishNamespace;
+import org.argeo.cms.util.StreamUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.resource.Requirement;
+
+public class PkgServlet extends HttpServlet {
+       private static final long serialVersionUID = 7660824185145214324L;
+
+       private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
+
+       @Override
+       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+               String pathInfo = req.getPathInfo();
+
+               String pkg, versionStr, file;
+               String[] parts = pathInfo.split("/");
+               // first is always empty
+               if (parts.length == 4) {
+                       pkg = parts[1];
+                       versionStr = parts[2];
+                       file = parts[3];
+               } else if (parts.length == 3) {
+                       pkg = parts[1];
+                       versionStr = null;
+                       file = parts[2];
+               } else {
+                       throw new IllegalArgumentException("Unsupported path length " + pathInfo);
+               }
+
+               FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
+               String filter;
+               if (versionStr == null) {
+                       filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")";
+               } else {
+                       if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range
+                               VersionRange versionRange = new VersionRange(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"
+                                               + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")";
+
+                       } else {
+                               Version version = new Version(versionStr);
+                               filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")("
+                                               + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))";
+                       }
+               }
+               Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter);
+               Collection<BundleCapability> packages = frameworkWiring.findProviders(requirement);
+               if (packages.isEmpty()) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               // TODO verify that it works with multiple versions
+               SortedMap<Version, BundleCapability> sorted = new TreeMap<>();
+               for (BundleCapability capability : packages) {
+                       sorted.put(capability.getRevision().getVersion(), capability);
+               }
+
+               Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle();
+               String entryPath = '/' + pkg.replace('.', '/') + '/' + file;
+               URL internalURL = bundle.getResource(entryPath);
+               if (internalURL == null) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               // Resource found, we now check whether it can be published
+               boolean publish = false;
+               BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
+               capabilities: for (BundleCapability bundleCapability : bundleWiring
+                               .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) {
+                       Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG);
+                       if (publishedPkg != null) {
+                               if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) {
+                                       Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE);
+                                       if (publishedFile == null) {
+                                               publish = true;
+                                               break capabilities;
+                                       } else {
+                                               String[] publishedFiles = publishedFile.toString().split(",");
+                                               for (String pattern : publishedFiles) {
+                                                       if (pattern.startsWith("*.")) {
+                                                               String ext = pattern.substring(1);
+                                                               if (file.endsWith(ext)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       } else {
+                                                               if (publishedFile.equals(file)) {
+                                                                       publish = true;
+                                                                       break capabilities;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               if (!publish) {
+                       resp.sendError(404);
+                       return;
+               }
+
+               try (InputStream in = internalURL.openStream()) {
+                       StreamUtils.copy(in, resp.getOutputStream());
+               }
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/internal/RobotServlet.java
new file mode 100644 (file)
index 0000000..288ee26
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.cms.servlet.internal;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class RobotServlet extends HttpServlet {
+       private static final long serialVersionUID = 7935661175336419089L;
+
+       @Override
+       protected void service(HttpServletRequest request, HttpServletResponse response)
+                       throws ServletException, IOException {
+               PrintWriter writer = response.getWriter();
+               writer.append("User-agent: *\n");
+               writer.append("Disallow:\n");
+               response.setHeader("Content-Type", "text/plain");
+               writer.flush();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java
new file mode 100644 (file)
index 0000000..4dfdc5d
--- /dev/null
@@ -0,0 +1,135 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.websocket.Extension;
+import javax.websocket.HandshakeResponse;
+import javax.websocket.server.HandshakeRequest;
+import javax.websocket.server.ServerEndpointConfig;
+import javax.websocket.server.ServerEndpointConfig.Configurator;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.CmsServletContext;
+
+/**
+ * <strong>Disabled until third party issues are solved.</strong>. Customises
+ * the initialisation of a new web socket.
+ */
+public class CmsWebSocketConfigurator extends Configurator {
+
+       private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
+
+       private final String httpAuthRealm = "Argeo";
+
+       @Override
+       public boolean checkOrigin(String originHeaderValue) {
+               return true;
+       }
+
+       @Override
+       public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
+               try {
+                       return endpointClass.getDeclaredConstructor().newInstance();
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Cannot get endpoint instance", e);
+               }
+       }
+
+       @Override
+       public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) {
+               return requested;
+       }
+
+       @Override
+       public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) {
+               if ((requested == null) || (requested.size() == 0))
+                       return "";
+               if ((supported == null) || (supported.isEmpty()))
+                       return "";
+               for (String possible : requested) {
+                       if (possible == null)
+                               continue;
+                       if (supported.contains(possible))
+                               return possible;
+               }
+               return "";
+       }
+
+       @Override
+       public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
+//             if (true)
+//                     return;
+
+               WebSocketHandshakeRequest remoteAuthRequest = new WebSocketHandshakeRequest(request);
+               WebSocketHandshakeResponse remoteAuthResponse = new WebSocketHandshakeResponse(response);
+//             RemoteAuthSession httpSession = new ServletHttpSession(
+//                             (javax.servlet.http.HttpSession) request.getHttpSession());
+               RemoteAuthSession remoteAuthSession = remoteAuthRequest.getSession();
+               if (log.isDebugEnabled() && remoteAuthSession != null)
+                       log.debug("Web socket HTTP session id: " + remoteAuthSession.getId());
+
+//             if (remoteAuthSession == null) {
+//                     rejectResponse(response, null);
+//             }
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+               LoginContext lc;
+               try {
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+                       lc.login();
+               } catch (LoginException e) {
+                       if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthRequest, remoteAuthResponse, httpAuthRealm,
+                                               true);
+//                             remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode));
+                               return;
+                       } else {
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+                       }
+                       if (lc == null) {
+                               rejectResponse(response, e);
+                               return;
+                       }
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
+
+//             Subject subject = lc.getSubject();
+//             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             // TODO also set login context in order to log out ?
+//                             RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+//                             return null;
+//                     }
+//
+//             });
+       }
+
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return true;
+       }
+
+       /**
+        * Behaviour when the web socket could not be authenticated. Throws an
+        * {@link IllegalStateException} by default.
+        * 
+        * @param e can be null
+        */
+       protected void rejectResponse(HandshakeResponse response, Exception e) {
+               response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList<String>());
+               // violent implementation, as suggested in
+               // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
+//             throw new IllegalStateException("Web socket cannot be authenticated");
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/EventEndpoint.java
new file mode 100644 (file)
index 0000000..defc59e
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.cms.websocket.server;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.util.Map;
+
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+@ServerEndpoint(value = "/cms/status/event/{topic}", configurator = CmsWebSocketConfigurator.class)
+public class EventEndpoint implements CmsEventSubscriber {
+       private final static CmsLog log = CmsLog.getLog(EventEndpoint.class);
+       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+
+       private RemoteEndpoint.Basic remote;
+       private CmsEventBus cmsEventBus;
+
+//     private String topic = "cms";
+
+       @OnOpen
+       public void onOpen(Session session, @PathParam("topic") String topic) {
+               if (bc != null) {
+                       cmsEventBus = bc.getService(bc.getServiceReference(CmsEventBus.class));
+                       cmsEventBus.addEventSubscriber(topic, this);
+               }
+               remote = session.getBasicRemote();
+
+       }
+
+       @OnClose
+       public void onClose(@PathParam("topic") String topic) {
+               cmsEventBus.removeEventSubscriber(topic, this);
+       }
+
+       @Override
+       public void onEvent(String topic, Map<String, Object> properties) {
+               try {
+                       remote.sendText(topic + ": " + properties);
+               } catch (IOException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       @OnError
+       public void onError(Throwable e) {
+               if (e instanceof ClosedChannelException) {
+                       // ignore, as it probably means ping was closed on the other side
+                       return;
+               }
+               log.error("Cannot process ping", e);
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PingEndpoint.java
new file mode 100644 (file)
index 0000000..dcbce67
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.cms.websocket.server;
+
+import java.nio.channels.ClosedChannelException;
+
+import javax.websocket.OnError;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.cms.CmsLog;
+
+@ServerEndpoint(value = "/cms/status/ping", configurator = PublicWebSocketConfigurator.class)
+public class PingEndpoint {
+       private final static CmsLog log = CmsLog.getLog(PingEndpoint.class);
+
+       @OnError
+       public void onError(Throwable e) {
+               if (e instanceof ClosedChannelException) {
+                       // ignore, as it probably means ping was closed on the other side
+                       return;
+               }
+               log.error("Cannot process ping", e);
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/PublicWebSocketConfigurator.java
new file mode 100644 (file)
index 0000000..e3e17cf
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.websocket.server;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicWebSocketConfigurator extends CmsWebSocketConfigurator {
+
+       @Override
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return false;
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/StatusHandler.java
new file mode 100644 (file)
index 0000000..a8466fe
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.cms.websocket.server;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+public class StatusHandler implements WebsocketEndpoints, HttpHandler {
+       private CmsState cmsState;
+
+       @Override
+       public Set<Class<?>> getEndPoints() {
+               Set<Class<?>> res = new HashSet<>();
+               res.add(PingEndpoint.class);
+               res.add(EventEndpoint.class);
+               res.add(TestEndpoint.class);
+               return res;
+       }
+
+       @Override
+       public void handle(HttpExchange exchange) throws IOException {
+
+               StringJoiner sb = new StringJoiner("\n");
+               CmsDeployProperty[] deployProperties = CmsDeployProperty.values();
+               Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name()));
+               for (CmsDeployProperty deployProperty : deployProperties) {
+                       List<String> values = cmsState.getDeployProperties(deployProperty.getProperty());
+                       for (int i = 0; i < values.size(); i++) {
+                               String value = values.get(i);
+                               if (value != null) {
+                                       String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value;
+                                       sb.add(line);
+                               }
+                       }
+               }
+
+               byte[] msg = sb.toString().getBytes(StandardCharsets.UTF_8);
+               exchange.sendResponseHeaders(200, msg.length);
+               exchange.getResponseBody().write(msg);
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/TestEndpoint.java
new file mode 100644 (file)
index 0000000..f0c7fca
--- /dev/null
@@ -0,0 +1,183 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.websocket.CloseReason;
+import javax.websocket.EndpointConfig;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.integration.CmsExceptionsChain;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/** Provides WebSocket access. */
+@ServerEndpoint(value = "/cms/status/test/{topic}", configurator = CmsWebSocketConfigurator.class)
+public class TestEndpoint implements EventHandler {
+       private final static CmsLog log = CmsLog.getLog(TestEndpoint.class);
+
+       final static String TOPICS_BASE = "/test";
+       final static String INPUT = "input";
+       final static String TOPIC = "topic";
+       final static String VIEW_UID = "viewUid";
+       final static String COMPUTATION_UID = "computationUid";
+       final static String MESSAGES = "messages";
+       final static String ERRORS = "errors";
+
+       final static String EXCEPTION = "exception";
+       final static String MESSAGE = "message";
+
+       private BundleContext bc = FrameworkUtil.getBundle(TestEndpoint.class).getBundleContext();
+
+       private String wsSessionId;
+       private RemoteEndpoint.Basic remote;
+       private ServiceRegistration<EventHandler> eventHandlerSr;
+
+       // json
+       private ObjectMapper objectMapper = new ObjectMapper();
+
+       private WebSocketView view;
+
+       @OnOpen
+       public void onOpen(Session session, EndpointConfig endpointConfig) {
+               Map<String, List<String>> parameters = NamingUtils.queryToMap(session.getRequestURI());
+               String path = NamingUtils.getQueryValue(parameters, "path");
+               log.debug("WS Path: " + path);
+
+               wsSessionId = session.getId();
+
+               // 24h timeout
+               session.setMaxIdleTimeout(1000 * 60 * 60 * 24);
+
+               Map<String, Object> userProperties = session.getUserProperties();
+               Subject subject = null;
+//             AccessControlContext accessControlContext = (AccessControlContext) userProperties
+//                             .get(ServletContextHelper.REMOTE_USER);
+//             Subject subject = Subject.getSubject(accessControlContext);
+//             // Deal with authentication failure
+//             if (subject == null) {
+//                     try {
+//                             CloseReason.CloseCode closeCode = new CloseReason.CloseCode() {
+//
+//                                     @Override
+//                                     public int getCode() {
+//                                             return 4001;
+//                                     }
+//                             };
+//                             session.close(new CloseReason(closeCode, "Unauthorized"));
+//                             if (log.isTraceEnabled())
+//                                     log.trace("Unauthorized web socket " + wsSessionId + ". Closing with code " + closeCode.getCode()
+//                                                     + ".");
+//                             return;
+//                     } catch (IOException e) {
+//                             // silent
+//                     }
+//                     return;// ignore
+//             }
+
+               if (log.isDebugEnabled())
+                       log.debug("WS#" + wsSessionId + " open for: " + subject);
+               remote = session.getBasicRemote();
+               view = new WebSocketView(subject);
+
+               // OSGi events
+               String[] topics = new String[] { TOPICS_BASE + "/*" };
+               Hashtable<String, Object> ht = new Hashtable<>();
+               ht.put(EventConstants.EVENT_TOPIC, topics);
+               ht.put(EventConstants.EVENT_FILTER, "(" + VIEW_UID + "=" + view.getUid() + ")");
+               eventHandlerSr = bc.registerService(EventHandler.class, this, ht);
+
+               if (log.isDebugEnabled())
+                       log.debug("New view " + view.getUid() + " opened, via web socket.");
+       }
+
+       @OnMessage
+       public void onWebSocketText(@PathParam("topic") String topic, Session session, String message)
+                       throws JsonMappingException, JsonProcessingException {
+               try {
+                       if (log.isTraceEnabled())
+                               log.trace("WS#" + view.getUid() + " received:\n" + message + "\n");
+//                     JsonNode jsonNode = objectMapper.readTree(message);
+//                     String topic = jsonNode.get(TOPIC).textValue();
+
+                       final String computationUid = null;
+//                     if (MY_TOPIC.equals(topic)) {
+//                             view.checkRole(SPECIFIC_ROLE);
+//                             computationUid= process();
+//                     }
+                       remote.sendText("ACK " + topic);
+               } catch (Exception e) {
+                       log.error("Error when receiving web socket message", e);
+                       sendSystemErrorMessage(e);
+               }
+       }
+
+       @OnClose
+       public void onWebSocketClose(CloseReason reason) {
+               if (eventHandlerSr != null)
+                       eventHandlerSr.unregister();
+               if (view != null && log.isDebugEnabled())
+                       log.debug("WS#" + view.getUid() + " closed: " + reason);
+       }
+
+       @OnError
+       public void onWebSocketError(Throwable cause) {
+               if (view != null) {
+                       log.error("WS#" + view.getUid() + " ERROR", cause);
+               } else {
+                       if (log.isTraceEnabled())
+                               log.error("Error in web socket session " + wsSessionId, cause);
+               }
+       }
+
+       @Override
+       public void handleEvent(Event event) {
+               try {
+                       Object uid = event.getProperty(COMPUTATION_UID);
+                       Exception exception = (Exception) event.getProperty(EXCEPTION);
+                       if (exception != null) {
+                               CmsExceptionsChain systemErrors = new CmsExceptionsChain(exception);
+                               String sent = systemErrors.toJsonString(objectMapper);
+                               remote.sendText(sent);
+                               return;
+                       }
+                       String topic = event.getTopic();
+                       if (log.isTraceEnabled())
+                               log.trace("WS#" + view.getUid() + " " + topic + ": notify event " + topic + "#" + uid + ", " + event);
+               } catch (Exception e) {
+                       log.error("Error when handling event for WebSocket", e);
+                       sendSystemErrorMessage(e);
+               }
+
+       }
+
+       /** Sends an error message in JSON format. */
+       protected void sendSystemErrorMessage(Exception e) {
+               CmsExceptionsChain systemErrors = new CmsExceptionsChain(e);
+               try {
+                       if (remote != null)
+                               remote.sendText(systemErrors.toJsonString(objectMapper));
+               } catch (Exception e1) {
+                       log.error("Cannot send WebSocket system error messages " + systemErrors, e1);
+               }
+       }
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java
new file mode 100644 (file)
index 0000000..31bcf92
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.server.HandshakeRequest;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+public class WebSocketHandshakeRequest implements RemoteAuthRequest {
+       private final HandshakeRequest handshakeRequest;
+       private final HttpSession httpSession;
+
+       private Map<String, Object> attributes = new HashMap<>();
+
+       public WebSocketHandshakeRequest(HandshakeRequest handshakeRequest) {
+               Objects.requireNonNull(handshakeRequest);
+               this.handshakeRequest = handshakeRequest;
+               this.httpSession = (HttpSession) handshakeRequest.getHttpSession();
+//             Objects.requireNonNull(this.httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               if (httpSession == null)
+                       return null;
+               return new ServletHttpSession(httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public Locale getLocale() {
+               // TODO check Accept-Language header
+               return Locale.getDefault();
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return attributes.get(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               attributes.put(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               List<String> values = handshakeRequest.getHeaders().get(key);
+               if (values.size() == 0)
+                       return null;
+               if (values.size() > 1)
+                       throw new IllegalStateException("More that one value for " + key + ": " + values);
+               return values.get(0);
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getLocalPort() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getRemotePort() {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java
new file mode 100644 (file)
index 0000000..b003c63
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.websocket.HandshakeResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class WebSocketHandshakeResponse implements RemoteAuthResponse {
+       private final HandshakeResponse handshakeResponse;
+
+       public WebSocketHandshakeResponse(HandshakeResponse handshakeResponse) {
+               this.handshakeResponse = handshakeResponse;
+       }
+
+       @Override
+       public void setHeader(String headerName, String value) {
+               handshakeResponse.getHeaders().put(headerName, Collections.singletonList(value));
+       }
+
+       @Override
+       public void addHeader(String headerName, String value) {
+               List<String> values = handshakeResponse.getHeaders().getOrDefault(headerName, new ArrayList<>());
+               values.add(value);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketTest.java
new file mode 100644 (file)
index 0000000..b10bcfd
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cms.websocket.server;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketTest {
+
+       public static void main(String[] args) throws Exception {
+               CompletableFuture<Boolean> received = new CompletableFuture<>();
+               WebSocket.Listener listener = new WebSocket.Listener() {
+
+                       public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+                               System.out.println(message);
+                               CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+                               received.complete(true);
+                               return res;
+                       }
+               };
+
+               HttpClient client = HttpClient.newHttpClient();
+               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                               .buildAsync(URI.create("ws://localhost:7070/cms/status/test/my%20topic?path=my%2Frelative%2Fpath"), listener);
+               WebSocket webSocket = ws.get();
+               webSocket.sendText("TEST", true);
+
+               received.get(10, TimeUnit.SECONDS);
+               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketView.java
new file mode 100644 (file)
index 0000000..736631b
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.cms.websocket.server;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Abstraction of a single Frontend view, that is a web browser page. There can
+ * be multiple views within one single authenticated HTTP session.
+ */
+public class WebSocketView {
+       private final String uid;
+       private Subject subject;
+
+       public WebSocketView(Subject subject) {
+               this.uid = UUID.randomUUID().toString();
+               this.subject = subject;
+       }
+
+       public String getUid() {
+               return uid;
+       }
+
+       public Set<String> getRoles() {
+               return roles(subject);
+       }
+
+       public boolean isInRole(String role) {
+               return getRoles().contains(role);
+       }
+
+       public void checkRole(String role) {
+               checkRole(subject, role);
+       }
+
+       public final static Set<String> roles(Subject subject) {
+               Set<String> roles = new HashSet<String>();
+               X500Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
+               String username = principal.getName();
+               roles.add(username);
+               for (Principal group : subject.getPrincipals()) {
+                       if (group instanceof Role)
+                               roles.add(group.getName());
+               }
+               return roles;
+       }
+
+       public static void checkRole(Subject subject, String role) {
+               Set<String> roles = roles(subject);
+               if (!roles.contains(role))
+                       throw new IllegalStateException("User is not in role " + role);
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebsocketEndpoints.java
new file mode 100644 (file)
index 0000000..f7cd693
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.Set;
+
+/** Configure web socket in Jetty without hard dependency. */
+public interface WebsocketEndpoints {
+       Set<Class<?>> getEndPoints();
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/package-info.java
new file mode 100644 (file)
index 0000000..9dfb766
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS websocket integration. */
+package org.argeo.cms.websocket.server;
\ No newline at end of file
diff --git a/org.argeo.cms.lib.jetty/.classpath b/org.argeo.cms.lib.jetty/.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.cms.lib.jetty/.project b/org.argeo.cms.lib.jetty/.project
new file mode 100644 (file)
index 0000000..132df7f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.jetty</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.jetty/bnd.bnd b/org.argeo.cms.lib.jetty/bnd.bnd
new file mode 100644 (file)
index 0000000..171059d
--- /dev/null
@@ -0,0 +1,5 @@
+Import-Package: \
+javax.servlet.http,\
+org.eclipse.jetty.server.handler,\
+org.eclipse.jetty.util.component,\
+*
\ No newline at end of file
diff --git a/org.argeo.cms.lib.jetty/build.properties b/org.argeo.cms.lib.jetty/build.properties
new file mode 100644 (file)
index 0000000..62f58d9
--- /dev/null
@@ -0,0 +1,8 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
+additional.bundles = org.eclipse.jetty.io,\
+                     org.eclipse.jetty.security,\
+                     org.argeo.ext.slf4j,\
+                     org.slf4j.api
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/CmsJettyServer.java
new file mode 100644 (file)
index 0000000..b0b348d
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.CompletableFuture;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+/** A {@link JettyHttpServer} which is compatible with Equinox servlets. */
+public class CmsJettyServer extends JettyHttpServer {
+       private static final String CONTEXT_TEMPDIR = "javax.servlet.context.tempdir";
+       // Equinox compatibility
+       private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
+       private Path tempDir;
+
+       private CompletableFuture<ServerContainer> serverContainer = new CompletableFuture<>();
+
+       protected void addServlets(ServletContextHandler servletContextHandler) throws ServletException {
+       }
+
+       @Override
+       public void start() {
+               try {
+                       tempDir = Files.createTempDirectory("jetty");
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot create temp dir", e);
+               }
+               super.start();
+       }
+
+       @Override
+       protected ServletContextHandler createRootContextHandler() {
+               ServletContextHandler servletContextHandler = new ServletContextHandler();
+               servletContextHandler.setAttribute(INTERNAL_CONTEXT_CLASSLOADER,
+                               Thread.currentThread().getContextClassLoader());
+               servletContextHandler.setClassLoader(this.getClass().getClassLoader());
+               servletContextHandler.setContextPath("/");
+
+               servletContextHandler.setAttribute(CONTEXT_TEMPDIR, tempDir.toAbsolutePath().toFile());
+               SessionHandler handler = new SessionHandler();
+               handler.setMaxInactiveInterval(-1);
+               servletContextHandler.setSessionHandler(handler);
+
+               JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                       @Override
+                       public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                       throws DeploymentException {
+                               CmsJettyServer.this.serverContainer.complete(serverContainer);
+                       }
+               });
+
+               return servletContextHandler;
+       }
+
+       @Override
+       protected ServerContainer getRootServerContainer() {
+               return serverContainer.join();
+       }
+
+       @Override
+       protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+               addServlets(servletContextHandler);
+       }
+
+       /*
+        * WEB SOCKET
+        */
+
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerAttributes.java
new file mode 100644 (file)
index 0000000..1e64fe0
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * A {@link Map} implementation wrapping the attributes of a Jetty
+ * {@link ContextHandler}.
+ */
+class ContextHandlerAttributes extends AbstractMap<String, Object> {
+       private ContextHandler contextHandler;
+
+       public ContextHandlerAttributes(ContextHandler contextHandler) {
+               super();
+               this.contextHandler = contextHandler;
+       }
+
+       @Override
+       public Set<Entry<String, Object>> entrySet() {
+               Set<Entry<String, Object>> entries = new HashSet<>();
+               for (Enumeration<String> keys = contextHandler.getAttributeNames(); keys.hasMoreElements();) {
+                       entries.add(new ContextAttributeEntry(keys.nextElement()));
+               }
+               return entries;
+       }
+
+       @Override
+       public Object put(String key, Object value) {
+               Object previousValue = get(key);
+               contextHandler.setAttribute(key, value);
+               return previousValue;
+       }
+
+       private class ContextAttributeEntry implements Map.Entry<String, Object> {
+               private final String key;
+
+               public ContextAttributeEntry(String key) {
+                       this.key = key;
+               }
+
+               @Override
+               public String getKey() {
+                       return key;
+               }
+
+               @Override
+               public Object getValue() {
+                       return contextHandler.getAttribute(key);
+               }
+
+               @Override
+               public Object setValue(Object value) {
+                       Object previousValue = getValue();
+                       contextHandler.setAttribute(key, value);
+                       return previousValue;
+               }
+
+       }
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ContextHandlerHttpContext.java
new file mode 100644 (file)
index 0000000..d6037ba
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.cms.jetty;
+
+import java.util.AbstractMap;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * An @{HttpContext} implementation based on a Jetty
+ * {@link ServletContextHandler}.
+ */
+class ContextHandlerHttpContext extends JettyHttpContext {
+       private final ServletContextHandler servletContextHandler;
+       private final ContextHandlerAttributes attributes;
+
+       public ContextHandlerHttpContext(JettyHttpServer httpServer, String path) {
+               super(httpServer, path);
+
+               // Jetty context handler
+               this.servletContextHandler = new ServletContextHandler();
+               servletContextHandler.setContextPath(path);
+               HttpContextServlet servlet = new HttpContextServlet(this);
+               servletContextHandler.addServlet(new ServletHolder(servlet), "/*");
+               SessionHandler sessionHandler = new SessionHandler();
+               // FIXME find a better default
+               sessionHandler.setMaxInactiveInterval(-1);
+               servletContextHandler.setSessionHandler(sessionHandler);
+
+               attributes = new ContextHandlerAttributes(servletContextHandler);
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               super.setHandler(handler);
+
+               // web socket
+               if (handler instanceof WebsocketEndpoints) {
+                       JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                               @Override
+                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                               throws DeploymentException {
+                                       for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+                                               serverContainer.addEndpoint(clss);
+                                       }
+                               }
+                       });
+               }
+
+               if (getJettyHttpServer().isStarted())
+                       try {
+                               servletContextHandler.start();
+                       } catch (Exception e) {
+                               throw new IllegalStateException("Cannot start context handler", e);
+                       }
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       protected ServletContextHandler getServletContextHandler() {
+               return servletContextHandler;
+       }
+
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpContext.java
new file mode 100644 (file)
index 0000000..551e54e
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.cms.jetty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.Filter;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/**
+ * An @{HttpContext} implementation based on Jetty. It supports web sockets if
+ * the handler implements {@link WebsocketEndpoints}.
+ */
+abstract class JettyHttpContext extends HttpContext {
+       private final JettyHttpServer httpServer;
+       private final String path;
+       private final List<Filter> filters = new ArrayList<>();
+
+       private HttpHandler handler;
+       private Authenticator authenticator;
+
+       public JettyHttpContext(JettyHttpServer httpServer, String path) {
+               this.httpServer = httpServer;
+               if (!path.endsWith("/"))
+                       throw new IllegalArgumentException("Path " + path + " should end with a /");
+               this.path = path;
+       }
+
+       protected abstract ServletContextHandler getServletContextHandler();
+
+       @Override
+       public HttpHandler getHandler() {
+               return handler;
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               if (this.handler != null)
+                       throw new IllegalArgumentException("Handler is already set");
+               Objects.requireNonNull(handler);
+               this.handler = handler;
+       }
+
+       @Override
+       public String getPath() {
+               return path;
+       }
+
+       @Override
+       public HttpServer getServer() {
+               return getJettyHttpServer();
+       }
+
+       protected JettyHttpServer getJettyHttpServer() {
+               return httpServer;
+       }
+
+       @Override
+       public List<Filter> getFilters() {
+               return filters;
+       }
+
+       @Override
+       public Authenticator setAuthenticator(Authenticator auth) {
+               Authenticator previousAuthenticator = authenticator;
+               this.authenticator = auth;
+               return previousAuthenticator;
+       }
+
+       @Override
+       public Authenticator getAuthenticator() {
+               return authenticator;
+       }
+
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/JettyHttpServer.java
new file mode 100644 (file)
index 0000000..363bbae
--- /dev/null
@@ -0,0 +1,358 @@
+package org.argeo.cms.jetty;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import javax.servlet.ServletException;
+import javax.websocket.server.ServerContainer;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.http.server.HttpServerUtils;
+import org.eclipse.jetty.http.UriCompliance;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.ExecutorThreadPool;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+
+/** An {@link HttpServer} implementation based on Jetty. */
+public class JettyHttpServer extends HttpsServer {
+       private final static CmsLog log = CmsLog.getLog(JettyHttpServer.class);
+
+       private static final int DEFAULT_IDLE_TIMEOUT = 30000;
+
+       private Server server;
+
+       protected ServerConnector httpConnector;
+       protected ServerConnector httpsConnector;
+
+       private InetSocketAddress httpAddress;
+       private InetSocketAddress httpsAddress;
+
+       private ThreadPoolExecutor executor;
+
+       private HttpsConfigurator httpsConfigurator;
+
+       private final Map<String, JettyHttpContext> contexts = new TreeMap<>();
+
+       private ServletContextHandler rootContextHandler;
+       protected final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
+
+       private boolean started;
+
+       private CmsState cmsState;
+
+       @Override
+       public void bind(InetSocketAddress addr, int backlog) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void start() {
+               try {
+
+                       ThreadPool threadPool = null;
+                       if (executor != null) {
+                               threadPool = new ExecutorThreadPool(executor);
+                       } else {
+                               // TODO make it configurable
+                               threadPool = new QueuedThreadPool(10, 1);
+                       }
+
+                       server = new Server(threadPool);
+
+                       configureConnectors();
+
+                       if (httpConnector != null) {
+                               httpConnector.open();
+                               server.addConnector(httpConnector);
+                       }
+
+                       if (httpsConnector != null) {
+                               httpsConnector.open();
+                               server.addConnector(httpsConnector);
+                       }
+
+                       // holder
+
+                       // context
+                       rootContextHandler = createRootContextHandler();
+                       // httpContext.addServlet(holder, "/*");
+                       if (rootContextHandler != null)
+                               configureRootContextHandler(rootContextHandler);
+
+                       if (rootContextHandler != null && !contexts.containsKey("/"))
+                               contextHandlerCollection.addHandler(rootContextHandler);
+
+                       server.setHandler(contextHandlerCollection);
+
+                       //
+                       // START
+                       server.start();
+                       //
+
+                       // Addresses
+                       String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+                       String fallBackHostname = cmsState != null ? cmsState.getHostname() : "::1";
+                       if (httpConnector != null) {
+                               httpAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
+                                               httpConnector.getLocalPort());
+                       } else if (httpsConnector != null) {
+                               httpsAddress = new InetSocketAddress(httpHost != null ? httpHost : fallBackHostname,
+                                               httpsConnector.getLocalPort());
+                       }
+                       // Clean up
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(), "Jetty shutdown"));
+
+                       log.info(httpPortsMsg());
+                       started = true;
+               } catch (Exception e) {
+                       stop();
+                       throw new IllegalStateException("Cannot start Jetty HTTP server", e);
+               }
+       }
+
+       protected void configureConnectors() {
+               String httpPortStr = getDeployProperty(CmsDeployProperty.HTTP_PORT);
+               String httpsPortStr = getDeployProperty(CmsDeployProperty.HTTPS_PORT);
+               if (httpPortStr != null && httpsPortStr != null)
+                       throw new IllegalArgumentException("Either an HTTP or an HTTPS port should be configured, not both");
+               if (httpPortStr == null && httpsPortStr == null)
+                       throw new IllegalArgumentException("Neither an HTTP or HTTPS port was configured");
+
+               /// TODO make it more generic
+               String httpHost = getDeployProperty(CmsDeployProperty.HOST);
+
+               // try {
+               if (httpPortStr != null || httpsPortStr != null) {
+                       // TODO deal with hostname resolving taking too much time
+//                     String fallBackHostname = InetAddress.getLocalHost().getHostName();
+
+                       boolean httpEnabled = httpPortStr != null;
+                       boolean httpsEnabled = httpsPortStr != null;
+
+                       if (httpEnabled) {
+                               HttpConfiguration httpConfiguration = new HttpConfiguration();
+
+                               if (httpsEnabled) {// not supported anymore to have both http and https, but it may change again
+                                       int httpsPort = Integer.parseInt(httpsPortStr);
+                                       httpConfiguration.setSecureScheme("https");
+                                       httpConfiguration.setSecurePort(httpsPort);
+                               }
+
+                               int httpPort = Integer.parseInt(httpPortStr);
+                               httpConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
+                               httpConnector.setPort(httpPort);
+                               httpConnector.setHost(httpHost);
+                               httpConnector.setIdleTimeout(DEFAULT_IDLE_TIMEOUT);
+
+                       }
+
+                       if (httpsEnabled) {
+                               SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+                               // sslContextFactory.setKeyStore(KeyS)
+
+                               sslContextFactory.setKeyStoreType(getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+                               sslContextFactory.setKeyStorePath(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+                               sslContextFactory.setKeyStorePassword(getDeployProperty(CmsDeployProperty.SSL_PASSWORD));
+                               // sslContextFactory.setKeyManagerPassword(getFrameworkProp(CmsDeployProperty.SSL_KEYPASSWORD));
+                               sslContextFactory.setProtocol("TLS");
+
+                               sslContextFactory.setTrustStoreType(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                               sslContextFactory.setTrustStorePath(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+                               sslContextFactory.setTrustStorePassword(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+                               String wantClientAuth = getDeployProperty(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null && wantClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setWantClientAuth(true);
+                               String needClientAuth = getDeployProperty(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null && needClientAuth.equals(Boolean.toString(true)))
+                                       sslContextFactory.setNeedClientAuth(true);
+
+                               // HTTPS Configuration
+                               HttpConfiguration httpsConfiguration = new HttpConfiguration();
+                               httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
+                               httpsConfiguration.setUriCompliance(UriCompliance.LEGACY);
+
+                               // HTTPS connector
+                               httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
+                                               new HttpConnectionFactory(httpsConfiguration));
+                               int httpsPort = Integer.parseInt(httpsPortStr);
+                               httpsConnector.setPort(httpsPort);
+                               httpsConnector.setHost(httpHost);
+                       }
+               }
+       }
+
+       @Override
+       public void stop(int delay) {
+               // TODO wait for processing to complete
+               stop();
+
+       }
+
+       public void stop() {
+               try {
+                       server.stop();
+                       // TODO delete temp dir
+                       started = false;
+               } catch (Exception e) {
+                       log.error("Cannot stop Jetty HTTP server", e);
+               }
+
+       }
+
+       @Override
+       public void setExecutor(Executor executor) {
+               if (!(executor instanceof ThreadPoolExecutor))
+                       throw new IllegalArgumentException("Only " + ThreadPoolExecutor.class.getName() + " are supported");
+               this.executor = (ThreadPoolExecutor) executor;
+       }
+
+       @Override
+       public Executor getExecutor() {
+               return executor;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path, HttpHandler handler) {
+               HttpContext httpContext = createContext(path);
+               httpContext.setHandler(handler);
+               return httpContext;
+       }
+
+       @Override
+       public synchronized HttpContext createContext(String path) {
+               if (!path.endsWith("/"))
+                       path = path + "/";
+               if (contexts.containsKey(path))
+                       throw new IllegalArgumentException("Context " + path + " already exists");
+
+               JettyHttpContext httpContext = new ServletHttpContext(this, path);
+               contexts.put(path, httpContext);
+
+               contextHandlerCollection.addHandler(httpContext.getServletContextHandler());
+               return httpContext;
+       }
+
+       @Override
+       public synchronized void removeContext(String path) throws IllegalArgumentException {
+               if (!contexts.containsKey(path))
+                       throw new IllegalArgumentException("Context " + path + " does not exist");
+               JettyHttpContext httpContext = contexts.remove(path);
+               if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
+                       // TODO stop handler first?
+                       contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+               }
+       }
+
+       @Override
+       public synchronized void removeContext(HttpContext context) {
+               removeContext(context.getPath());
+       }
+
+       @Override
+       public InetSocketAddress getAddress() {
+               InetSocketAddress res = httpAddress != null ? httpAddress : httpsAddress;
+               if (res == null)
+                       throw new IllegalStateException("Neither an HTTP nor and HTTPS address is available");
+               return res;
+       }
+
+       @Override
+       public void setHttpsConfigurator(HttpsConfigurator config) {
+               this.httpsConfigurator = config;
+       }
+
+       @Override
+       public HttpsConfigurator getHttpsConfigurator() {
+               return httpsConfigurator;
+       }
+
+       protected String getDeployProperty(CmsDeployProperty deployProperty) {
+               return cmsState != null ? cmsState.getDeployProperty(deployProperty.getProperty())
+                               : System.getProperty(deployProperty.getProperty());
+       }
+
+       private String httpPortsMsg() {
+
+               return (httpConnector != null ? "HTTP " + getHttpPort() + " " : "")
+                               + (httpsConnector != null ? "HTTPS " + getHttpsPort() : "");
+       }
+
+       public Integer getHttpPort() {
+               if (httpConnector == null)
+                       return null;
+               return httpConnector.getLocalPort();
+       }
+
+       public Integer getHttpsPort() {
+               if (httpsConnector == null)
+                       return null;
+               return httpsConnector.getLocalPort();
+       }
+
+       protected ServletContextHandler createRootContextHandler() {
+               return null;
+       }
+
+       protected void configureRootContextHandler(ServletContextHandler servletContextHandler) throws ServletException {
+
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+       boolean isStarted() {
+               return started;
+       }
+
+       ServletContextHandler getRootContextHandler() {
+               return rootContextHandler;
+       }
+
+       ServerContainer getRootServerContainer() {
+               throw new UnsupportedOperationException();
+       }
+
+       public static void main(String... args) {
+               JettyHttpServer httpServer = new JettyHttpServer();
+               System.setProperty("argeo.http.port", "8080");
+               httpServer.createContext("/", (exchange) -> {
+                       exchange.getResponseBody().write("Hello World!".getBytes());
+               });
+               httpServer.start();
+               httpServer.createContext("/sub/context", (exchange) -> {
+                       final String key = "count";
+                       Integer count = (Integer) exchange.getHttpContext().getAttributes().get(key);
+                       if (count == null)
+                               exchange.getHttpContext().getAttributes().put(key, 0);
+                       else
+                               exchange.getHttpContext().getAttributes().put(key, count + 1);
+                       StringBuilder sb = new StringBuilder();
+                       sb.append("Subcontext:");
+                       sb.append(" " + key + "=" + exchange.getHttpContext().getAttributes().get(key));
+                       sb.append(" relativePath=" + HttpServerUtils.relativize(exchange));
+                       exchange.getResponseBody().write(sb.toString().getBytes());
+               });
+       }
+}
diff --git a/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java b/org.argeo.cms.lib.jetty/src/org/argeo/cms/jetty/ServletHttpContext.java
new file mode 100644 (file)
index 0000000..3361194
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.jetty;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.servlet.httpserver.HttpContextServlet;
+import org.argeo.cms.websocket.server.WebsocketEndpoints;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * A {@link JettyHttpContext} based on registering a servlet to the root handler
+ * of the {@link JettyHttpServer}, in order to integrate the sessions.
+ */
+public class ServletHttpContext extends JettyHttpContext {
+       private final static CmsLog log = CmsLog.getLog(ServletHttpContext.class);
+
+       private Map<String, Object> attributes = Collections.synchronizedMap(new HashMap<>());
+
+       public ServletHttpContext(JettyHttpServer httpServer, String path) {
+               super(httpServer, path);
+
+               ServletContextHandler rootContextHandler = httpServer.getRootContextHandler();
+               HttpContextServlet servlet = new HttpContextServlet(this);
+               rootContextHandler.addServlet(new ServletHolder(servlet), path + "*");
+       }
+
+       @Override
+       public void setHandler(HttpHandler handler) {
+               super.setHandler(handler);
+
+               // web socket
+               if (handler instanceof WebsocketEndpoints) {
+                       ServerContainer serverContainer = getJettyHttpServer().getRootServerContainer();
+                       for (Class<?> clss : ((WebsocketEndpoints) handler).getEndPoints()) {
+                               try {
+                                       serverContainer.addEndpoint(clss);
+                                       log.debug(() -> "Added web socket " + clss + " to " + getPath());
+                               } catch (DeploymentException e) {
+                                       log.error("Cannot deploy Web Socket " + clss, e);
+                               }
+                       }
+               }
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return attributes;
+       }
+
+       @Override
+       protected ServletContextHandler getServletContextHandler() {
+               return getJettyHttpServer().getRootContextHandler();
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/.classpath b/org.argeo.cms.lib.sshd/.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.cms.lib.sshd/.gitignore b/org.argeo.cms.lib.sshd/.gitignore
new file mode 100644 (file)
index 0000000..7fb0c18
--- /dev/null
@@ -0,0 +1,3 @@
+/hostkey.ser
+/id_rsa
+/id_rsa.pub
diff --git a/org.argeo.cms.lib.sshd/.project b/org.argeo.cms.lib.sshd/.project
new file mode 100644 (file)
index 0000000..588b829
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.sshd</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml b/org.argeo.cms.lib.sshd/OSGI-INF/cmsSshServer.xml
new file mode 100644 (file)
index 0000000..987b977
--- /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" activate="start" deactivate="stop" name="CMS SSH Server" immediate="true">
+   <implementation class="org.argeo.cms.ssh.CmsSshServer"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <service>
+      <provide interface="org.argeo.cms.CmsSshd"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms.lib.sshd/bnd.bnd b/org.argeo.cms.lib.sshd/bnd.bnd
new file mode 100644 (file)
index 0000000..85546f6
--- /dev/null
@@ -0,0 +1,10 @@
+Import-Package: \
+org.apache.sshd.server.forward,\
+org.apache.sshd.common.forward,\
+org.apache.sshd.common.channel,\
+org.apache.sshd.common.helpers,\
+org.apache.sshd.common.file.util,\
+*
+
+Service-Component: \
+OSGI-INF/cmsSshServer.xml
diff --git a/org.argeo.cms.lib.sshd/build.properties b/org.argeo.cms.lib.sshd/build.properties
new file mode 100644 (file)
index 0000000..a04ec77
--- /dev/null
@@ -0,0 +1,9 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
+additional.bundles = org.slf4j.api,\
+                     org.argeo.ext.slf4j,\
+                     org.apache.tomcat.jni
+                     
\ No newline at end of file
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java
new file mode 100644 (file)
index 0000000..d2fc89f
--- /dev/null
@@ -0,0 +1,168 @@
+package org.argeo.cms.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.cms.CmsLog;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCSException;
+
+/** Utilities around the BouncyCastle crypto library. */
+public class BcUtils {
+       private final static CmsLog log = CmsLog.getLog(BcUtils.class);
+
+       private final static String BC_SECURITY_PROVIDER;
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+               BC_SECURITY_PROVIDER = "BC";
+       }
+
+       public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
+               // for (Provider provider : Security.getProviders())
+               // System.out.println(provider.getName());
+               // File keyStoreFile = keyStorePath.toFile();
+               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+               if (!Files.exists(keyStorePath)) {
+                       try {
+                               Files.createDirectories(keyStorePath.getParent());
+                               KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
+                               generateSelfSignedCertificate(keyStore,
+                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
+                                               1024, keyPwd);
+                               saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
+                       } catch (Exception e) {
+                               try {
+                                       if (Files.size(keyStorePath) == 0)
+                                               Files.delete(keyStorePath);
+                               } catch (IOException e1) {
+                                       // silent
+                               }
+                               log.error("Cannot create keystore " + keyStorePath, e);
+                       }
+               } else {
+                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
+               }
+       }
+
+       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
+                       int keySize, char[] keyPassword) {
+               try {
+                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC_SECURITY_PROVIDER);
+                       kpGen.initialize(keySize, new SecureRandom());
+                       KeyPair pair = kpGen.generateKeyPair();
+                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
+                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
+                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
+                                       notAfter, x500Principal, pair.getPublic());
+                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+                                       .setProvider(BC_SECURITY_PROVIDER).build(pair.getPrivate());
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
+                                       .getCertificate(certGen.build(sigGen));
+                       cert.checkValidity(new Date());
+                       cert.verify(cert.getPublicKey());
+
+                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
+                       return cert;
+               } catch (GeneralSecurityException | OperatorCreationException e) {
+                       throw new RuntimeException("Cannot generate self-signed certificate", e);
+               }
+       }
+
+       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_SECURITY_PROVIDER);
+                       Object object = pemParser.readObject();
+                       PrivateKeyInfo privateKeyInfo;
+                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               if (keyPassword == null)
+                                       throw new IllegalArgumentException("A key password is required");
+                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
+                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
+                       } else if (object instanceof PrivateKeyInfo) {
+                               privateKeyInfo = (PrivateKeyInfo) object;
+                       } else {
+                               throw new IllegalArgumentException("Unsupported format for private key");
+                       }
+                       return converter.getPrivateKey(privateKeyInfo);
+               } catch (IOException | OperatorCreationException | PKCSException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static X509Certificate loadPemCertificate(Reader reader) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
+                                       .getCertificate(certHolder);
+                       return cert;
+               } catch (IOException | CertificateException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       private static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+               try {
+                       KeyStore store = KeyStore.getInstance(keyStoreType, BC_SECURITY_PROVIDER);
+                       if (Files.exists(keyStoreFile)) {
+                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
+                                       store.load(fis, keyStorePassword);
+                               }
+                       } else {
+                               store.load(null);
+                       }
+                       return store;
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
+               }
+       }
+
+       private static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+               try {
+                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
+                               keyStore.store(fis, keyStorePassword);
+                       }
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
+               }
+       }
+
+       /** singleton */
+       private BcUtils() {
+       }
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/AbstractSsh.java
new file mode 100644 (file)
index 0000000..c91ab48
--- /dev/null
@@ -0,0 +1,210 @@
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.io.input.NoCloseInputStream;
+import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+
+public abstract class AbstractSsh {
+       private final static CmsLog log = CmsLog.getLog(AbstractSsh.class);
+
+       private SshClient sshClient;
+       private SftpFileSystemProvider sftpFileSystemProvider;
+
+       private boolean passwordSet = false;
+       private ClientSession session;
+
+       private SshKeyPair sshKeyPair;
+
+       public synchronized SshClient getSshClient() {
+               if (sshClient == null) {
+                       long begin = System.currentTimeMillis();
+                       sshClient = SshClient.setUpDefaultClient();
+                       sshClient.start();
+                       long duration = System.currentTimeMillis() - begin;
+                       if (log.isDebugEnabled())
+                               log.debug("SSH client started in " + duration + " ms");
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> sshClient.stop(), "Stop SSH client"));
+               }
+               return sshClient;
+       }
+
+       synchronized SftpFileSystemProvider getSftpFileSystemProvider() {
+               if (sftpFileSystemProvider == null) {
+                       sftpFileSystemProvider = new SftpFileSystemProvider(sshClient);
+               }
+               return sftpFileSystemProvider;
+       }
+
+       public void authenticate() {
+               if (sshKeyPair != null) {
+                       session.addPublicKeyIdentity(sshKeyPair.asKeyPair());
+               } else {
+
+                       if (!passwordSet) {
+                               String password;
+                               Console console = System.console();
+                               if (console == null) {// IDE
+                                       System.out.print("Password: ");
+                                       try (Scanner s = new Scanner(System.in)) {
+                                               password = s.next();
+                                       }
+                               } else {
+                                       console.printf("Password: ");
+                                       char[] pwd = console.readPassword();
+                                       password = new String(pwd);
+                                       Arrays.fill(pwd, ' ');
+                               }
+                               session.addPasswordIdentity(password);
+                               passwordSet = true;
+                       }
+               }
+               verifyAuth();
+       }
+
+       public void verifyAuth() {
+               try {
+                       session.auth().verify(1000l);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot verify auth", e);
+               }
+       }
+
+       public static char[] readPassword() {
+               Console console = System.console();
+               if (console == null) {// IDE
+                       System.out.print("Password: ");
+                       try (Scanner s = new Scanner(System.in)) {
+                               String password = s.next();
+                               return password.toCharArray();
+                       }
+               } else {
+                       console.printf("Password: ");
+                       char[] pwd = console.readPassword();
+                       return pwd;
+               }
+       }
+
+       void addPassword(String password) {
+               session.addPasswordIdentity(password);
+       }
+
+       void loadKey(String password) {
+               loadKey(password, System.getProperty("user.home") + "/.ssh/id_rsa");
+       }
+
+       void loadKey(String password, String keyPath) {
+//             try {
+//                     KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
+//                                     FilePasswordProvider.of(password));
+//                     session.addPublicKeyIdentity(keyPair);
+//             } catch (IOException | GeneralSecurityException e) {
+//                     throw new IllegalStateException(e);
+//             }
+       }
+
+       void openSession(URI uri) {
+               openSession(uri.getUserInfo(), uri.getHost(), uri.getPort() > 0 ? uri.getPort() : null);
+       }
+
+       void openSession(String login, String host, Integer port) {
+               if (session != null)
+                       throw new IllegalStateException("Session is already open");
+
+               if (host == null)
+                       host = "localhost";
+               if (port == null)
+                       port = 22;
+               if (login == null)
+                       login = System.getProperty("user.name");
+               String password = null;
+               int sepIndex = login.indexOf(':');
+               if (sepIndex > 0)
+                       if (sepIndex + 1 < login.length()) {
+                               password = login.substring(sepIndex + 1);
+                               login = login.substring(0, sepIndex);
+                       } else {
+                               throw new IllegalArgumentException("Illegal authority: " + login);
+                       }
+               try {
+                       ConnectFuture connectFuture = getSshClient().connect(login, host, port);
+                       connectFuture.await();
+                       ClientSession session = connectFuture.getSession();
+                       if (password != null) {
+                               session.addPasswordIdentity(password);
+                               passwordSet = true;
+                       }
+                       this.session = session;
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot connect to " + host + ":" + port);
+               }
+       }
+
+       public void closeSession() {
+               if (session == null)
+                       throw new IllegalStateException("No session is open");
+               try {
+                       session.close();
+               } catch (IOException e) {
+                       e.printStackTrace();
+               } finally {
+                       session = null;
+               }
+       }
+
+       ClientSession getSession() {
+               return session;
+       }
+
+       public void setSshKeyPair(SshKeyPair sshKeyPair) {
+               this.sshKeyPair = sshKeyPair;
+       }
+
+       public static void openShell(AbstractSsh ssh) {
+               openShell(ssh.getSession());
+       }
+
+       public static void openShell(ClientSession session) {
+               try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
+                       channel.setIn(new NoCloseInputStream(System.in));
+                       channel.setOut(new NoCloseOutputStream(System.out));
+                       channel.setErr(new NoCloseOutputStream(System.err));
+                       channel.open();
+
+                       Set<ClientChannelEvent> events = new HashSet<>();
+                       events.add(ClientChannelEvent.CLOSED);
+                       channel.waitFor(events, 0);
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+                       session.close(false);
+               }
+       }
+
+       static URI toUri(String username, String host, int port) {
+               try {
+                       if (username == null)
+                               username = "root";
+                       return new URI("ssh://" + username + "@" + host + ":" + port);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot generate SSH URI to " + host + ":" + port + " for " + username,
+                                       e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/BasicSshServer.java
new file mode 100644 (file)
index 0000000..cde6c93
--- /dev/null
@@ -0,0 +1,107 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.ProcessShellFactory;
+import org.argeo.cms.util.OS;
+
+/** A simple SSH server with some defaults. Supports SCP. */
+public class BasicSshServer {
+       private Integer port;
+       private Path hostKeyPath;
+
+       private SshServer sshd = null;
+
+       public BasicSshServer(Integer port, Path hostKeyPath) {
+               this.port = port;
+               this.hostKeyPath = hostKeyPath;
+       }
+
+       public void init() {
+               try {
+                       sshd = SshServer.setUpDefaultServer();
+                       sshd.setPort(port);
+                       if (hostKeyPath == null)
+                               throw new IllegalStateException("An SSH server key must be set");
+                       sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+                       // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+                       // "-l" }));
+                       String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+                       // FIXME transfer args
+//                     sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+                       sshd.setShellFactory(new ProcessShellFactory(shellCommand[0], shellCommand));
+                       sshd.setCommandFactory(new ScpCommandFactory());
+
+                       sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+                       sshd.start();
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
+               }
+       }
+
+       public void destroy() {
+               try {
+                       sshd.stop();
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot stop SSH server on port " + port, e);
+               }
+       }
+
+       public Integer getPort() {
+               return port;
+       }
+
+       public void setPort(Integer port) {
+               this.port = port;
+       }
+
+       public Path getHostKeyPath() {
+               return hostKeyPath;
+       }
+
+       public void setHostKeyPath(Path hostKeyPath) {
+               this.hostKeyPath = hostKeyPath;
+       }
+
+       public static void main(String[] args) {
+               int port = 2222;
+               Path hostKeyPath = Paths.get("hostkey.ser");
+               try {
+                       if (args.length > 0)
+                               port = Integer.parseInt(args[0]);
+                       if (args.length > 1)
+                               hostKeyPath = Paths.get(args[1]);
+               } catch (Exception e1) {
+                       printUsage();
+               }
+
+               BasicSshServer sshServer = new BasicSshServer(port, hostKeyPath);
+               sshServer.init();
+               Runtime.getRuntime().addShutdownHook(new Thread("Shutdown SSH server") {
+
+                       @Override
+                       public void run() {
+                               sshServer.destroy();
+                       }
+               });
+               try {
+                       synchronized (sshServer) {
+                               sshServer.wait();
+                       }
+               } catch (InterruptedException e) {
+                       sshServer.destroy();
+               }
+
+       }
+
+       public static void printUsage() {
+               System.out.println("java " + BasicSshServer.class.getName() + " [port] [server key path]");
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/CmsSshServer.java
new file mode 100644 (file)
index 0000000..f5609a3
--- /dev/null
@@ -0,0 +1,218 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
+
+public class CmsSshServer implements CmsSshd {
+       private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
+
+       private CmsState cmsState;
+       private SshServer sshd = null;
+
+       private int port;
+       private String host;
+
+       public void start() {
+               String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+               if (portStr == null)
+                       return; // ignore
+               port = Integer.parseInt(portStr);
+
+               host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
+
+               KeyPair nodeKeyPair = loadNodeKeyPair();
+
+               try {
+                       // authorized keys
+                       String authorizedKeysStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_AUTHORIZEDKEYS.getProperty());
+                       Path authorizedKeysPath = authorizedKeysStr != null ? Paths.get(authorizedKeysStr)
+                                       : AuthorizedKeysAuthenticator.getDefaultAuthorizedKeysFile();
+                       if (authorizedKeysStr != null && !Files.exists(authorizedKeysPath)) {
+                               Files.createFile(authorizedKeysPath);
+                               Set<PosixFilePermission> posixPermissions = new HashSet<>();
+                               posixPermissions.add(PosixFilePermission.OWNER_READ);
+                               posixPermissions.add(PosixFilePermission.OWNER_WRITE);
+                               Files.setPosixFilePermissions(authorizedKeysPath, posixPermissions);
+
+                               if (nodeKeyPair != null)
+                                       try {
+                                               String openSsshPublicKey = PublicKeyEntry.toString(nodeKeyPair.getPublic());
+                                               try (Writer writer = Files.newBufferedWriter(authorizedKeysPath, StandardCharsets.US_ASCII,
+                                                               StandardOpenOption.APPEND)) {
+                                                       writer.write(openSsshPublicKey);
+                                               }
+                                       } catch (IOException e) {
+                                               log.error("Cannot add node public key to SSH authorized keys", e);
+                                       }
+                       }
+
+                       // create server
+                       sshd = SshServer.setUpDefaultServer();
+                       sshd.setPort(port);
+                       if (host != null)
+                               sshd.setHost(host);
+
+                       // host key
+                       if (nodeKeyPair != null) {
+                               sshd.setKeyPairProvider(KeyPairProvider.wrap(nodeKeyPair));
+                       } else {
+                               Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
+                               if (hostKeyPath == null) // TODO deal with no data area?
+                                       throw new IllegalStateException("An SSH server key must be set");
+                               sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+                       }
+
+                       // tunnels
+                       sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+                       sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
+
+                               @Override
+                               public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding) throws IOException {
+                                       log.debug("Establishing tunnel " + local + ", " + remote);
+                               }
+
+                               @Override
+                               public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
+                                               Throwable reason) throws IOException {
+                                       log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
+                               }
+
+                               @Override
+                               public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
+                                       log.debug("Establishing dynamic tunnel " + local);
+                               }
+
+                               @Override
+                               public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
+                                               SshdSocketAddress boundAddress, Throwable reason) throws IOException {
+                                       log.debug("Established dynamic tunnel " + local);
+                               }
+
+                       });
+
+                       // Authentication
+                       // FIXME use strict, set proper permissions, etc.
+                       sshd.setPublickeyAuthenticator(
+                                       new DefaultAuthorizedKeysAuthenticator("user.name", authorizedKeysPath, true));
+                       // sshd.setPublickeyAuthenticator(null);
+                       // sshd.setKeyboardInteractiveAuthenticator(null);
+                       JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+                       jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
+                       sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
+
+                       boolean gssApi = false;
+                       if (gssApi) {
+                               Path krb5keyTab = cmsState.getDataPath("private/krb5.keytab");
+                               if (Files.exists(krb5keyTab)) {
+                                       // FIXME experimental
+                                       GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
+                                       gssAuthenticator.setKeytabFile(krb5keyTab.toString());
+                                       gssAuthenticator.setServicePrincipalName("HTTP@" + host);
+                                       sshd.setGSSAuthenticator(gssAuthenticator);
+                               }
+                       }
+
+                       // shell
+                       // TODO make it configurable
+                       sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+//                     String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+//                     StringJoiner command = new StringJoiner(" ");
+//                     for (String str : shellCommand) {
+//                             command.add(str);
+//                     }
+//                     sshd.setShellFactory(new ProcessShellFactory(command.toString(), shellCommand));
+                       sshd.setCommandFactory(new ScpCommandFactory());
+
+                       // SFTP
+                       sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+
+                       // start
+                       sshd.start();
+
+                       log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot start SSH server on port " + port, e);
+               }
+
+       }
+
+       public void stop() {
+               if (sshd == null)
+                       return;
+               try {
+                       sshd.stop();
+                       log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot stop SSH server", e);
+               }
+
+       }
+
+       protected KeyPair loadNodeKeyPair() {
+               try {
+                       char[] keyStorePassword = cmsState.getDeployProperty(CmsDeployProperty.SSL_PASSWORD.getProperty())
+                                       .toCharArray();
+                       Path keyStorePath = Paths.get(cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORE.getProperty()));
+                       String keyStoreType = cmsState.getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE.getProperty());
+
+                       KeyStore store = KeyStore.getInstance(keyStoreType, "SunJSSE");
+                       try (InputStream fis = Files.newInputStream(keyStorePath)) {
+                               store.load(fis, keyStorePassword);
+                       }
+                       return new KeyPair(store.getCertificate(CmsConstants.NODE).getPublicKey(),
+                                       (PrivateKey) store.getKey(CmsConstants.NODE, keyStorePassword));
+               } catch (IOException | KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException
+                               | CertificateException | IllegalArgumentException | UnrecoverableKeyException e) {
+                       log.error("Cannot add node public key to SSH authorized keys", e);
+                       return null;
+               }
+
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Sftp.java
new file mode 100644 (file)
index 0000000..f6f4474
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+
+/** Create an SFTP {@link FileSystem}. */
+public class Sftp extends AbstractSsh {
+       private URI uri;
+
+       private SftpFileSystem fileSystem;
+
+       public Sftp(String username, String host, int port) {
+               this(AbstractSsh.toUri(username, host, port));
+       }
+
+       public Sftp(URI uri) {
+               this.uri = uri;
+               openSession(uri);
+       }
+
+       public FileSystem getFileSystem() {
+               if (fileSystem == null) {
+                       try {
+                               authenticate();
+                               fileSystem = getSftpFileSystemProvider().newFileSystem(getSession());
+                       } catch (IOException e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+               return fileSystem;
+       }
+
+       public Path getBasePath() {
+               String p = uri.getPath() != null ? uri.getPath() : "/";
+               return getFileSystem().getPath(p);
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/Ssh.java
new file mode 100644 (file)
index 0000000..6e7b6ef
--- /dev/null
@@ -0,0 +1,81 @@
+package org.argeo.cms.ssh;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** Create an SSH shell. */
+public class Ssh extends AbstractSsh {
+       private final URI uri;
+
+       public Ssh(String username, String host, int port) {
+               this(AbstractSsh.toUri(username, host, port));
+       }
+
+       public Ssh(URI uri) {
+               this.uri = uri;
+               openSession(uri);
+       }
+
+       public static void main(String[] args) {
+               Options options = getOptions();
+               CommandLineParser parser = new DefaultParser();
+               try {
+                       CommandLine line = parser.parse(options, args);
+                       List<String> remaining = line.getArgList();
+                       if (remaining.size() == 0) {
+                               System.err.println("There must be at least one argument");
+                               printHelp(options);
+                               System.exit(1);
+                       }
+                       URI uri = new URI("ssh://" + remaining.get(0));
+                       List<String> command = new ArrayList<>();
+                       if (remaining.size() > 1) {
+                               for (int i = 1; i < remaining.size(); i++) {
+                                       command.add(remaining.get(i));
+                               }
+                       }
+
+                       // auth
+                       Ssh ssh = new Ssh(uri);
+                       ssh.authenticate();
+
+                       if (command.size() == 0) {// shell
+                               AbstractSsh.openShell(ssh.getSession());
+                       } else {// execute command
+
+                       }
+                       ssh.closeSession();
+               } catch (Exception exp) {
+                       exp.printStackTrace();
+                       printHelp(options);
+                       System.exit(1);
+               } finally {
+
+               }
+       }
+
+       public URI getUri() {
+               return uri;
+       }
+
+       public static Options getOptions() {
+               Options options = new Options();
+//             options.addOption("p", true, "port");
+               options.addOption(Option.builder("p").hasArg().argName("port").desc("port of the SSH server").build());
+
+               return options;
+       }
+
+       public static void printHelp(Options options) {
+               HelpFormatter formatter = new HelpFormatter();
+               formatter.printHelp("ssh [username@]hostname", options, true);
+       }
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshKeyPair.java
new file mode 100644 (file)
index 0000000..f5cbb04
--- /dev/null
@@ -0,0 +1,205 @@
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.RSAPublicKeySpec;
+
+import org.apache.sshd.common.config.keys.KeyUtils;
+import org.apache.sshd.common.config.keys.PublicKeyEntry;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PKCS8Generator;
+import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+
+@SuppressWarnings("restriction")
+public class SshKeyPair {
+       public final static String RSA_KEY_TYPE = "ssh-rsa";
+
+       private PublicKey publicKey;
+       private PrivateKey privateKey;
+       private KeyPair keyPair;
+
+       public SshKeyPair(KeyPair keyPair) {
+               super();
+               this.publicKey = keyPair.getPublic();
+               this.privateKey = keyPair.getPrivate();
+               this.keyPair = keyPair;
+       }
+
+       public SshKeyPair(PublicKey publicKey, PrivateKey privateKey) {
+               super();
+               this.publicKey = publicKey;
+               this.privateKey = privateKey;
+               this.keyPair = new KeyPair(publicKey, privateKey);
+       }
+
+       public KeyPair asKeyPair() {
+               return keyPair;
+       }
+
+       public String getPublicKeyAsOpenSshString() {
+               return PublicKeyEntry.toString(publicKey);
+       }
+
+       public String getPrivateKeyAsPemString(char[] password) {
+               try {
+                       Object obj;
+
+                       if (password != null) {
+                               JceOpenSSLPKCS8EncryptorBuilder encryptorBuilder = new JceOpenSSLPKCS8EncryptorBuilder(
+                                               PKCS8Generator.PBE_SHA1_3DES);
+                               encryptorBuilder.setPasssword(password);
+                               OutputEncryptor oe = encryptorBuilder.build();
+                               JcaPKCS8Generator gen = new JcaPKCS8Generator(privateKey, oe);
+                               obj = gen.generate();
+                       } else {
+                               obj = privateKey;
+                       }
+
+                       StringWriter sw = new StringWriter();
+                       JcaPEMWriter pemWrt = new JcaPEMWriter(sw);
+                       pemWrt.writeObject(obj);
+                       pemWrt.close();
+                       return sw.toString();
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot convert private key", e);
+               }
+       }
+
+       public static SshKeyPair loadOrGenerate(Path privateKeyPath, int size, char[] password) {
+               try {
+                       SshKeyPair sshKeyPair;
+                       if (Files.exists(privateKeyPath)) {
+//                             String privateKeyStr = new String(Files.readAllBytes(privateKeyPath), StandardCharsets.US_ASCII);
+                               sshKeyPair = load(
+                                               new InputStreamReader(Files.newInputStream(privateKeyPath), StandardCharsets.US_ASCII),
+                                               password);
+                               // TOD make sure public key is consistemt
+                       } else {
+                               sshKeyPair = generate(size);
+                               Files.write(privateKeyPath,
+                                               sshKeyPair.getPrivateKeyAsPemString(password).getBytes(StandardCharsets.US_ASCII));
+                               Path publicKeyPath = privateKeyPath.resolveSibling(privateKeyPath.getFileName() + ".pub");
+                               Files.write(publicKeyPath,
+                                               sshKeyPair.getPublicKeyAsOpenSshString().getBytes(StandardCharsets.US_ASCII));
+                       }
+                       return sshKeyPair;
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot read or write private key " + privateKeyPath, e);
+               }
+       }
+
+       public static SshKeyPair generate(int size) {
+               return generate(RSA_KEY_TYPE, size);
+       }
+
+       public static SshKeyPair generate(String keyType, int size) {
+               try {
+                       KeyPair keyPair = KeyUtils.generateKeyPair(keyType, size);
+                       PublicKey publicKey = keyPair.getPublic();
+                       PrivateKey privateKey = keyPair.getPrivate();
+                       return new SshKeyPair(publicKey, privateKey);
+               } catch (GeneralSecurityException e) {
+                       throw new RuntimeException("Cannot generate SSH key", e);
+               }
+       }
+
+       public static SshKeyPair loadDefault(char[] password) {
+               Path privateKeyPath = Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa");
+               // TODO try other formats
+               return load(privateKeyPath, password);
+       }
+
+       public static SshKeyPair load(Path privateKeyPath, char[] password) {
+               try (Reader reader = Files.newBufferedReader(privateKeyPath)) {
+                       return load(reader, password);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot load private key from " + privateKeyPath, e);
+               }
+
+       }
+
+       public static SshKeyPair load(Reader reader, char[] password) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       Object object = pemParser.readObject();
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter();// .setProvider("BC");
+                       KeyPair kp;
+                       if (object instanceof PEMEncryptedKeyPair) {
+                               PEMEncryptedKeyPair ekp = (PEMEncryptedKeyPair) object;
+                               PEMDecryptorProvider decryptorProvider = new BcPEMDecryptorProvider(password);
+                               PEMKeyPair pemKp = ekp.decryptKeyPair(decryptorProvider);
+                               kp = converter.getKeyPair(pemKp);
+                       } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               // Encrypted key - we will use provided password
+                               PKCS8EncryptedPrivateKeyInfo ckp = (PKCS8EncryptedPrivateKeyInfo) object;
+//                             PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password);
+                               InputDecryptorProvider inputDecryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder()
+                                               .build(password);
+                               PrivateKeyInfo pkInfo = ckp.decryptPrivateKeyInfo(inputDecryptorProvider);
+                               PrivateKey privateKey = converter.getPrivateKey(pkInfo);
+
+                               // generate public key
+                               RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey;
+                               RSAPublicKeySpec publicKeySpec = new java.security.spec.RSAPublicKeySpec(privk.getModulus(),
+                                               privk.getPublicExponent());
+                               KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+                               PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
+
+                               kp = new KeyPair(publicKey, privateKey);
+                       } else {
+                               // Unencrypted key - no password needed
+//                             PKCS8EncryptedPrivateKeyInfo ukp = (PKCS8EncryptedPrivateKeyInfo) object;
+                               PEMKeyPair pemKp = (PEMKeyPair) object;
+                               kp = converter.getKeyPair(pemKp);
+                       }
+                       return new SshKeyPair(kp);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot load private key", e);
+               }
+       }
+
+       public static void main(String args[]) {
+               Path privateKeyPath = Paths.get(System.getProperty("user.dir") + "/id_rsa");
+               SshKeyPair skp = SshKeyPair.loadOrGenerate(privateKeyPath, 1024, null);
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+               StringReader reader = new StringReader(skp.getPrivateKeyAsPemString(null));
+               skp = SshKeyPair.load(reader, null);
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+
+               reader = new StringReader(skp.getPrivateKeyAsPemString("demo".toCharArray()));
+               skp = SshKeyPair.load(reader, "demo".toCharArray());
+               System.out.println("Public:\n" + skp.getPublicKeyAsOpenSshString());
+               System.out.println("Private (plain):\n" + skp.getPrivateKeyAsPemString(null));
+               System.out.println("Private (encrypted):\n" + skp.getPrivateKeyAsPemString("demo".toCharArray()));
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/SshSync.java
new file mode 100644 (file)
index 0000000..31e6341
--- /dev/null
@@ -0,0 +1,154 @@
+package org.argeo.cms.ssh;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.sftp.client.fs.SftpFileSystem;
+import org.apache.sshd.sftp.client.fs.SftpFileSystemProvider;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.util.StreamUtils;
+
+public class SshSync {
+       private final static CmsLog log = CmsLog.getLog(SshSync.class);
+
+       public static void main(String[] args) {
+
+               try (SshClient client = SshClient.setUpDefaultClient()) {
+                       client.start();
+                       boolean osAgent = false;
+                       SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
+                       // SshAgentFactory agentFactory = new LocalAgentFactory();
+                       client.setAgentFactory(agentFactory);
+                       SshAgent sshAgent = agentFactory.createClient(null, client);
+
+                       String login = System.getProperty("user.name");
+                       String host = "localhost";
+                       int port = 22;
+
+                       if (!osAgent) {
+                               String keyPath = "/home/" + login + "/.ssh/id_rsa";
+
+                               String password;
+                               Console console = System.console();
+                               if (console != null) {
+                                       password = new String(console.readPassword(keyPath + ": "));
+                               } else {
+                                       System.out.print(keyPath + ": ");
+                                       try (Scanner s = new Scanner(System.in)) {
+                                               password = s.next();
+                                       }
+                               }
+                               NamedResource namedResource = new NamedResource() {
+
+                                       @Override
+                                       public String getName() {
+                                               return keyPath;
+                                       }
+                               };
+                               KeyPair keyPair = ClientIdentityLoader.DEFAULT
+                                               .loadClientIdentities(null, namedResource, FilePasswordProvider.of(password)).iterator().next();
+                               sshAgent.addIdentity(keyPair, "NO COMMENT");
+                       }
+
+//                     List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
+//                     for (Map.Entry<PublicKey, String> entry : identities) {
+//                             System.out.println(entry.getValue() + " : " + entry.getKey());
+//                     }
+
+                       ConnectFuture connectFuture = client.connect(login, host, port);
+                       connectFuture.await();
+                       ClientSession session = connectFuture.getSession();
+
+                       try {
+
+//                             session.addPasswordIdentity(new String(password));
+                               session.auth().verify(1000l);
+
+                               SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
+
+                               SftpFileSystem fs = fsProvider.newFileSystem(session);
+                               Path testPath = fs.getPath("/home/" + login + "/tmp");
+                               Files.list(testPath).forEach(System.out::println);
+                               test(testPath);
+
+                       } finally {
+                               client.stop();
+                       }
+               } catch (Exception e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       static void test(Path testBase) {
+               try {
+                       Path testPath = testBase.resolve("ssh-test.txt");
+                       Files.createFile(testPath);
+                       log.debug("Created file " + testPath);
+                       Files.delete(testPath);
+                       log.debug("Deleted " + testPath);
+                       String txt = "TEST\nTEST2\n";
+                       byte[] arr = txt.getBytes();
+                       Files.write(testPath, arr);
+                       log.debug("Wrote " + testPath);
+                       byte[] read = Files.readAllBytes(testPath);
+                       log.debug("Read " + testPath);
+                       Path testDir = testBase.resolve("testDir");
+                       log.debug("Resolved " + testDir);
+                       // Copy
+                       Files.createDirectory(testDir);
+                       log.debug("Created directory " + testDir);
+                       Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+                       log.debug("Created sub directories " + subsubdir);
+                       Path copiedFile = testDir.resolve("copiedFile.txt");
+                       log.debug("Resolved " + copiedFile);
+                       Path relativeCopiedFile = testDir.relativize(copiedFile);
+                       log.debug("Relative copied file " + relativeCopiedFile);
+                       try (OutputStream out = Files.newOutputStream(copiedFile);
+                                       InputStream in = Files.newInputStream(testPath)) {
+                               StreamUtils.copy(in, out);
+                       }
+                       log.debug("Copied " + testPath + " to " + copiedFile);
+                       Files.delete(testPath);
+                       log.debug("Deleted " + testPath);
+                       byte[] copiedRead = Files.readAllBytes(copiedFile);
+                       log.debug("Read " + copiedFile);
+                       // Browse directories
+                       DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+                       int fileCount = 0;
+                       Path listedFile = null;
+                       for (Path file : files) {
+                               fileCount++;
+                               if (!Files.isDirectory(file))
+                                       listedFile = file;
+                       }
+                       log.debug("Listed " + testDir);
+                       // Generic attributes
+                       Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+                       log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/DefaultClientIdentityLoader.java
new file mode 100644 (file)
index 0000000..9199198
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.cms.ssh.cli;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Objects;
+
+import org.apache.sshd.client.config.keys.ClientIdentityLoader;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.apache.sshd.common.session.SessionContext;
+import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.apache.sshd.common.util.io.resource.PathResource;
+import org.apache.sshd.common.util.security.SecurityUtils;
+
+/** Separate class in order to avoit static field from Apache SSHD. */
+class DefaultClientIdentityLoader implements ClientIdentityLoader {
+       @Override
+       public boolean isValidLocation(NamedResource location) throws IOException {
+               Path path = toPath(location);
+               return Files.exists(path, IoUtils.EMPTY_LINK_OPTIONS);
+       }
+
+       @Override
+       public Iterable<KeyPair> loadClientIdentities(SessionContext session, NamedResource location,
+                       FilePasswordProvider provider) throws IOException, GeneralSecurityException {
+               Path path = toPath(location);
+               PathResource resource = new PathResource(path);
+               try (InputStream inputStream = resource.openInputStream()) {
+                       return SecurityUtils.loadKeyPairIdentities(session, resource, inputStream, provider);
+               }
+       }
+
+       @Override
+       public String toString() {
+               return "DEFAULT";
+       }
+
+       private Path toPath(NamedResource location) {
+               Objects.requireNonNull(location, "No location provided");
+
+               Path path = Paths
+                               .get(ValidateUtils.checkNotNullAndNotEmpty(location.getName(), "No location value for %s", location));
+               path = path.toAbsolutePath();
+               path = path.normalize();
+               return path;
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshCli.java
new file mode 100644 (file)
index 0000000..2877275
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.ssh.cli;
+
+import org.argeo.api.cli.CommandsCli;
+
+/** SSH command line interface. */
+public class SshCli extends CommandsCli {
+       public SshCli(String commandName) {
+               super(commandName);
+               addCommand("shell", new SshShell());
+       }
+
+       @Override
+       public String getDescription() {
+               return "SSH utilities.";
+       }
+
+       public static void main(String[] args) {
+               mainImpl(new SshCli("ssh"), args);
+       }
+
+}
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/cli/SshShell.java
new file mode 100644 (file)
index 0000000..dffb440
--- /dev/null
@@ -0,0 +1,122 @@
+package org.argeo.cms.ssh.cli;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.keys.FilePasswordProvider;
+import org.argeo.api.cli.CommandArgsException;
+import org.argeo.api.cli.DescribedCommand;
+import org.argeo.cms.ssh.AbstractSsh;
+import org.argeo.cms.ssh.Ssh;
+
+public class SshShell implements DescribedCommand<String> {
+       private Option portOption;
+
+       @Override
+       public Options getOptions() {
+               Options options = new Options();
+               portOption = Option.builder().option("p").longOpt("port").hasArg().desc("port to connect to").build();
+               options.addOption(portOption);
+               return options;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               CommandLine cl = toCommandLine(args);
+               String portStr = cl.getOptionValue(portOption);
+               if (portStr == null)
+                       portStr = "22";
+
+               if (cl.getArgList().size() == 0)
+                       throw new CommandArgsException("Host must be provided");
+               String host = cl.getArgList().get(0);
+
+               String uriStr = "ssh://" + host + ":" + portStr + "/";
+               // System.out.println(uriStr);
+               URI uri = URI.create(uriStr);
+
+               Ssh ssh = null;
+               try {
+                       ssh = new Ssh(uri);
+                       boolean osAgent;
+                       SshAgent sshAgent;
+                       try {
+                               String sshAuthSockentEnv = System.getenv(SshAgent.SSH_AUTHSOCKET_ENV_NAME);
+                               if (sshAuthSockentEnv != null) {
+                                       ssh.getSshClient().getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, sshAuthSockentEnv);
+                                       SshAgentFactory agentFactory = new UnixAgentFactory();
+                                       ssh.getSshClient().setAgentFactory(agentFactory);
+                                       sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+                                       osAgent = true;
+                               } else {
+                                       osAgent = false;
+                               }
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                               osAgent = false;
+                       }
+
+                       if (!osAgent) {
+                               SshAgentFactory agentFactory = new LocalAgentFactory();
+                               ssh.getSshClient().setAgentFactory(agentFactory);
+                               sshAgent = agentFactory.createClient(null, ssh.getSshClient());
+                               String keyPath = System.getProperty("user.home") + "/.ssh/id_rsa";
+
+                               char[] keyPassword = AbstractSsh.readPassword();
+                               NamedResource namedResource = new NamedResource() {
+
+                                       @Override
+                                       public String getName() {
+                                               return keyPath;
+                                       }
+                               };
+                               KeyPair keyPair = new DefaultClientIdentityLoader()
+                                               .loadClientIdentities(null, namedResource, FilePasswordProvider.of(new String(keyPassword)))
+                                               .iterator().next();
+                               sshAgent.addIdentity(keyPair, "NO COMMENT");
+                       }
+
+//                             char[] keyPassword = AbstractSsh.readPassword();
+//                             SshKeyPair keyPair = SshKeyPair.loadDefault(keyPassword);
+//                             Arrays.fill(keyPassword, '*');
+//                             ssh.setSshKeyPair(keyPair);
+//                             ssh.authenticate();
+                       ssh.verifyAuth();
+
+                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                       System.out.println("Ssh available in " + jvmUptime + " ms.");
+
+                       AbstractSsh.openShell(ssh);
+               } catch (IOException | GeneralSecurityException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+                       if (ssh != null)
+                               ssh.closeSession();
+               }
+               return null;
+       }
+
+       @Override
+       public String getUsage() {
+               return "<hostname>";
+       }
+
+       @Override
+       public String getDescription() {
+               return "Opens a remote shell";
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/ssh/package-info.java
new file mode 100644 (file)
index 0000000..9555662
--- /dev/null
@@ -0,0 +1,2 @@
+/** SSH support. */
+package org.argeo.cms.ssh;
\ No newline at end of file
diff --git a/org.argeo.cms.pgsql/.classpath b/org.argeo.cms.pgsql/.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.cms.pgsql/.project b/org.argeo.cms.pgsql/.project
deleted file mode 100644 (file)
index ff01ad9..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.pgsql</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.cms.pgsql/bnd.bnd b/org.argeo.cms.pgsql/bnd.bnd
deleted file mode 100644 (file)
index 9c73009..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Import-Package: org.postgresql;version="[42,43)"
diff --git a/org.argeo.cms.pgsql/build.properties b/org.argeo.cms.pgsql/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/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java b/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java
deleted file mode 100644 (file)
index 9db43df..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.cms.pgsql.util;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import org.postgresql.Driver;
-
-/** Simple PostgreSQL check. */
-public class CheckPg {
-
-       public List<String> listTables() {
-               String osUser = System.getProperty("user.name");
-
-               String url = "jdbc:postgresql://localhost/" + osUser;
-               Properties props = new Properties();
-               props.setProperty("user", osUser);
-               props.setProperty("password", "changeit");
-               List<String> result = new ArrayList<>();
-
-               Driver driver = new Driver();
-               try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) {
-                       s.execute("SELECT * FROM pg_catalog.pg_tables");
-                       ResultSet rs = s.getResultSet();
-                       while (rs.next()) {
-                               result.add(rs.getString("tablename"));
-                       }
-                       return result;
-               } catch (SQLException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public static void main(String[] args) {
-               new CheckPg().listTables().forEach(System.out::println);
-       }
-
-}
diff --git a/org.argeo.cms.ux/.classpath b/org.argeo.cms.ux/.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.cms.ux/.project b/org.argeo.cms.ux/.project
new file mode 100644 (file)
index 0000000..a2f33e2
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.ux</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.ux/bnd.bnd b/org.argeo.cms.ux/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/org.argeo.cms.ux/build.properties b/org.argeo.cms.ux/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/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java b/org.argeo.cms.ux/src/org/argeo/cms/media/SvgToPng.java
new file mode 100644 (file)
index 0000000..852cb52
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.media;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.ImageTranscoder;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+
+public class SvgToPng {
+
+       public void convertSvgDir(Path sourceDir, Path targetDir, int width) {
+               System.out.println("##\n## " + width + "px - " + sourceDir + "\n##");
+               try {
+                       if (targetDir == null)
+                               targetDir = sourceDir.getParent().resolve(Integer.toString(width));
+                       Files.createDirectories(targetDir);
+
+                       PNGTranscoder transcoder = new PNGTranscoder();
+                       // transcoder.addTranscodingHint(ImageTranscoder.KEY_BACKGROUND_COLOR,
+                       // Color.WHITE);
+                       transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) width);
+                       transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) width);
+
+                       for (Path source : Files.newDirectoryStream(sourceDir, "*.svg")) {
+                               // FIXME extract base name
+                               String baseName = null; // = FilenameUtils.getBaseName(source.toString());
+                               Path target = targetDir.resolve(baseName + ".png");
+                               convertSvgFile(transcoder, source, target);
+                       }
+               } catch (IOException | TranscoderException e) {
+                       throw new IllegalStateException("Cannot convert from " + sourceDir + " to " + targetDir, e);
+               }
+
+       }
+
+       protected void convertSvgFile(ImageTranscoder transcoder, Path source, Path target)
+                       throws IOException, TranscoderException {
+               try (Reader reader = Files.newBufferedReader(source); OutputStream out = Files.newOutputStream(target);) {
+                       TranscoderInput input = new TranscoderInput(reader);
+//                     BufferedImage image = transcoder.createImage(32, 32);
+                       TranscoderOutput output = new TranscoderOutput(out);
+                       transcoder.transcode(input, output);
+                       System.out.println(source.getFileName() + " -> " + target);
+               }
+       }
+
+       public static void main(String[] args) throws Exception {
+
+               Path path = Paths.get(args[0]);
+
+               SvgToPng svgToPng = new SvgToPng();
+               svgToPng.convertSvgDir(path, null, 16);
+               svgToPng.convertSvgDir(path, null, 32);
+               svgToPng.convertSvgDir(path, null, 64);
+               svgToPng.convertSvgDir(path, null, 96);
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractCmsEditable.java
new file mode 100644 (file)
index 0000000..a1cd3d9
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.ux;
+
+import java.util.IdentityHashMap;
+
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.api.cms.ux.CmsEditionEvent;
+import org.argeo.api.cms.ux.CmsEditionListener;
+
+public abstract class AbstractCmsEditable implements CmsEditable {
+       private IdentityHashMap<CmsEditionListener, Object> listeners = new IdentityHashMap<>();
+
+       protected void notifyListeners(CmsEditionEvent e) {
+               if (CmsEditionEvent.START_EDITING == e.getType()) {
+                       for (CmsEditionListener listener : listeners.keySet())
+                               listener.editionStarted(e);
+               } else if (CmsEditionEvent.STOP_EDITING == e.getType()) {
+                       for (CmsEditionListener listener : listeners.keySet())
+                               listener.editionStopped(e);
+               } else {
+                       throw new IllegalArgumentException("Unkown edition event type " + e.getType());
+               }
+       }
+
+       @Override
+       public void addCmsEditionListener(CmsEditionListener listener) {
+               listeners.put(listener, new Object());
+       }
+
+       @Override
+       public void removeCmsEditionListener(CmsEditionListener listener) {
+               listeners.remove(listener, new Object());
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/AbstractImageManager.java
new file mode 100644 (file)
index 0000000..41c905e
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.cms.ux;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+
+/** Manages only public images so far. */
+public abstract class AbstractImageManager<V, M> implements CmsImageManager<V, M> {
+       public final static String NO_IMAGE = "icons/noPic-square-640px.png";
+       public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320);
+       public final static Float NO_IMAGE_RATIO = 1f;
+
+       protected Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) {
+               if (constraints.getWidth() != 0 && constraints.getHeight() != 0) {
+                       return constraints;
+               } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) {
+                       return orig;
+               } else if (constraints.getHeight() == 0) {// force width
+                       return new Cms2DSize(constraints.getWidth(),
+                                       scale(orig.getHeight(), orig.getWidth(), constraints.getWidth()));
+               } else if (constraints.getWidth() == 0) {// force height
+                       return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()),
+                                       constraints.getHeight());
+               }
+               throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints);
+       }
+
+       protected int scale(int origDimension, int otherDimension, int otherConstraint) {
+               return Math.round(origDimension * divide(otherConstraint, otherDimension));
+       }
+
+       protected float divide(int a, int b) {
+               return ((float) a) / ((float) b);
+       }
+
+       /** @return null if not available */
+       @Override
+       public String getImageTag(M node) {
+               return getImageTag(node, getImageSize(node));
+       }
+
+       protected String getImageTag(M node, Cms2DSize size) {
+               StringBuilder buf = getImageTagBuilder(node, size);
+               if (buf == null)
+                       return null;
+               return buf.append("/>").toString();
+       }
+
+       /** @return null if not available */
+       @Override
+       public StringBuilder getImageTagBuilder(M node, Cms2DSize size) {
+               return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
+       }
+
+       /** @return null if not available */
+       protected StringBuilder getImageTagBuilder(M node, String width, String height) {
+               String url = getImageUrl(node);
+               if (url == null)
+                       return null;
+               return CmsUxUtils.imgBuilder(url, width, height);
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/CmsUxUtils.java
new file mode 100644 (file)
index 0000000..087b4ff
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.cms.ux;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.util.CurrentSubject;
+
+public class CmsUxUtils {
+       public static ContentSession getContentSession(ContentRepository contentRepository, CmsView cmsView) {
+               return CurrentSubject.callAs(cmsView.getCmsSession().getSubject(), () -> contentRepository.get());
+       }
+
+       public static String getTitle(Content content) {
+               return content.getName().getLocalPart();
+       }
+
+       /** singleton */
+       private CmsUxUtils() {
+
+       }
+
+       public static StringBuilder imgBuilder(String src, String width, String height) {
+               return new StringBuilder(64).append("<img width='").append(width).append("' height='").append(height)
+                               .append("' src='").append(src).append("'");
+       }
+
+       public static String img(String src, String width, String height) {
+               return imgBuilder(src, width, height).append("/>").toString();
+       }
+
+       public static String img(String src, Cms2DSize size) {
+               return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight()));
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentHierarchicalPart.java
new file mode 100644 (file)
index 0000000..baaa252
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.cms.ux.acr;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.ux.widgets.AbstractHierarchicalPart;
+import org.argeo.cms.ux.widgets.HierarchicalPart;
+
+public class ContentHierarchicalPart extends AbstractHierarchicalPart<Content> implements HierarchicalPart<Content> {
+       @Override
+       public List<Content> getChildren(Content content) {
+               List<Content> res = new ArrayList<>();
+               if (isLeaf(content))
+                       return res;
+               if (content == null)
+                       return res;
+               for (Iterator<Content> it = content.iterator(); it.hasNext();) {
+                       res.add(it.next());
+               }
+
+               return res;
+       }
+
+       protected boolean isLeaf(Content content) {
+               return false;
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/acr/ContentPart.java
new file mode 100644 (file)
index 0000000..0a0ad0e
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.ux.acr;
+
+import org.argeo.api.acr.Content;
+
+/** A part displaying or editing a content. */
+public interface ContentPart {
+       Content getContent();
+
+       @Deprecated
+       default Content getNode() {
+               return getContent();
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractColumnsPart.java
new file mode 100644 (file)
index 0000000..143b9cc
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractColumnsPart<INPUT, TYPE> extends AbstractDataPart<INPUT, TYPE> implements ColumnsPart<INPUT, TYPE> {
+
+       private List<Column<TYPE>> columns = new ArrayList<>();
+
+       @Override
+       public Column<TYPE> getColumn(int index) {
+               if (index >= columns.size())
+                       throw new IllegalArgumentException("There a only " + columns.size());
+               return columns.get(index);
+       }
+
+       @Override
+       public void addColumn(Column<TYPE> column) {
+               columns.add(column);
+       }
+
+       @Override
+       public int getColumnCount() {
+               return columns.size();
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractDataPart.java
new file mode 100644 (file)
index 0000000..04811af
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.IdentityHashMap;
+import java.util.function.Consumer;
+
+public abstract class AbstractDataPart<INPUT, TYPE> implements DataPart<INPUT, TYPE> {
+       private Consumer<TYPE> onSelected;
+       private Consumer<TYPE> onAction;
+
+       private IdentityHashMap<DataView<INPUT, TYPE>, Object> views = new IdentityHashMap<>();
+
+       private INPUT data;
+
+       @Override
+       public void setInput(INPUT data) {
+               this.data = data;
+               refresh();
+       }
+
+       @Override
+       public INPUT getInput() {
+               return data;
+       }
+
+       @Override
+       public void onSelected(Consumer<TYPE> onSelected) {
+               this.onSelected = onSelected;
+       }
+
+       @Override
+       public void onAction(Consumer<TYPE> onAction) {
+               this.onAction = onAction;
+       }
+
+       public Consumer<TYPE> getOnSelected() {
+               return onSelected;
+       }
+
+       public Consumer<TYPE> getOnAction() {
+               return onAction;
+       }
+
+       @Override
+       public void refresh() {
+               for (DataView<INPUT, TYPE> view : views.keySet()) {
+                       view.refresh();
+               }
+       }
+
+       protected void notifyItemCountChange() {
+               for (DataView<INPUT, TYPE> view : views.keySet()) {
+                       view.notifyItemCountChange();
+               }
+       }
+
+       @Override
+       public void addView(DataView<INPUT, TYPE> view) {
+               views.put(view, new Object());
+       }
+
+       @Override
+       public void removeView(DataView<INPUT, TYPE> view) {
+               views.remove(view);
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedForm.java
new file mode 100644 (file)
index 0000000..f1e89f1
--- /dev/null
@@ -0,0 +1,86 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractGuidedForm implements GuidedForm {
+       private String formTitle;
+       private List<Page> pages = new ArrayList<>();
+       private View view;
+
+       @Override
+       public abstract void addPages();
+
+       public void addPage(AbstractGuidedFormPage page) {
+               page.setView(view);
+               pages.add(page);
+       }
+
+       @Override
+       public boolean canFinish() {
+               return false;
+       }
+
+       @Override
+       public boolean performFinish() {
+               return false;
+       }
+
+       @Override
+       public boolean performCancel() {
+               return false;
+       }
+
+       @Override
+       public int getPageCount() {
+               return pages.size();
+       }
+
+       @Override
+       public List<Page> getPages() {
+               return Collections.unmodifiableList(pages);
+       }
+
+       @Override
+       public Page getStartingPage() {
+               if (pages.isEmpty())
+                       throw new IllegalStateException("No page available");
+               return pages.get(0);
+       }
+
+       @Override
+       public Page getPreviousPage(Page page) {
+               int index = pages.indexOf(page);
+               if (index == 0 || index == -1) {
+                       // first page or page not found
+                       return null;
+               }
+               return pages.get(index - 1);
+       }
+
+       @Override
+       public Page getNextPage(Page page) {
+               int index = pages.indexOf(page);
+               if (index == pages.size() - 1 || index == -1) {
+                       // last page or page not found
+                       return null;
+               }
+               return pages.get(index + 1);
+       }
+
+       public void setFormTitle(String formTitle) {
+               this.formTitle = formTitle;
+       }
+
+       @Override
+       public String getFormTitle() {
+               return formTitle;
+       }
+
+       @Override
+       public void setView(View view) {
+               this.view = view;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractGuidedFormPage.java
new file mode 100644 (file)
index 0000000..d565510
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.cms.ux.widgets;
+
+import org.argeo.cms.ux.widgets.GuidedForm.View;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+
+public class AbstractGuidedFormPage implements Page {
+       private String pageName;
+       private String title;
+       private View view;
+
+       public AbstractGuidedFormPage(String pageName) {
+               super();
+               this.pageName = pageName;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+       }
+
+       public void setView(View container) {
+               this.view = container;
+
+       }
+
+       public String getPageName() {
+               return pageName;
+       }
+
+       @Override
+       public String getTitle() {
+               return title;
+       }
+
+       public View getView() {
+               return view;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractHierarchicalPart.java
new file mode 100644 (file)
index 0000000..ccdcf4e
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.cms.ux.widgets;
+
+public abstract class AbstractHierarchicalPart<T> extends AbstractColumnsPart<T, T> implements HierarchicalPart<T> {
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/AbstractTabularPart.java
new file mode 100644 (file)
index 0000000..835bc7e
--- /dev/null
@@ -0,0 +1,6 @@
+package org.argeo.cms.ux.widgets;
+
+public abstract class AbstractTabularPart<INPUT, TYPE> extends AbstractColumnsPart<INPUT, TYPE>
+               implements TabularPart<INPUT, TYPE> {
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/CmsDialog.java
new file mode 100644 (file)
index 0000000..3b1630d
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.ux.widgets;
+
+public interface CmsDialog {
+
+       // must be the same value as org.eclipse.jface.window.Window#OK
+       int OK = 0;
+       // must be the same value as org.eclipse.jface.window.Window#CANCEL
+       int CANCEL = 1;
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/Column.java
new file mode 100644 (file)
index 0000000..71cd263
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.ux.widgets;
+
+import org.argeo.api.cms.ux.CmsIcon;
+
+/** A column in a data representation. */
+@FunctionalInterface
+public interface Column<TYPE> {
+       String getText(TYPE model);
+
+       default int getWidth() {
+               return 200;
+       }
+
+       default CmsIcon getIcon(TYPE model) {
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/ColumnsPart.java
new file mode 100644 (file)
index 0000000..2aaeb49
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.cms.ux.widgets;
+
+/** A presentation of data in columns. */
+public interface ColumnsPart<INPUT, TYPE> extends DataPart<INPUT, TYPE> {
+
+       Column<TYPE> getColumn(int index);
+
+       void addColumn(Column<TYPE> column);
+
+       int getColumnCount();
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataPart.java
new file mode 100644 (file)
index 0000000..9d3ca33
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.function.Consumer;
+
+public interface DataPart<INPUT, TYPE> {
+       void setInput(INPUT data);
+
+       INPUT getInput();
+
+       void onSelected(Consumer<TYPE> onSelected);
+
+       Consumer<TYPE> getOnSelected();
+
+       void onAction(Consumer<TYPE> onAction);
+
+       Consumer<TYPE> getOnAction();
+
+       void refresh();
+
+       void addView(DataView<INPUT, TYPE> view);
+
+       void removeView(DataView<INPUT, TYPE> view);
+
+//     void select(TYPE data);
+//
+//     TYPE getSelected();
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DataView.java
new file mode 100644 (file)
index 0000000..311cf92
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.cms.ux.widgets;
+
+public interface DataView<INPUT,TYPE> {
+       void refresh();
+       
+       void notifyItemCountChange();
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/DefaultTabularPart.java
new file mode 100644 (file)
index 0000000..cb30af6
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultTabularPart<INPUT, T> extends AbstractTabularPart<INPUT, T> implements TabularPart<INPUT, T> {
+       private List<T> content;
+
+       @Override
+       public int getItemCount() {
+               return content.size();
+       }
+
+       @Override
+       public T getData(int row) {
+               assert row < getItemCount();
+               return content.get(row);
+       }
+
+       @Override
+       public void refresh() {
+               INPUT input = getInput();
+               if (input == null) {
+                       content = new ArrayList<>();
+                       return;
+               }
+               content = asList(input);
+               super.refresh();
+       }
+
+       protected List<T> asList(INPUT input) {
+               List<T> res = new ArrayList<>();
+               content.clear();
+               if (input instanceof List) {
+                       content = (List<T>) input;
+               } else if (input instanceof Iterable) {
+                       for (T item : (Iterable<T>) input)
+                               content.add(item);
+               } else {
+                       throw new IllegalArgumentException(
+                                       "Unsupported class " + input.getClass() + ", method should be overridden.");
+               }
+               return res;
+       }
+
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/EditablePart.java
new file mode 100644 (file)
index 0000000..1257cfc
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.ux.widgets;
+
+/** Manages whether an editable or non editable control is shown. */
+public interface EditablePart {
+       public void startEditing();
+
+       public void stopEditing();
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/GuidedForm.java
new file mode 100644 (file)
index 0000000..de8554e
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.List;
+
+public interface GuidedForm {
+       String getFormTitle();
+
+       boolean canFinish();
+
+       boolean performFinish();
+
+       boolean performCancel();
+
+       void addPages();
+
+       int getPageCount();
+
+       List<Page> getPages();
+
+       Page getStartingPage();
+
+       Page getPreviousPage(Page page);
+
+       Page getNextPage(Page page);
+
+       void setView(View view);
+
+       interface Page {
+
+               default boolean canFlipToNextPage() {
+                       return true;
+               }
+
+               default String getMessage() {
+                       return null;
+               }
+
+               String getTitle();
+
+       }
+
+       interface View {
+               void updateButtons();
+       }
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/HierarchicalPart.java
new file mode 100644 (file)
index 0000000..8f0e798
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.List;
+
+/** A hierarchical representation of data. */
+public interface HierarchicalPart<T> extends ColumnsPart<T, T> {
+       List<T> getChildren(T parent);
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TabularPart.java
new file mode 100644 (file)
index 0000000..01b4d6b
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.ux.widgets;
+
+/** A tabular presentation of data. */
+public interface TabularPart<INPUT, TYPE> extends ColumnsPart<INPUT, TYPE> {
+       int getItemCount();
+
+       TYPE getData(int row);
+}
diff --git a/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java b/org.argeo.cms.ux/src/org/argeo/cms/ux/widgets/TreeParent.java
new file mode 100644 (file)
index 0000000..97dedcc
--- /dev/null
@@ -0,0 +1,133 @@
+package org.argeo.cms.ux.widgets;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Parent / children semantic to be used for simple UI Tree structure */
+public class TreeParent {
+       private String name;
+       private TreeParent parent;
+
+       private List<Object> children;
+
+       /**
+        * Unique id within the context of a tree display. If set, equals() and
+        * hashCode() methods will be based on it
+        */
+       private String path = null;
+
+       /** False until at least one child has been added, then true until cleared */
+       private boolean loaded = false;
+
+       public TreeParent(String name) {
+               this.name = name;
+               children = new ArrayList<Object>();
+       }
+
+       public synchronized void addChild(Object child) {
+               loaded = true;
+               children.add(child);
+               if (child instanceof TreeParent)
+                       ((TreeParent) child).setParent(this);
+       }
+
+       /**
+        * Remove this child. The child is disposed.
+        */
+       public synchronized void removeChild(Object child) {
+               children.remove(child);
+               if (child instanceof TreeParent) {
+                       ((TreeParent) child).dispose();
+               }
+       }
+
+       public synchronized void clearChildren() {
+               for (Object obj : children) {
+                       if (obj instanceof TreeParent)
+                               ((TreeParent) obj).dispose();
+               }
+               loaded = false;
+               children.clear();
+       }
+
+       /**
+        * If overridden, <code>super.dispose()</code> must be called, typically
+        * after custom cleaning.
+        */
+       public synchronized void dispose() {
+               clearChildren();
+               parent = null;
+               children = null;
+       }
+
+       public synchronized Object[] getChildren() {
+               return children.toArray(new Object[children.size()]);
+       }
+
+       @SuppressWarnings("unchecked")
+       public synchronized <T> List<T> getChildrenOfType(Class<T> clss) {
+               List<T> lst = new ArrayList<T>();
+               for (Object obj : children) {
+                       if (clss.isAssignableFrom(obj.getClass()))
+                               lst.add((T) obj);
+               }
+               return lst;
+       }
+
+       public synchronized boolean hasChildren() {
+               return children.size() > 0;
+       }
+
+       public Object getChildByName(String name) {
+               for (Object child : children) {
+                       if (child.toString().equals(name))
+                               return child;
+               }
+               return null;
+       }
+
+       public synchronized Boolean isLoaded() {
+               return loaded;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setParent(TreeParent parent) {
+               this.parent = parent;
+               if (parent != null && parent.path != null)
+                       this.path = parent.path + '/' + name;
+               else
+                       this.path = '/' + name;
+       }
+
+       public TreeParent getParent() {
+               return parent;
+       }
+
+       public String toString() {
+               return getName();
+       }
+
+       public int compareTo(TreeParent o) {
+               return name.compareTo(o.name);
+       }
+
+       @Override
+       public int hashCode() {
+               if (path != null)
+                       return path.hashCode();
+               else
+                       return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (path != null && obj instanceof TreeParent)
+                       return path.equals(((TreeParent) obj).path);
+               else
+                       return name.equals(obj.toString());
+       }
+
+}
index 4a00becd81df57ed094ab0d2958c974510456391..3628e336878e528db30e5202048bfc67be000fbd 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
                <attributes>
                        <attribute name="module" value="true"/>
                </attributes>
diff --git a/org.argeo.cms/.gitignore b/org.argeo.cms/.gitignore
new file mode 100644 (file)
index 0000000..77dacfc
--- /dev/null
@@ -0,0 +1 @@
+node/
\ No newline at end of file
diff --git a/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml b/org.argeo.cms/OSGI-INF/cmsAcrHttpHandler.xml
new file mode 100644 (file)
index 0000000..afd4e0a
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true">
+   <implementation class="org.argeo.cms.internal.runtime.CmsAcrHttpHandler"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpHandler"/>
+   </service>
+   <property name="context.path" type="String" value="/api/acr/" />
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsAuthenticator.xml b/org.argeo.cms/OSGI-INF/cmsAuthenticator.xml
new file mode 100644 (file)
index 0000000..9b15214
--- /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="org.argeo.cms">
+   <implementation class="org.argeo.cms.internal.http.CmsAuthenticator"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.Authenticator"/>
+   </service>
+   <reference cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsContentRepository.xml b/org.argeo.cms/OSGI-INF/cmsContentRepository.xml
new file mode 100644 (file)
index 0000000..306717b
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="ACR Content Repository">
+   <implementation class="org.argeo.cms.internal.runtime.DeployedContentRepository"/>
+   <reference bind="addProvider" cardinality="0..n" interface="org.argeo.api.acr.spi.ContentProvider" name="ContentProvider" policy="dynamic" />
+   <service>
+      <provide interface="org.argeo.api.acr.ContentRepository"/>
+      <provide interface="org.argeo.api.acr.spi.ProvidedRepository"/>
+   </service>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+   <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.api.cms.directory.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
index 63d43192e9b6294084e9d4a83c37d6b1980881f1..d2413bd1ffcd6f9aecbd55da02946c8605b1b1ee 100644 (file)
@@ -6,5 +6,7 @@
       <provide interface="org.argeo.api.cms.CmsContext"/>
    </service>
    <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
    <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-</scr:component>
+   <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+ </scr:component>
index d36a911102376e4f3f29b14409a184c46d8f4d5f..66541827db71f0e9af08059420aa7186fe8d9140 100644 (file)
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="false" name="CMS Deployment">
-   <reference bind="setDeployConfig" cardinality="1..1" interface="org.argeo.cms.internal.osgi.DeployConfig" name="DeployConfig" policy="static"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS Deployment">
    <implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
    <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.cms.CmsSshd" policy="dynamic"/>
+   <reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="dynamic"/>
+   <reference bind="addHttpHandler" unbind="removeHttpHandler" cardinality="0..n" interface="com.sun.net.httpserver.HttpHandler" policy="dynamic"/>
    <service>
       <provide interface="org.argeo.api.cms.CmsDeployment"/>
    </service>
diff --git a/org.argeo.cms/OSGI-INF/cmsEventBus.xml b/org.argeo.cms/OSGI-INF/cmsEventBus.xml
new file mode 100644 (file)
index 0000000..6bb67ce
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Event Bus">
+   <implementation class="org.argeo.cms.internal.runtime.CmsEventBusImpl"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsEventBus"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml b/org.argeo.cms/OSGI-INF/cmsOsgiLogger.xml
new file mode 100644 (file)
index 0000000..93fc5c0
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS OSGi Logger">
+   <implementation class="org.argeo.cms.internal.osgi.CmsOsgiLogger"/>
+   <reference bind="setLogReaderService" cardinality="1..1" interface="org.osgi.service.log.LogReaderService" name="LogReaderService" policy="static"/>
+</scr:component>
index a81e9f0681c17a03a65ab57a024c9cf5ba877be2..71dc6d4db41c19ac8a3c7b2c3e9e866ca3383f98 100644 (file)
@@ -4,4 +4,5 @@
    <service>
       <provide interface="org.argeo.api.cms.CmsState"/>
    </service>
+   <reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
 </scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml b/org.argeo.cms/OSGI-INF/cmsUserAdmin.xml
new file mode 100644 (file)
index 0000000..52c7531
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="false" name="Node User Admin">
+   <implementation class="org.argeo.cms.internal.runtime.CmsUserAdmin"/>
+   <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
+   <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkControl" name="WorkControl" policy="static"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+    <provide interface="org.osgi.service.useradmin.UserAdmin"/>
+ </service>
+</scr:component>
index 524c054eccf733b1a3a295cce1d9e64dc918d7d9..d76c89a1b858d4e1e5f145ba31ae1674817dc05c 100644 (file)
@@ -1,10 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="User Admin Service">
-   <implementation class="org.argeo.cms.internal.auth.CmsUserManagerImpl"/>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="User Admin Service">
+   <implementation class="org.argeo.cms.internal.runtime.CmsUserManagerImpl"/>
    <service>
-      <provide interface="org.argeo.cms.CmsUserManager"/>
+      <provide interface="org.argeo.api.cms.directory.CmsUserManager"/>
    </service>
    <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
-   <reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.api.cms.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
 </scr:component>
diff --git a/org.argeo.cms/OSGI-INF/deployConfig.xml b/org.argeo.cms/OSGI-INF/deployConfig.xml
deleted file mode 100644 (file)
index 0309434..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="Deploy Config">
-   <implementation class="org.argeo.cms.internal.osgi.DeployConfig"/>
-   <service>
-      <provide interface="org.argeo.cms.internal.osgi.DeployConfig"/>
-   </service>
-   <reference bind="setConfigurationAdmin" cardinality="1..1" interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" policy="static"/>
-   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml
deleted file mode 100644 (file)
index eb048d9..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="Node User Admin">
-   <implementation class="org.argeo.cms.internal.osgi.NodeUserAdmin"/>
-   <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
-   <service>
-      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
-   </service>
-   <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkControl" name="WorkControl" policy="static"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
-   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml
deleted file mode 100644 (file)
index c331aa4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Simple Transaction Manager">
-   <implementation class="org.argeo.osgi.transaction.SimpleTransactionManager"/>
-   <service>
-      <provide interface="org.argeo.osgi.transaction.WorkControl"/>
-      <provide interface="org.argeo.osgi.transaction.WorkTransaction"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/transactionManager.xml b/org.argeo.cms/OSGI-INF/transactionManager.xml
new file mode 100644 (file)
index 0000000..df317e9
--- /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" name="Simple Transaction Manager">
+   <implementation class="org.argeo.api.cms.transaction.SimpleTransactionManager"/>
+   <service>
+      <provide interface="org.argeo.api.cms.transaction.WorkControl"/>
+      <provide interface="org.argeo.api.cms.transaction.WorkTransaction"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/uuidFactory.xml b/org.argeo.cms/OSGI-INF/uuidFactory.xml
new file mode 100644 (file)
index 0000000..c1ad6f8
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="UUID Factory">
+   <implementation class="org.argeo.cms.acr.CmsUuidFactory"/>
+   <service>
+      <provide interface="org.argeo.api.uuid.UuidFactory"/>
+   </service>
+</scr:component>
index e75adcdc3b49afd28a98c73422944d81afce5736..ade2f3aa94f72ac1f1d2eee2616dffb48a30a4ef 100644 (file)
@@ -1,18 +1,19 @@
 Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
 Import-Package: \
-org.argeo.osgi.transaction, \
-org.apache.commons.httpclient.cookie;resolution:=optional,\
-!com.sun.security.jgss,\
 org.osgi.*;version=0.0.0,\
 *
 
 Service-Component:\
+OSGI-INF/cmsOsgiLogger.xml,\
+OSGI-INF/uuidFactory.xml,\
+OSGI-INF/cmsEventBus.xml,\
 OSGI-INF/cmsState.xml,\
-OSGI-INF/simpleTransactionManager.xml,\
-OSGI-INF/nodeUserAdmin.xml,\
+OSGI-INF/transactionManager.xml,\
+OSGI-INF/cmsUserAdmin.xml,\
 OSGI-INF/cmsUserManager.xml,\
-OSGI-INF/deployConfig.xml,\
+OSGI-INF/cmsContentRepository.xml,\
+OSGI-INF/cmsAcrHttpHandler.xml,\
 OSGI-INF/cmsDeployment.xml,\
 OSGI-INF/cmsContext.xml,\
 
index db86d95a50099a1a879c9b09b154cee6fd72227b..6ca041a2a19c39c0db563da5d0bc0ab660494744 100644 (file)
@@ -1,12 +1,7 @@
-output.. = bin/
 bin.includes = META-INF/,\
                .,\
                bin/,\
                OSGI-INF/,\
-               OSGI-INF/simpleTransactionManager.xml,\
-               OSGI-INF/cmsState.xml,\
-               OSGI-INF/nodeUserAdmin.xml,\
-               OSGI-INF/deployConfig.xml,\
-               OSGI-INF/cmsDeployment.xml,\
-               OSGI-INF/cmsContext.xml
+               OSGI-INF/cmsEventBus.xml
 source.. = src/
+output.. = bin/
index a7049a4f49a23ea2fcd27dc508418fa73845c5c1..cefdb86b3d71cdf480a5ad22528ad47141e54624 100644 (file)
@@ -8,15 +8,21 @@ import java.util.Map;
 
 import org.argeo.api.cms.CmsApp;
 import org.argeo.api.cms.CmsAppListener;
-import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsTheme;
 
 /** Base class for {@link CmsApp}s. */
 public abstract class AbstractCmsApp implements CmsApp {
+       private CmsContext cmsContext;
+       
        private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
 
        private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
 
-       protected abstract String getThemeId(String uiName);
+       /** To be overridden in order to provide themes. */
+       protected String getThemeId(String uiName) {
+               return null;
+       }
 
        @Override
        public CmsTheme getTheme(String uiName) {
@@ -35,7 +41,7 @@ public abstract class AbstractCmsApp implements CmsApp {
                        String themeId = getThemeId(uiName);
                        if ("org.eclipse.rap.rwt.theme.Default".equals(themeId))
                                continue uiNames;
-                       if (!themes.containsKey(themeId)) {
+                       if (themeId != null && !themes.containsKey(themeId)) {
                                themeMissing = true;
                                break uiNames;
                        }
@@ -66,4 +72,15 @@ public abstract class AbstractCmsApp implements CmsApp {
                cmsAppListeners.remove(listener);
        }
 
+       @Override
+       public CmsContext getCmsContext() {
+               return cmsContext;
+       }
+
+       public void setCmsContext(CmsContext cmsContext) {
+               this.cmsContext = cmsContext;
+       }
+       
+       
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java b/org.argeo.cms/src/org/argeo/cms/AbstractKeyring.java
new file mode 100644 (file)
index 0000000..c2c9c61
--- /dev/null
@@ -0,0 +1,290 @@
+package org.argeo.cms;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.keyring.PBEKeySpecCallback;
+import org.argeo.cms.util.CurrentSubject;
+import org.argeo.cms.util.StreamUtils;
+import org.argeo.api.cms.keyring.CryptoKeyring;
+import org.argeo.api.cms.keyring.Keyring;
+
+/** username / password based keyring. TODO internationalize */
+public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
+       // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
+
+       // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
+       private CallbackHandler defaultCallbackHandler;
+
+       private String charset = "UTF-8";
+
+       /**
+        * Default provider is bouncy castle, in order to have consistent behaviour
+        * across implementations
+        */
+       private String securityProviderName = "BC";
+
+       /**
+        * Whether the keyring has already been created in the past with a master
+        * password
+        */
+       protected abstract Boolean isSetup();
+
+       /**
+        * Setup the keyring persistently, {@link #isSetup()} must return true
+        * afterwards
+        */
+       protected abstract void setup(char[] password);
+
+       /** Populates the key spec callback */
+       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
+
+       protected abstract void encrypt(String path, InputStream unencrypted);
+
+       protected abstract InputStream decrypt(String path);
+
+       /** Triggers lazy initialization */
+       protected SecretKey getSecretKey(char[] password) {
+               Subject subject = CurrentSubject.current();
+               if (subject == null)
+                       throw new IllegalStateException("Current subject cannot be null");
+               // we assume only one secrete key is available
+               Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
+               if (!iterator.hasNext() || password != null) {// not initialized
+                       CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
+                                       : new PasswordProvidedCallBackHandler(password);
+                       ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+                       Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+                       try {
+                               LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject, callbackHandler);
+                               loginContext.login();
+                               // FIXME will login even if password is wrong
+                               iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
+                               return iterator.next();
+                       } catch (LoginException e) {
+                               throw new IllegalStateException("Keyring login failed", e);
+                       } finally {
+                               Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+                       }
+
+               } else {
+                       SecretKey secretKey = iterator.next();
+                       if (iterator.hasNext())
+                               throw new IllegalStateException("More than one secret key in private credentials");
+                       return secretKey;
+               }
+       }
+
+       public InputStream getAsStream(String path) {
+               return decrypt(path);
+       }
+
+       public void set(String path, InputStream in) {
+               encrypt(path, in);
+       }
+
+       public char[] getAsChars(String path) {
+               // InputStream in = getAsStream(path);
+               // CharArrayWriter writer = null;
+               // Reader reader = null;
+               try (InputStream in = getAsStream(path);
+                               CharArrayWriter writer = new CharArrayWriter();
+                               Reader reader = new InputStreamReader(in, charset);) {
+                       StreamUtils.copy(reader, writer);
+                       return writer.toCharArray();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot decrypt to char array", e);
+               } finally {
+                       // IOUtils.closeQuietly(reader);
+                       // IOUtils.closeQuietly(in);
+                       // IOUtils.closeQuietly(writer);
+               }
+       }
+
+       public void set(String path, char[] arr) {
+               // ByteArrayOutputStream out = new ByteArrayOutputStream();
+               // ByteArrayInputStream in = null;
+               // Writer writer = null;
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream();
+                               Writer writer = new OutputStreamWriter(out, charset);) {
+                       // writer = new OutputStreamWriter(out, charset);
+                       writer.write(arr);
+                       writer.flush();
+                       // in = new ByteArrayInputStream(out.toByteArray());
+                       try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
+                               set(path, in);
+                       }
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot encrypt to char array", e);
+               } finally {
+                       // IOUtils.closeQuietly(writer);
+                       // IOUtils.closeQuietly(out);
+                       // IOUtils.closeQuietly(in);
+               }
+       }
+
+       public void unlock(char[] password) {
+               if (!isSetup())
+                       setup(password);
+               SecretKey secretKey = getSecretKey(password);
+               if (secretKey == null)
+                       throw new IllegalStateException("Could not unlock keyring");
+       }
+
+       protected Provider getSecurityProvider() {
+               return Security.getProvider(securityProviderName);
+       }
+
+       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
+               this.defaultCallbackHandler = defaultCallbackHandler;
+       }
+
+       public void setCharset(String charset) {
+               this.charset = charset;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+
+       // @Deprecated
+       // protected static byte[] hash(char[] password, byte[] salt, Integer
+       // iterationCount) {
+       // ByteArrayOutputStream out = null;
+       // OutputStreamWriter writer = null;
+       // try {
+       // out = new ByteArrayOutputStream();
+       // writer = new OutputStreamWriter(out, "UTF-8");
+       // writer.write(password);
+       // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
+       // pwDigest.reset();
+       // pwDigest.update(salt);
+       // byte[] btPass = pwDigest.digest(out.toByteArray());
+       // for (int i = 0; i < iterationCount; i++) {
+       // pwDigest.reset();
+       // btPass = pwDigest.digest(btPass);
+       // }
+       // return btPass;
+       // } catch (Exception e) {
+       // throw new CmsException("Cannot hash", e);
+       // } finally {
+       // IOUtils.closeQuietly(out);
+       // IOUtils.closeQuietly(writer);
+       // }
+       //
+       // }
+
+       /**
+        * Convenience method using the underlying callback to ask for a password
+        * (typically used when the password is not saved in the keyring)
+        */
+       protected char[] ask() {
+               PasswordCallback passwordCb = new PasswordCallback("Password", false);
+               Callback[] dialogCbs = new Callback[] { passwordCb };
+               try {
+                       defaultCallbackHandler.handle(dialogCbs);
+                       char[] password = passwordCb.getPassword();
+                       return password;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot ask for a password", e);
+               }
+
+       }
+
+       class KeyringCallbackHandler implements CallbackHandler {
+               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                       // checks
+                       if (callbacks.length != 2)
+                               throw new IllegalArgumentException(
+                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+                       if (!(callbacks[0] instanceof PasswordCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+
+                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+
+                       if (isSetup()) {
+                               Callback[] dialogCbs = new Callback[] { passwordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+                       } else {// setup keyring
+                               TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+                                               "Enter a master password which will protect your private data");
+                               TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+                                               "(for example your credentials to third-party services)");
+                               TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION,
+                                               "Don't forget this password since the data cannot be read without it");
+                               PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false);
+                               // first try
+                               Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb };
+                               defaultCallbackHandler.handle(dialogCbs);
+
+                               // if passwords different, retry (except if cancelled)
+                               while (passwordCb.getPassword() != null
+                                               && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) {
+                                       TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR,
+                                                       "The passwords do not match");
+                                       dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb };
+                                       defaultCallbackHandler.handle(dialogCbs);
+                               }
+
+                               if (passwordCb.getPassword() != null) {// not cancelled
+                                       setup(passwordCb.getPassword());
+                               }
+                       }
+
+                       if (passwordCb.getPassword() != null)
+                               handleKeySpecCallback(pbeCb);
+               }
+
+       }
+
+       class PasswordProvidedCallBackHandler implements CallbackHandler {
+               private final char[] password;
+
+               public PasswordProvidedCallBackHandler(char[] password) {
+                       this.password = password;
+               }
+
+               @Override
+               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                       // checks
+                       if (callbacks.length != 2)
+                               throw new IllegalArgumentException(
+                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
+                       if (!(callbacks[0] instanceof PasswordCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
+                               throw new UnsupportedCallbackException(callbacks[0]);
+
+                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
+                       passwordCb.setPassword(password);
+                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
+                       handleKeySpecCallback(pbeCb);
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java
deleted file mode 100644 (file)
index a01858a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms;
-
-/** Framework agnostic interface for log notifications */
-@Deprecated
-public interface ArgeoLogListener {
-       /**
-        * Appends a log
-        * 
-        * @param username
-        *            authentified user, null for anonymous
-        * @param level
-        *            INFO, DEBUG, WARN, etc. (logging framework specific)
-        * @param category
-        *            hierarchy (logging framework specific)
-        * @param thread
-        *            name of the thread which logged this message
-        * @param msg
-        *            any object as long as its toString() method returns the
-        *            message
-        * @param exception
-        *            exception in log4j ThrowableStrRep format
-        */
-       public void appendLog(String username, Long timestamp, String level,
-                       String category, String thread, Object msg, String[] exception);
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java
deleted file mode 100644 (file)
index 71c5039..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms;
-
-/**
- * Logging framework agnostic identifying a logging service, to which one can
- * register
- */
-@Deprecated
-public interface ArgeoLogger {
-       /**
-        * Register for events by threads with the same authentication (or all
-        * threads if admin)
-        */
-       public void register(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents);
-
-       /**
-        * For admin use only: register for all users
-        * 
-        * @param listener
-        *            the log listener
-        * @param numberOfPreviousEvents
-        *            the number of previous events to notify
-        * @param everything
-        *            if true even anonymous is logged
-        */
-       public void registerForAll(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents, boolean everything);
-
-       public void unregister(ArgeoLogListener listener);
-
-       public void unregisterForAll(ArgeoLogListener listener);
-}
index 90260c314e1a252ceaec94f8daad09d0eaabccf6..4e869b7fd532df762bfccd7b607ccd96153d8763 100644 (file)
@@ -1,6 +1,7 @@
 package org.argeo.cms;
 
 /** JCR names in the http://www.argeo.org/argeo namespace */
+@Deprecated
 public interface ArgeoNames {
        public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo";
 
index ce043910238ad4a95f9a92f3b13cf29088d3093b..9995725d80ea29473cd77eb10ce97c6e0b8977e3 100644 (file)
@@ -1,9 +1,10 @@
 package org.argeo.cms;
 
 /** JCR types in the http://www.argeo.org/argeo namespace */
+@Deprecated
 public interface ArgeoTypes {
        public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository";
-       
+
        // tabular
        public final static String ARGEO_TABLE = "argeo:table";
        public final static String ARGEO_COLUMN = "argeo:column";
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java b/org.argeo.cms/src/org/argeo/cms/CmsDeployProperty.java
new file mode 100644 (file)
index 0000000..43343bf
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms;
+
+import java.util.Objects;
+
+/** A property that can be used to configure a CMS node deployment. */
+public enum CmsDeployProperty {
+       //
+       // DIRECTORY
+       //
+       DIRECTORY("argeo.directory", 64),
+       //
+       // DATABASE
+       //
+       /** URL of the database backend. */
+       DB_URL("argeo.db.url"),
+       /** DB user of the database backend. */
+       DB_USER("argeo.db.user"),
+       /** DB user password of the database backend. */
+       DB_PASSWORD("argeo.db.password"),
+       //
+       // NETWORK
+       //
+       /** Either a host or an IP address. Restricts all servers to it. */
+       HOST("argeo.host"),
+       /** Either a host or an IP address. Restricts all servers to it. */
+       DNS("argeo.dns", 16),
+       //
+       // HTTP
+       //
+       /** Request an HTTP server on this port. */
+       HTTP_PORT("argeo.http.port"),
+       /** Request an HTTPS server on this port. */
+       HTTPS_PORT("argeo.https.port"),
+       /**
+        * The HTTP header used to convey the DN of a client verified by a reverse
+        * proxy. Typically SSL_CLIENT_S_DN for Apache.
+        */
+       HTTP_PROXY_SSL_HEADER_DN("argeo.http.proxy.ssl.header.dn"),
+       //
+       // SSL
+       //
+       /** SSL keystore for the system. */
+       SSL_KEYSTORE("argeo.ssl.keystore"),
+       /** SSL keystore password for the system. */
+       SSL_PASSWORD("argeo.ssl.password"),
+       /** SSL keystore type password for the system. */
+       SSL_KEYSTORETYPE("argeo.ssl.keystoretype"),
+       /** SSL password for the private key. */
+       SSL_KEYPASSWORD("argeo.ssl.keypassword"),
+       /** Whether a client certificate is required. */
+       SSL_NEEDCLIENTAUTH("argeo.ssl.needclientauth"),
+       /** Whether a client certificate can be used. */
+       SSL_WANTCLIENTAUTH("argeo.ssl.wantclientauth"),
+       /** SSL protocol to use. */
+       SSL_PROTOCOL("argeo.ssl.protocol"),
+       /** SSL algorithm to use. */
+       SSL_ALGORITHM("argeo.ssl.algorithm"),
+       /** Custom SSL trust store. */
+       SSL_TRUSTSTORE("argeo.ssl.truststore"),
+       /** Custom SSL trust store type. */
+       SSL_TRUSTSTORETYPE("argeo.ssl.truststoretype"),
+       /** Custom SSL trust store type. */
+       SSL_TRUSTSTOREPASSWORD("argeo.ssl.truststorepassword"),
+       //
+       // WEBSOCKET
+       //
+       /** Whether web socket should be enables in web server. */
+       WEBSOCKET_ENABLED("argeo.websocket.enabled"),
+       //
+       // SSH
+       //
+       /** Request an HTTP server on this port. */
+       SSHD_PORT("argeo.sshd.port"),
+       /** Path to admin authorized keys file. */
+       SSHD_AUTHORIZEDKEYS("argeo.sshd.authorizedkeys"),
+       //
+       // INTERNATIONALIZATION
+       //
+       /** Locales enabled for this system, the first one is considered the default. */
+       LOCALE("argeo.locale", 256),
+       //
+       // NODE
+       //
+       /** Directories to copy to the data area during the first initialisation. */
+       NODE_INIT("argeo.node.init", 64),
+       //
+       // JAVA
+       //
+       /** Custom JAAS config. */
+       JAVA_LOGIN_CONFIG("java.security.auth.login.config", true),
+       //
+       // OSGi
+       //
+       /** OSGi writable data area. */
+       OSGI_INSTANCE_AREA("osgi.instance.area"),
+       /** OSGi writable configuration area. */
+       OSGI_CONFIGURATION_AREA("osgi.configuration.area"),
+       //
+       ;
+
+       private String property;
+       private boolean systemPropertyOnly = false;
+
+       private int maxCount = 1;
+
+       CmsDeployProperty(String property) {
+               this(property, 1, false);
+       }
+
+       CmsDeployProperty(String property, int maxCount) {
+               this(property, maxCount, false);
+       }
+
+       CmsDeployProperty(String property, boolean systemPropertyOnly) {
+               this.property = property;
+       }
+
+       CmsDeployProperty(String property, int maxCount, boolean systemPropertyOnly) {
+               this.property = property;
+               this.systemPropertyOnly = systemPropertyOnly;
+               this.maxCount = maxCount;
+       }
+
+       public String getProperty() {
+               return property;
+       }
+
+       public boolean isSystemPropertyOnly() {
+               return systemPropertyOnly;
+       }
+
+       public int getMaxCount() {
+               return maxCount;
+       }
+
+       public static CmsDeployProperty find(String property) {
+               int index = getPropertyIndex(property);
+               String propertyName = index == 0 ? property : property.substring(0, property.lastIndexOf('.'));
+               for (CmsDeployProperty deployProperty : values()) {
+                       if (deployProperty.getProperty().equals(propertyName))
+                               return deployProperty;
+               }
+               return null;
+       }
+
+       public static int getPropertyIndex(String property) {
+               Objects.requireNonNull(property);
+               int lastDot = property.lastIndexOf('.');
+               if (lastDot <= 0 || lastDot == (property.length() - 1)) {
+                       throw new IllegalArgumentException("Property " + property + " is not qualified (must contain a dot).");
+               }
+               String lastSegment = property.substring(lastDot + 1);
+               int index;
+               try {
+                       index = Integer.parseInt(lastSegment);
+               } catch (NumberFormatException e) {
+                       index = 0;
+               }
+               return index;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsException.java b/org.argeo.cms/src/org/argeo/cms/CmsException.java
deleted file mode 100644 (file)
index 09d55c2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.cms;
-
-/** @deprecated Use standard Java {@link RuntimeException} instead. */
-@Deprecated
-public class CmsException extends RuntimeException {
-       private static final long serialVersionUID = -5341764743356771313L;
-
-       public CmsException(String message) {
-               super(message);
-       }
-
-       public CmsException(String message, Throwable e) {
-               super(message, e);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsSshd.java b/org.argeo.cms/src/org/argeo/cms/CmsSshd.java
new file mode 100644 (file)
index 0000000..41968be
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** Just a marker interface for the time being. */
+public interface CmsSshd {
+       final static String NODE_USERNAME_ALIAS = "user.name";
+       final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java b/org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
deleted file mode 100644 (file)
index cd76d65..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.cms;
-
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Set;
-
-import javax.security.auth.Subject;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/**
- * Provide method interfaces to manage user concepts without accessing directly
- * the userAdmin.
- */
-public interface CmsUserManager {
-
-       // CurrentUser
-       /** Returns the e-mail of the current logged in user */
-       public String getMyMail();
-
-       // Other users
-       /** Returns a {@link User} given a username */
-       public User getUser(String username);
-
-       /** Can be a group or a user */
-       public String getUserDisplayName(String dn);
-
-       /** Can be a group or a user */
-       public String getUserMail(String dn);
-
-       /** Lists all roles of the given user */
-       public String[] getUserRoles(String dn);
-
-       /** Checks if the passed user belongs to the passed role */
-       public boolean isUserInRole(String userDn, String roleDn);
-
-       // Search
-       /** Returns a filtered list of roles */
-       public Role[] getRoles(String filter) throws InvalidSyntaxException;
-
-       /** Recursively lists users in a given group. */
-       public Set<User> listUsersInGroup(String groupDn, String filter);
-
-       /** Search among groups including system roles and users if needed */
-       public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles);
-
-       /* MISCELLANEOUS */
-       /** Returns the dn of a role given its local ID */
-       public String buildDefaultDN(String localId, int type);
-
-       /** Exposes the main default domain name for this instance */
-       public String getDefaultDomainName();
-
-       /**
-        * Search for a {@link User} (might also be a group) whose uid or cn is equals
-        * to localId within the various user repositories defined in the current
-        * context.
-        */
-       public User getUserFromLocalId(String localId);
-
-       void changeOwnPassword(char[] oldPassword, char[] newPassword);
-
-       void resetPassword(String username, char[] newPassword);
-
-       @Deprecated
-       String addSharedSecret(String username, int hours);
-
-//     String addSharedSecret(String username, String authInfo, String authToken);
-
-       void addAuthToken(String userDn, String token, Integer hours, String... roles);
-
-       void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles);
-
-       void expireAuthToken(String token);
-
-       void expireAuthTokens(Subject subject);
-
-//     User createUserFromPerson(Node person);
-
-//     @Deprecated
-//     public UserAdmin getUserAdmin();
-//
-//     @Deprecated
-//     public UserTransaction getUserTransaction();
-}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/CurrentUser.java
new file mode 100644 (file)
index 0000000..ad12a86
--- /dev/null
@@ -0,0 +1,187 @@
+package org.argeo.cms;
+
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
+import org.argeo.cms.internal.auth.ImpliedByPrincipal;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.service.useradmin.Authorization;
+
+/**
+ * Programmatic access to the currently authenticated user, within a CMS
+ * context.
+ */
+public final class CurrentUser {
+       /**
+        * Technical username of the currently authenticated user.
+        * 
+        * @return the authenticated username or null if not authenticated / anonymous
+        */
+       public static String getUsername() {
+               return getUsername(currentSubject());
+       }
+
+       /**
+        * Human readable name of the currently authenticated user (typically first name
+        * and last name).
+        */
+       public static String getDisplayName() {
+               return getDisplayName(currentSubject());
+       }
+
+       /** Whether a user is currently authenticated. */
+       public static boolean isAnonymous() {
+               return isAnonymous(currentSubject());
+       }
+
+       /** Locale of the current user */
+       public static Locale locale() {
+               return locale(currentSubject());
+       }
+
+       /** Roles of the currently logged-in user */
+       public static Set<String> roles() {
+               return roles(currentSubject());
+       }
+
+       /** Returns true if the current user is in the specified role */
+       public static boolean isInRole(String role) {
+               Set<String> roles = roles();
+               return roles.contains(role);
+       }
+
+       /** Implies this {@link SystemRole} in this context. */
+       public static boolean implies(SystemRole role, String context) {
+               return role.implied(currentSubject(), context);
+       }
+
+       /** Implies this role name, also independently of the context. */
+       public static boolean implies(String role, String context) {
+               return SystemRole.implied(NamespaceUtils.parsePrefixedName(role), currentSubject(), context);
+       }
+
+       /** Get the primary context this user belongs to. */
+       public static boolean isUserContext(String context) {
+               // TODO have the role context as a separated credential in the Subjecto?
+               return RoleNameUtils.getContext(getUsername()).equalsIgnoreCase(context);
+       }
+
+       /** Executes as the current user */
+       public static <T> T doAs(PrivilegedAction<T> action) {
+               return Subject.doAs(currentSubject(), action);
+       }
+
+       /** Executes as the current user */
+       public static <T> T tryAs(PrivilegedExceptionAction<T> action) throws PrivilegedActionException {
+               return Subject.doAs(currentSubject(), action);
+       }
+
+       /*
+        * WRAPPERS
+        */
+
+       public static String getUsername(Subject subject) {
+               if (subject == null)
+                       throw new IllegalArgumentException("Subject cannot be null");
+               if (subject.getPrincipals(X500Principal.class).size() != 1)
+                       return CmsConstants.ROLE_ANONYMOUS;
+               Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
+               return principal.getName();
+       }
+
+       public static String getDisplayName(Subject subject) {
+               return getAuthorization(subject).toString();
+       }
+
+       public static Set<String> roles(Subject subject) {
+               Set<String> roles = new HashSet<String>();
+               roles.add(getUsername(subject));
+               for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) {
+                       roles.add(group.getName());
+               }
+               return roles;
+       }
+
+       public static Locale locale(Subject subject) {
+               Set<Locale> locales = subject.getPublicCredentials(Locale.class);
+               if (locales.isEmpty()) {
+                       Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
+                       return defaultLocale;
+               } else
+                       return locales.iterator().next();
+       }
+
+       /** Whether this user is currently authenticated. */
+       public static boolean isAnonymous(Subject subject) {
+               if (subject == null)
+                       return true;
+               String username = getUsername(subject);
+               return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS);
+       }
+
+       public static CmsSession getCmsSession() {
+               Subject subject = currentSubject();
+               Iterator<CmsSessionId> it = subject.getPrivateCredentials(CmsSessionId.class).iterator();
+               if (!it.hasNext())
+                       throw new IllegalStateException("No CMS session id available for " + subject);
+               CmsSessionId cmsSessionId = it.next();
+               if (it.hasNext())
+                       throw new IllegalStateException("More than one CMS session id available for " + subject);
+               return CmsContextImpl.getCmsContext().getCmsSessionByUuid(cmsSessionId.getUuid());
+       }
+
+       public static boolean isAvailable() {
+               return CurrentSubject.current() != null;
+       }
+
+       /*
+        * HELPERS
+        */
+       private static Subject currentSubject() {
+               Subject subject = CurrentSubject.current();
+               if (subject == null)
+                       throw new IllegalStateException("Cannot find related subject");
+               return subject;
+       }
+
+       private static Authorization getAuthorization(Subject subject) {
+               return subject.getPrivateCredentials(Authorization.class).iterator().next();
+       }
+
+       public static boolean logoutCmsSession(Subject subject) {
+               UUID nodeSessionId;
+               if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1)
+                       nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
+               else
+                       return false;
+               CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByUuid(nodeSessionId);
+
+               // FIXME logout all views
+               // TODO check why it is sometimes null
+               if (cmsSession != null)
+                       cmsSession.close();
+               // if (log.isDebugEnabled())
+               // log.debug("Logged out CMS session " + cmsSession.getUuid());
+               return true;
+       }
+
+       /** singleton */
+       private CurrentUser() {
+       }
+}
index f02e6a2b4439f57a3e5a23f69aaecbda3dcf1c46..8aca8768a04d9e3af196ce9690ea449ba1d7d651 100644 (file)
@@ -1,15 +1,9 @@
 package org.argeo.cms;
 
-import java.security.AccessController;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Locale;
 import java.util.ResourceBundle;
 
-import javax.security.auth.Subject;
-
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.auth.CurrentUser;
 
 /** Utilities simplifying the development of localization enums. */
 public class LocaleUtils {
@@ -64,9 +58,10 @@ public class LocaleUtils {
        /** Where the search for a message is actually performed. */
        public static String local(String key, Locale locale, String resource, ClassLoader classLoader) {
                ResourceBundle rb = ResourceBundle.getBundle(resource, locale, classLoader);
-               assert key.length() > 2;
-               if (isLocaleKey(key))
+               if (isLocaleKey(key)) {
+                       assert key.length() > 1;
                        key = key.substring(1);
+               }
                if (rb.containsKey(key))
                        return rb.getString(key);
                else // for simple cases, the key will actually be the English word
@@ -102,7 +97,7 @@ public class LocaleUtils {
 
        static Locale getCurrentLocale() {
                Locale currentLocale = null;
-               if (Subject.getSubject(AccessController.getContext()) != null)
+               if (CurrentUser.isAvailable())
                        currentLocale = CurrentUser.locale();
                else if (threadLocale.get() != null) {
                        currentLocale = threadLocale.get();
@@ -118,26 +113,4 @@ public class LocaleUtils {
                // return Locale.getDefault();
        }
 
-       /** Returns null if argument is null. */
-       public static List<Locale> asLocaleList(Object locales) {
-               if (locales == null)
-                       return null;
-               ArrayList<Locale> availableLocales = new ArrayList<Locale>();
-               String[] codes = locales.toString().split(",");
-               for (int i = 0; i < codes.length; i++) {
-                       String code = codes[i];
-                       // variant not supported
-                       int indexUnd = code.indexOf("_");
-                       Locale locale;
-                       if (indexUnd > 0) {
-                               String language = code.substring(0, indexUnd);
-                               String country = code.substring(indexUnd + 1);
-                               locale = new Locale(language, country);
-                       } else {
-                               locale = new Locale(code);
-                       }
-                       availableLocales.add(locale);
-               }
-               return availableLocales;
-       }
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java
new file mode 100644 (file)
index 0000000..04302c4
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.cms;
+
+import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.cms.directory.ldap.LdapNameUtils;
+
+/** Simplifies analysis of system roles. */
+public class RoleNameUtils {
+       public static String getLastRdnValue(String dn) {
+               return LdapNameUtils.getLastRdnValue(dn);
+//             // we don't use LdapName for portability with Android
+//             // TODO make it more robust
+//             String[] parts = dn.split(",");
+//             String[] rdn = parts[0].split("=");
+//             return rdn[1];
+       }
+
+       public static QName getLastRdnAsName(String dn) {
+               String cn = getLastRdnValue(dn);
+               QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
+               return roleName;
+       }
+
+       public static boolean isSystemRole(QName roleName) {
+               return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
+       }
+
+       public static String getParent(String dn) {
+               int index = dn.indexOf(',');
+               return dn.substring(index + 1);
+       }
+
+       /** Up two levels. */
+       public static String getContext(String dn) {
+               return getParent(getParent(dn));
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/SystemRole.java
new file mode 100644 (file)
index 0000000..9564399
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms;
+
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.internal.auth.ImpliedByPrincipal;
+
+/** A programmatic role. */
+public interface SystemRole {
+       QName qName();
+
+       /** Whether this role is implied for this authenticated user. */
+       default boolean implied(Subject subject, String context) {
+               return implied(qName(), subject, context);
+       }
+
+       /** Whether this role is implied for this distinguished name. */
+       default boolean implied(String dn, String context) {
+               String roleContext = RoleNameUtils.getContext(dn);
+               QName roleName = RoleNameUtils.getLastRdnAsName(dn);
+               return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
+       }
+
+       /**
+        * Whether this role is implied for this authenticated subject. If context is
+        * <code>null</code>, it is not considered; this should be used to build user
+        * interfaces, but not to authorise.
+        */
+       static boolean implied(QName name, Subject subject, String context) {
+               Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
+               for (ImpliedByPrincipal role : roles) {
+                       if (role.isSystemRole()) {
+                               if (role.getRoleName().equals(name)) {
+                                       // !! if context is not specified, it is considered irrelevant
+                                       if (context == null)
+                                               return true;
+                                       if (role.getContext().equalsIgnoreCase(context)
+                                                       || role.getContext().equals(CmsConstants.NODE_BASEDN))
+                                               return true;
+                               }
+                       }
+               }
+               return false;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContent.java
new file mode 100644 (file)
index 0000000..16f3960
--- /dev/null
@@ -0,0 +1,248 @@
+package org.argeo.cms.acr;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.util.LangUtils;
+
+/** Partial reference implementation of a {@link ProvidedContent}. */
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+       private final ProvidedSession session;
+
+       // cache
+//     private String _path = null;
+
+       public AbstractContent(ProvidedSession session) {
+               this.session = session;
+       }
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+//     protected abstract Iterable<QName> keys();
+//
+//     protected abstract void removeAttr(QName key);
+
+       @Override
+       public Set<Entry<QName, Object>> entrySet() {
+               Set<Entry<QName, Object>> result = new AttrSet();
+               return result;
+       }
+
+       @Override
+       public Class<?> getType(QName key) {
+               return String.class;
+       }
+
+       @Override
+       public boolean isMultiple(QName key) {
+               return false;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> List<A> getMultiple(QName key, Class<A> clss) {
+               Object value = get(key);
+               if (value == null)
+                       return new ArrayList<>();
+               if (value instanceof List) {
+                       if (isDefaultAttrTypeRequested(clss))
+                               return (List<A>) value;
+                       List<A> res = new ArrayList<>();
+                       List<?> lst = (List<?>) value;
+                       for (Object o : lst) {
+                               A item = clss.isAssignableFrom(String.class) ? (A) o.toString() : (A) o;
+                               res.add(item);
+                       }
+                       return res;
+               } else {// singleton
+//                     try {
+                       A res = (A) value;
+                       return Collections.singletonList(res);
+//                     } catch (ClassCastException e) {
+//                             return Optional.empty();
+//                     }
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public String getPath() {
+//             if (_path != null)
+//                     return _path;
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               StringBuilder path = new StringBuilder();
+               ancestors: for (Content c : ancestors) {
+                       QName name = c.getName();
+                       if (CrName.root.qName().equals(name))
+                               continue ancestors;
+
+                       path.append('/');
+                       path.append(NamespaceUtils.toPrefixedName(name));
+                       int siblingIndex = c.getSiblingIndex();
+                       if (siblingIndex != 1)
+                               path.append('[').append(siblingIndex).append(']');
+               }
+//             _path = path.toString();
+//             return _path;
+               return path.toString();
+       }
+
+       private void collectAncestors(List<Content> ancestors, Content content) {
+               if (content == null)
+                       return;
+               ancestors.add(0, content);
+               collectAncestors(ancestors, content.getParent());
+       }
+
+       @Override
+       public int getDepth() {
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               return ancestors.size();
+       }
+
+       @Override
+       public String getSessionLocalId() {
+               return getPath();
+       }
+
+       /*
+        * SESSION
+        */
+
+       @Override
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       /*
+        * TYPING
+        */
+
+       @Override
+       public List<QName> getContentClasses() {
+               return new ArrayList<>();
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class
+               return clss.isAssignableFrom(Object.class);
+       }
+
+//     @Override
+//     public String toString() {
+//             return "content " + getPath();
+//     }
+
+       /*
+        * DEFAULTS
+        */
+       // - no children
+       // - no attributes
+       // - cannot be modified
+       @Override
+       public Iterator<Content> iterator() {
+               return Collections.emptyIterator();
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               throw new UnsupportedOperationException("Content cannot be added.");
+       }
+
+       @Override
+       public void remove() {
+               throw new UnsupportedOperationException("Content cannot be removed.");
+       }
+
+       protected Iterable<QName> keys() {
+               return Collections.emptySet();
+       }
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               return null;
+       }
+
+       protected void removeAttr(QName key) {
+               throw new UnsupportedOperationException("Attributes cannot be removed.");
+       }
+
+       /*
+        * SUB CLASSES
+        */
+
+       class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+               @Override
+               public Iterator<Entry<QName, Object>> iterator() {
+                       final Iterator<QName> keys = keys().iterator();
+                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+                               QName key = null;
+
+                               @Override
+                               public boolean hasNext() {
+                                       return keys.hasNext();
+                               }
+
+                               @Override
+                               public Entry<QName, Object> next() {
+                                       key = keys.next();
+                                       // TODO check type
+                                       Optional<?> value = get(key, Object.class);
+                                       assert !value.isEmpty();
+                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+                                       return entry;
+                               }
+
+                               @Override
+                               public void remove() {
+                                       if (key != null) {
+                                               AbstractContent.this.removeAttr(key);
+                                       } else {
+                                               throw new IllegalStateException("Iteration has not started");
+                                       }
+                               }
+
+                       };
+                       return it;
+               }
+
+               @Override
+               public int size() {
+                       return LangUtils.size(keys());
+               }
+
+       }
+
+       /*
+        * OBJECT METHODS
+        */
+       @Override
+       public String toString() {
+               return "content " + getPath();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java
new file mode 100644 (file)
index 0000000..c1f1ef5
--- /dev/null
@@ -0,0 +1,228 @@
+package org.argeo.cms.acr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.acr.xml.DomContentProvider;
+import org.argeo.cms.acr.xml.DomUtils;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+/**
+ * Base implementation of a {@link ProvidedRepository}.
+ */
+public abstract class AbstractContentRepository implements ProvidedRepository {
+       private final static CmsLog log = CmsLog.getLog(AbstractContentRepository.class);
+
+       private MountManager mountManager;
+       private TypesManager typesManager;
+
+       private CmsContentSession systemSession;
+
+       private Set<ContentProvider> providersToAdd = new HashSet<>();
+
+       // utilities
+       /** Should be used only to copy source and results. */
+       private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance();
+
+//     public final static String ACR_MOUNT_PATH_PROPERTY = "acr.mount.path";
+
+       public AbstractContentRepository() {
+               long begin = System.currentTimeMillis();
+               // types
+               typesManager = new TypesManager();
+               typesManager.init();
+               Set<QName> types = typesManager.listTypes();
+               if (log.isTraceEnabled())
+                       for (QName type : types) {
+                               log.trace(type + " - " + typesManager.getAttributeTypes(type));
+                       }
+               long duration = System.currentTimeMillis() - begin;
+               log.debug(() -> "CMS content types available (initialisation took " + duration + " ms)");
+       }
+
+       protected abstract CmsContentSession newSystemSession();
+
+       public void start() {
+               systemSession = newSystemSession();
+               // mounts
+               mountManager = new MountManager(systemSession);
+       }
+
+       public void stop() {
+               systemSession.close();
+               systemSession = null;
+       }
+
+       /*
+        * REPOSITORY
+        */
+       @Override
+       public void addProvider(ContentProvider provider) {
+               if (mountManager == null) {
+                       providersToAdd.add(provider);
+                       log.debug(
+                                       () -> "Will add provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")");
+               } else {
+                       mountManager.addStructuralContentProvider(provider);
+                       log.debug(() -> "Added provider " + provider.getMountPath() + " (" + provider.getClass().getName() + ")");
+               }
+       }
+
+       @Override
+       public void registerTypes(ContentNamespace... namespaces) {
+               typesManager.registerTypes(namespaces);
+       }
+
+       /*
+        * FACTORIES
+        */
+       public void initRootContentProvider(Path path) {
+               try {
+//                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+//                     factory.setNamespaceAware(true);
+//                     factory.setXIncludeAware(true);
+//                     factory.setSchema(contentTypesManager.getSchema());
+//
+                       DocumentBuilder dBuilder = typesManager.newDocumentBuilder();
+
+                       Document document;
+//                     if (path != null && Files.exists(path)) {
+//                             InputSource inputSource = new InputSource(path.toAbsolutePath().toUri().toString());
+//                             inputSource.setEncoding(StandardCharsets.UTF_8.name());
+//                             // TODO public id as well?
+//                             document = dBuilder.parse(inputSource);
+//                     } else {
+                       document = dBuilder.newDocument();
+                       Element root = document.createElementNS(ArgeoNamespace.CR_NAMESPACE_URI,
+                                       NamespaceUtils.toPrefixedName(CrName.root.qName()));
+
+                       for (String prefix : RuntimeNamespaceContext.getPrefixes().keySet()) {
+//                             root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+//                                             contentTypesManager.getPrefixes().get(prefix));
+                               DomUtils.addNamespace(root, prefix,
+                                               RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix));
+                       }
+
+                       document.appendChild(root);
+
+                       // write it
+                       if (path != null) {
+                               try (OutputStream out = Files.newOutputStream(path)) {
+                                       writeDom(document, out);
+                               }
+                       }
+//                     }
+
+                       String mountPath = "/";
+                       DomContentProvider contentProvider = new DomContentProvider(mountPath, document);
+                       addProvider(contentProvider);
+               } catch (DOMException | IOException e) {
+                       throw new IllegalStateException("Cannot init ACR root " + path, e);
+               }
+
+               // add content providers already notified
+               for (ContentProvider contentProvider : providersToAdd)
+                       addProvider(contentProvider);
+               providersToAdd.clear();
+       }
+
+       public void writeDom(Document document, OutputStream out) throws IOException {
+               try {
+                       Transformer transformer = identityTransformerFactory.newTransformer();
+                       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+                       DOMSource source = new DOMSource(document);
+                       typesManager.validate(source);
+                       StreamResult result = new StreamResult(out);
+                       transformer.transform(source, result);
+               } catch (TransformerException e) {
+                       throw new IOException("Cannot write dom", e);
+               }
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+
+       @Override
+       public ContentProvider getMountContentProvider(Content mountPoint, boolean initialize, QName... types) {
+               String mountPath = mountPoint.getPath();
+               // TODO check consistency with types
+
+               return mountManager.getOrAddMountedProvider(mountPath, (path) -> {
+                       DocumentBuilder dBuilder = typesManager.newDocumentBuilder();
+                       Document document;
+                       if (initialize) {
+                               QName firstType = types[0];
+                               document = dBuilder.newDocument();
+                               String prefix = ((ProvidedContent) mountPoint).getSession().getPrefix(firstType.getNamespaceURI());
+                               Element root = document.createElementNS(firstType.getNamespaceURI(),
+                                               prefix + ":" + firstType.getLocalPart());
+                               DomUtils.addNamespace(root, prefix, firstType.getNamespaceURI());
+                               document.appendChild(root);
+                       } else {
+                               try (InputStream in = mountPoint.open(InputStream.class)) {
+                                       document = dBuilder.parse(in);
+                                       // TODO check consistency with types
+                               } catch (IOException | SAXException e) {
+                                       throw new IllegalStateException("Cannot load mount from " + mountPoint, e);
+                               }
+                       }
+                       DomContentProvider contentProvider = new DomContentProvider(path, document);
+                       return contentProvider;
+               });
+       }
+
+       @Override
+       public boolean shouldMount(QName... types) {
+               if (types.length == 0)
+                       return false;
+               QName firstType = types[0];
+               Set<QName> registeredTypes = typesManager.listTypes();
+               if (registeredTypes.contains(firstType))
+                       return true;
+               return false;
+       }
+
+       MountManager getMountManager() {
+               return mountManager;
+       }
+
+       TypesManager getTypesManager() {
+               return typesManager;
+       }
+
+       CmsContentSession getSystemSession() {
+               return systemSession;
+       }
+       
+       
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentNamespace.java
new file mode 100644 (file)
index 0000000..429b759
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.acr;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Objects;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.spi.ContentNamespace;
+
+/** Content namespaces supported by CMS. */
+public enum CmsContentNamespace implements ContentNamespace {
+       //
+       // ARGEO
+       //
+       CR(ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI, "cr.xsd", null),
+       //
+       SLC("slc", "http://www.argeo.org/ns/slc", null, null),
+       //
+       ARGEO("argeo", "http://www.argeo.org/ns/argeo", null, null),
+       //
+       // EXTERNAL
+       //
+       XSD("xs", "http://www.w3.org/2001/XMLSchema", "XMLSchema.xsd", "http://www.w3.org/2001/XMLSchema.xsd"),
+       //
+       XML("xml", "http://www.w3.org/XML/1998/namespace", "xml.xsd", "http://www.w3.org/2001/xml.xsd"),
+       //
+       XLINK("xlink", "http://www.w3.org/1999/xlink", "xlink.xsd", "https://www.w3.org/1999/xlink.xsd"),
+       //
+       WEBDAV("D", "DAV:", null, "https://raw.githubusercontent.com/lookfirst/sardine/master/webdav.xsd"),
+       //
+       XSLT("xsl", "http://www.w3.org/1999/XSL/Transform", "schema-for-xslt20.xsd",
+                       "https://www.w3.org/2007/schema-for-xslt20.xsd"),
+       //
+       SVG("svg", "http://www.w3.org/2000/svg", "SVG.xsd",
+                       "https://raw.githubusercontent.com/oreillymedia/HTMLBook/master/schema/svg/SVG.xsd"),
+       //
+       DSML("dsml", "urn:oasis:names:tc:DSML:2:0:core", "DSMLv2.xsd",
+                       "https://www.oasis-open.org/committees/dsml/docs/DSMLv2.xsd"),
+       //
+       ;
+
+       private final static String RESOURCE_BASE = "/org/argeo/cms/acr/schemas/";
+
+       private String defaultPrefix;
+       private String namespace;
+       private URL resource;
+       private URL publicUrl;
+
+       CmsContentNamespace(String defaultPrefix, String namespace, String resourceFileName, String publicUrl) {
+               Objects.requireNonNull(namespace);
+               this.defaultPrefix = defaultPrefix;
+               Objects.requireNonNull(namespace);
+               this.namespace = namespace;
+               if (resourceFileName != null) {
+                       resource = getClass().getResource(RESOURCE_BASE + resourceFileName);
+                       Objects.requireNonNull(resource);
+               }
+               if (publicUrl != null)
+                       try {
+                               this.publicUrl = new URL(publicUrl);
+                       } catch (MalformedURLException e) {
+                               throw new IllegalArgumentException("Cannot interpret public URL", e);
+                       }
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return defaultPrefix;
+       }
+
+       @Override
+       public String getNamespaceURI() {
+               return namespace;
+       }
+
+       @Override
+       public URL getSchemaResource() {
+               return resource;
+       }
+
+       public URL getPublicUrl() {
+               return publicUrl;
+       }
+
+}
index 9cd8bd22dd6e8eef8670a45fada48cf5a8037f5f..3b47c1630aa4f63a41a30d0909d2ace7b13350de 100644 (file)
@@ -1,43 +1,35 @@
 package org.argeo.cms.acr;
 
-import java.security.AccessController;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
 
-import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 
-import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
-import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedRepository;
-import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CurrentUser;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
 
-public class CmsContentRepository implements ProvidedRepository {
-       private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+/**
+ * Multi-session {@link ProvidedRepository}, integrated with a CMS.
+ */
+public class CmsContentRepository extends AbstractContentRepository {
+       public final static String RUN_BASE = "/run";
+       public final static String DIRECTORY_BASE = "/directory";
 
-       // TODO synchronize ?
-       private NavigableMap<String, String> prefixes = new TreeMap<>();
+       private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
 
-       public CmsContentRepository() {
-               prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
-               prefixes.put("basic", CrName.CR_NAMESPACE_URI);
-               prefixes.put("owner", CrName.CR_NAMESPACE_URI);
-               prefixes.put("posix", CrName.CR_NAMESPACE_URI);
-       }
-
-       public void start() {
-
-       }
-
-       public void stop() {
-
-       }
+       private CmsState cmsState;
+       private UuidFactory uuidFactory;
 
        /*
         * REPOSITORY
@@ -50,90 +42,52 @@ public class CmsContentRepository implements ProvidedRepository {
 
        @Override
        public ContentSession get(Locale locale) {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               return new CmsContentSession(subject, locale);
-       }
-
-       public void addProvider(String base, ContentProvider provider) {
-               partitions.put(base, provider);
-       }
-
-       public void registerPrefix(String prefix, String namespaceURI) {
-               String registeredUri = prefixes.get(prefix);
-               if (registeredUri == null) {
-                       prefixes.put(prefix, namespaceURI);
-                       return;
-               }
-               if (!registeredUri.equals(namespaceURI))
-                       throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
-               // do nothing if same namespace is already registered
-       }
-
-       /*
-        * NAMESPACE CONTEXT
-        */
-
-       /*
-        * SESSION
-        */
-
-       class CmsContentSession implements ProvidedSession {
-               private Subject subject;
-               private Locale locale;
-
-               public CmsContentSession(Subject subject, Locale locale) {
-                       this.subject = subject;
-                       this.locale = locale;
-               }
-
-               @Override
-               public Content get(String path) {
-                       Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
-                       String mountPath = entry.getKey();
-                       ContentProvider provider = entry.getValue();
-                       String relativePath = path.substring(mountPath.length());
-                       return provider.get(CmsContentSession.this, mountPath, relativePath);
-               }
-
-               @Override
-               public Subject getSubject() {
-                       return subject;
-               }
-
-               @Override
-               public Locale getLocale() {
-                       return locale;
+               if (!CmsSession.hasCmsSession(CurrentSubject.current())) {
+                       if (DataAdminPrincipal.isDataAdmin(CurrentSubject.current())) {
+                               // TODO open multiple data admin sessions?
+                               return getSystemSession();
+                       }
+                       throw new IllegalStateException("Caller must be authenticated");
                }
 
-               @Override
-               public ProvidedRepository getRepository() {
-                       return CmsContentRepository.this;
+               CmsSession cmsSession = CurrentUser.getCmsSession();
+               CmsContentSession contentSession = userSessions.get(cmsSession);
+               if (contentSession == null) {
+                       final CmsContentSession newContentSession = new CmsContentSession(this, cmsSession.getUuid(),
+                                       cmsSession.getSubject(), locale, uuidFactory);
+                       cmsSession.addOnCloseCallback((c) -> {
+                               newContentSession.close();
+                               userSessions.remove(cmsSession);
+                       });
+                       contentSession = newContentSession;
                }
+               return contentSession;
+       }
 
-               /*
-                * NAMESPACE CONTEXT
-                */
-
-               @Override
-               public String findNamespace(String prefix) {
-                       return prefixes.get(prefix);
+       @Override
+       protected CmsContentSession newSystemSession() {
+               LoginContext loginContext;
+               try {
+                       loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
                }
+               return new CmsContentSession(this, getCmsState().getUuid(), loginContext.getSubject(), Locale.getDefault(),
+                               uuidFactory);
+       }
 
-               @Override
-               public Set<String> findPrefixes(String namespaceURI) {
-                       Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
-                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
-
-                       return res;
-               }
+       protected CmsState getCmsState() {
+               return cmsState;
+       }
 
-               @Override
-               public String findPrefix(String namespaceURI) {
-                       if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
-                               return CrName.CR_DEFAULT_PREFIX;
-                       return ProvidedSession.super.findPrefix(namespaceURI);
-               }
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
 
+       public void setUuidFactory(UuidFactory uuidFactory) {
+               this.uuidFactory = uuidFactory;
        }
 
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java
new file mode 100644 (file)
index 0000000..17a03fd
--- /dev/null
@@ -0,0 +1,173 @@
+package org.argeo.cms.acr;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.xml.DomContentProvider;
+
+/** Implements {@link ProvidedSession}. */
+class CmsContentSession implements ProvidedSession {
+       final private AbstractContentRepository contentRepository;
+
+       private final UUID uuid;
+       private Subject subject;
+       private Locale locale;
+
+       private UuidFactory uuidFactory;
+
+       private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
+
+       private CompletableFuture<ContentSession> edition;
+
+       private Set<ContentProvider> modifiedProviders = new HashSet<>();
+
+       private Content sessionRunDir;
+
+       public CmsContentSession(AbstractContentRepository contentRepository, UUID uuid, Subject subject, Locale locale,
+                       UuidFactory uuidFactory) {
+               this.contentRepository = contentRepository;
+               this.subject = subject;
+               this.locale = locale;
+               this.uuid = uuid;
+               this.uuidFactory = uuidFactory;
+       }
+
+       public void close() {
+               closed.complete(this);
+
+               if (sessionRunDir != null)
+                       sessionRunDir.remove();
+       }
+
+       @Override
+       public CompletionStage<ProvidedSession> onClose() {
+               return closed.minimalCompletionStage();
+       }
+
+       @Override
+       public Content get(String path) {
+               if (!path.startsWith(ContentUtils.ROOT_SLASH))
+                       throw new IllegalArgumentException(path + " is not an absolute path");
+               ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
+               String mountPath = contentProvider.getMountPath();
+               String relativePath = extractRelativePath(mountPath, path);
+               ProvidedContent content = contentProvider.get(CmsContentSession.this, relativePath);
+               return content;
+       }
+
+       @Override
+       public boolean exists(String path) {
+               if (!path.startsWith(ContentUtils.ROOT_SLASH))
+                       throw new IllegalArgumentException(path + " is not an absolute path");
+               ContentProvider contentProvider = contentRepository.getMountManager().findContentProvider(path);
+               String mountPath = contentProvider.getMountPath();
+               String relativePath = extractRelativePath(mountPath, path);
+               return contentProvider.exists(this, relativePath);
+       }
+
+       private String extractRelativePath(String mountPath, String path) {
+               String relativePath = path.substring(mountPath.length());
+               if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
+                       relativePath = relativePath.substring(1);
+               return relativePath;
+       }
+
+       @Override
+       public Subject getSubject() {
+               return subject;
+       }
+
+       @Override
+       public Locale getLocale() {
+               return locale;
+       }
+
+       @Override
+       public ProvidedRepository getRepository() {
+               return contentRepository;
+       }
+
+       public UuidFactory getUuidFactory() {
+               return uuidFactory;
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+       @Override
+       public Content getMountPoint(String path) {
+               String[] parent = ContentUtils.getParentPath(path);
+               ProvidedContent mountParent = (ProvidedContent) get(parent[0]);
+//                     Content mountPoint = mountParent.getProvider().get(CmsContentSession.this, null, path);
+               return mountParent.getMountPoint(parent[1]);
+       }
+
+       /*
+        * EDITION
+        */
+       @Override
+       public CompletionStage<ContentSession> edit(Consumer<ContentSession> work) {
+               edition = CompletableFuture.supplyAsync(() -> {
+                       work.accept(this);
+                       return this;
+               }).thenApply((s) -> {
+                       synchronized (CmsContentSession.this) {
+                               // TODO optimise
+                               for (ContentProvider provider : modifiedProviders) {
+                                       if (provider instanceof DomContentProvider) {
+                                               ((DomContentProvider) provider).persist(s);
+                                       }
+                               }
+                               modifiedProviders.clear();
+                               return s;
+                       }
+               });
+               return edition.minimalCompletionStage();
+       }
+
+       @Override
+       public boolean isEditing() {
+               return edition != null && !edition.isDone();
+       }
+
+       @Override
+       public synchronized void notifyModification(ProvidedContent content) {
+               ContentProvider contentProvider = content.getProvider();
+               modifiedProviders.add(contentProvider);
+       }
+
+       @Override
+       public UUID getUuid() {
+               return uuid;
+       }
+
+//     @Override
+       public Content getSessionRunDir() {
+               if (sessionRunDir == null) {
+                       String runDirPath = CmsContentRepository.RUN_BASE + '/' + uuid.toString();
+                       if (exists(runDirPath))
+                               sessionRunDir = get(runDirPath);
+                       else {
+                               Content runDir = get(CmsContentRepository.RUN_BASE);
+                               // TODO deal with no run dir available?
+                               sessionRunDir = runDir.add(uuid.toString(), DName.collection.qName());
+                       }
+               }
+               return sessionRunDir;
+       }
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
new file mode 100644 (file)
index 0000000..d324ac4
--- /dev/null
@@ -0,0 +1,203 @@
+package org.argeo.cms.acr;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringJoiner;
+import java.util.function.BiConsumer;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.service.useradmin.Role;
+
+/** Utilities and routines around {@link Content}. */
+public class ContentUtils {
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
+               traverse(content, doIt, (Integer) null);
+       }
+
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, Integer maxDepth) {
+               doTraverse(content, doIt, 0, maxDepth);
+       }
+
+       private static void doTraverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth,
+                       Integer maxDepth) {
+               doIt.accept(content, currentDepth);
+               if (maxDepth != null && currentDepth == maxDepth)
+                       return;
+               int nextDepth = currentDepth + 1;
+               for (Content child : content) {
+                       doTraverse(child, doIt, nextDepth, maxDepth);
+               }
+       }
+
+       public static void print(Content content, PrintStream out, int depth, boolean printText) {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < depth; i++) {
+                       sb.append("  ");
+               }
+               String prefix = sb.toString();
+               out.println(prefix + content.getName());
+               for (QName key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
+               if (printText) {
+                       if (content.hasText()) {
+                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                       }
+               }
+       }
+
+//     public static <T> boolean isString(T t) {
+//             return t instanceof String;
+//     }
+
+       public static final char SLASH = '/';
+       public static final String SLASH_STRING = Character.toString(SLASH);
+       public static final String ROOT_SLASH = "" + SLASH;
+       public static final String EMPTY = "";
+
+       /**
+        * Split a path (with '/' separator) in an array of length 2, the first part
+        * being the parent path (which could be either absolute or relative), the
+        * second one being the last segment, (guaranteed to be without a '/').
+        */
+       public static String[] getParentPath(String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               if (path.length() == 0)
+                       throw new IllegalArgumentException("Path cannot be empty");
+               checkDoubleSlash(path);
+               int parentIndex = path.lastIndexOf(SLASH);
+               if (parentIndex == path.length() - 1) {// trailing '/'
+                       path = path.substring(0, path.length() - 1);
+                       parentIndex = path.lastIndexOf(SLASH);
+               }
+
+               if (parentIndex == -1) // no '/'
+                       return new String[] { EMPTY, path };
+
+               return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
+                               path.substring(parentIndex + 1) };
+       }
+
+       public static String toPath(List<String> segments) {
+               // TODO checks
+               StringJoiner sj = new StringJoiner("/");
+               segments.forEach((s) -> sj.add(s));
+               return sj.toString();
+       }
+
+       public static List<String> toPathSegments(String path) {
+               List<String> res = new ArrayList<>();
+               if (EMPTY.equals(path) || ROOT_SLASH.equals(path))
+                       return res;
+               collectPathSegments(path, res);
+               return res;
+       }
+
+       private static void collectPathSegments(String path, List<String> segments) {
+               String[] parent = getParentPath(path);
+               if (EMPTY.equals(parent[1])) // root
+                       return;
+               segments.add(0, parent[1]);
+               if (EMPTY.equals(parent[0])) // end
+                       return;
+               collectPathSegments(parent[0], segments);
+       }
+
+       public static void checkDoubleSlash(String path) {
+               if (path.contains(SLASH + "" + SLASH))
+                       throw new IllegalArgumentException("Path " + path + " contains //");
+       }
+
+       /*
+        * DIRECTORY
+        */
+
+       public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
+               UserDirectory userDirectory = userManager.getDirectory(role);
+               String path = directoryPath(userDirectory) + userDirectory.getRolePath(role);
+               Content content = contentSession.get(path);
+               return content;
+       }
+
+       public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) {
+               CmsDirectory directory = hierarchyUnit.getDirectory();
+               StringJoiner relativePath = new StringJoiner(SLASH_STRING);
+               buildHierarchyUnitPath(hierarchyUnit, relativePath);
+               String path = directoryPath(directory) + relativePath.toString();
+               Content content = contentSession.get(path);
+               return content;
+       }
+
+       /** The path to this {@link CmsDirectory}. Ends with a /. */
+       private static String directoryPath(CmsDirectory directory) {
+               return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH;
+       }
+
+       /** Recursively build a relative path of a {@link HierarchyUnit}. */
+       private static void buildHierarchyUnitPath(HierarchyUnit current, StringJoiner relativePath) {
+               if (current.getParent() == null) // directory
+                       return;
+               buildHierarchyUnitPath(current.getParent(), relativePath);
+               relativePath.add(current.getHierarchyUnitName());
+       }
+
+       /*
+        * CONSUMER UTILS
+        */
+
+       public static Content createCollections(ContentSession session, String path) {
+               if (session.exists(path)) {
+                       Content content = session.get(path);
+                       if (!content.isContentClass(DName.collection.qName())) {
+                               throw new IllegalStateException("Content " + path + " already exists, but is not a collection");
+                       } else {
+                               return content;
+                       }
+               } else {
+                       String[] parentPath = getParentPath(path);
+                       Content parent = createCollections(session, parentPath[0]);
+                       Content content = parent.add(parentPath[1], DName.collection.qName());
+                       return content;
+               }
+       }
+
+       public static ContentSession openDataAdminSession(ContentRepository repository) {
+               LoginContext loginContext;
+               try {
+                       loginContext = CmsAuth.DATA_ADMIN.newLoginContext();
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
+               }
+
+               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader());
+                       return CurrentSubject.callAs(loginContext.getSubject(), () -> repository.get());
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCl);
+               }
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java b/org.argeo.cms/src/org/argeo/cms/acr/MountManager.java
new file mode 100644 (file)
index 0000000..36b0cfe
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.acr;
+
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+
+/** Manages the structural and dynamic mounts within the content repository. */
+class MountManager {
+       private final NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+
+       private final CmsContentSession systemSession;
+
+       public MountManager(CmsContentSession systemSession) {
+               this.systemSession = systemSession;
+       }
+
+       synchronized void addStructuralContentProvider(ContentProvider contentProvider) {
+               String mountPath = contentProvider.getMountPath();
+               Objects.requireNonNull(mountPath);
+               if (partitions.containsKey(mountPath))
+                       throw new IllegalStateException("A provider is already registered for " + mountPath);
+               partitions.put(mountPath, contentProvider);
+               if ("/".equals(mountPath))// root
+                       return;
+               String[] parentPath = ContentUtils.getParentPath(mountPath);
+               Content parent = systemSession.get(parentPath[0]);
+               Content mount = parent.add(parentPath[1]);
+               mount.put(CrName.mount.qName(), "true");
+
+       }
+
+       synchronized ContentProvider getOrAddMountedProvider(String mountPath, Function<String, ContentProvider> factory) {
+               Objects.requireNonNull(factory);
+               if (!partitions.containsKey(mountPath)) {
+                       ContentProvider contentProvider = factory.apply(mountPath);
+                       if (!mountPath.equals(contentProvider.getMountPath()))
+                               throw new IllegalArgumentException("Mount path " + mountPath + " is inconsistent with content provider "
+                                               + contentProvider.getMountPath());
+                       partitions.put(mountPath, contentProvider);
+               }
+               return partitions.get(mountPath);
+       }
+
+       synchronized ContentProvider findContentProvider(String path) {
+//             if (ContentUtils.EMPTY.equals(path))
+//                     return partitions.firstEntry().getValue();
+               Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
+               if (entry == null)
+                       throw new IllegalArgumentException("No entry provider found for path '" + path + "'");
+               String mountPath = entry.getKey();
+               if (!path.startsWith(mountPath)) {
+                       // FIXME make it more robust and find when there is no content provider
+                       String[] parent = ContentUtils.getParentPath(path);
+                       return findContentProvider(parent[0]);
+                       // throw new IllegalArgumentException("Path " + path + " doesn't have a content
+                       // provider");
+               }
+               ContentProvider contentProvider = entry.getValue();
+               assert mountPath.equals(contentProvider.getMountPath());
+               return contentProvider;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/SingleUserContentRepository.java
new file mode 100644 (file)
index 0000000..b9b940f
--- /dev/null
@@ -0,0 +1,104 @@
+package org.argeo.cms.acr;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.uuid.MacAddressUuidFactory;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.fs.FsContentProvider;
+
+/**
+ * A standalone {@link ProvidedRepository} with a single {@link Subject} (which
+ * also provides the system session).
+ */
+public class SingleUserContentRepository extends AbstractContentRepository {
+       private final Subject subject;
+       private final Locale locale;
+
+       private UUID uuid;
+
+       private UuidFactory uuidFactory = new MacAddressUuidFactory();
+
+       // the single session
+       private CmsContentSession contentSession;
+
+       public SingleUserContentRepository(Subject subject) {
+               this(subject, Locale.getDefault());
+       }
+
+       public SingleUserContentRepository(Subject subject, Locale locale) {
+               Objects.requireNonNull(subject);
+               Objects.requireNonNull(locale);
+
+               this.subject = subject;
+               this.locale = locale;
+
+               // TODO use an UUID factory
+               this.uuid = UUID.randomUUID();
+       }
+
+       @Override
+       public void start() {
+               Objects.requireNonNull(subject);
+               Objects.requireNonNull(locale);
+
+               super.start();
+               initRootContentProvider(null);
+               if (contentSession != null)
+                       throw new IllegalStateException("Repository is already started, stop it first.");
+               contentSession = new CmsContentSession(this, uuid, subject, locale, uuidFactory);
+       }
+
+       @Override
+       public void stop() {
+               if (contentSession != null)
+                       contentSession.close();
+               contentSession = null;
+               super.stop();
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               if (!this.locale.equals(locale))
+                       throw new UnsupportedOperationException("This repository does not support multi-locale sessions");
+               return contentSession;
+       }
+
+       @Override
+       public ContentSession get() {
+               return contentSession;
+       }
+
+       @Override
+       protected CmsContentSession newSystemSession() {
+               return new CmsContentSession(this, uuid, subject, Locale.getDefault(), uuidFactory);
+       }
+
+       public static void main(String... args) {
+               Path homePath = Paths.get(System.getProperty("user.home"));
+               String username = System.getProperty("user.name");
+               X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + ",dc=localhost");
+               Subject subject = new Subject();
+               subject.getPrincipals().add(principal);
+
+               SingleUserContentRepository contentRepository = new SingleUserContentRepository(subject);
+               contentRepository.start();
+               FsContentProvider homeContentProvider = new FsContentProvider("/home", homePath);
+               contentRepository.addProvider(homeContentProvider);
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> contentRepository.stop(), "Shutdown content repository"));
+
+               ContentSession contentSession = contentRepository.get();
+               ContentUtils.traverse(contentSession.get("/"), (c, depth) -> ContentUtils.print(c, System.out, depth, false),
+                               2);
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java b/org.argeo.cms/src/org/argeo/cms/acr/SvgAttrs.java
new file mode 100644 (file)
index 0000000..61d8e04
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.cms.acr;
+
+import org.argeo.api.acr.QNamed;
+
+public enum SvgAttrs implements QNamed {
+       /** */
+       width,
+       /** */
+       height,
+       /** */
+       length,
+       /** */
+       unit,
+       /** */
+       dur,
+       /** */
+       direction,
+       //
+       ;
+
+       @Override
+       public String getNamespace() {
+               return CmsContentNamespace.SVG.getNamespaceURI();
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return CmsContentNamespace.SVG.getDefaultPrefix();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java
new file mode 100644 (file)
index 0000000..c3bea5b
--- /dev/null
@@ -0,0 +1,492 @@
+package org.argeo.cms.acr;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+
+import org.apache.xerces.impl.xs.XSImplementationImpl;
+import org.apache.xerces.impl.xs.util.StringListImpl;
+import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
+import org.apache.xerces.xs.StringList;
+import org.apache.xerces.xs.XSAttributeDeclaration;
+import org.apache.xerces.xs.XSAttributeUse;
+import org.apache.xerces.xs.XSComplexTypeDefinition;
+import org.apache.xerces.xs.XSConstants;
+import org.apache.xerces.xs.XSElementDeclaration;
+import org.apache.xerces.xs.XSException;
+import org.apache.xerces.xs.XSImplementation;
+import org.apache.xerces.xs.XSLoader;
+import org.apache.xerces.xs.XSModel;
+import org.apache.xerces.xs.XSModelGroup;
+import org.apache.xerces.xs.XSNamedMap;
+import org.apache.xerces.xs.XSObjectList;
+import org.apache.xerces.xs.XSParticle;
+import org.apache.xerces.xs.XSSimpleTypeDefinition;
+import org.apache.xerces.xs.XSTerm;
+import org.apache.xerces.xs.XSTypeDefinition;
+import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentNamespace;
+import org.argeo.api.cms.CmsLog;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/** Register content types. */
+class TypesManager {
+       private final static CmsLog log = CmsLog.getLog(TypesManager.class);
+//     private Map<String, String> prefixes = new TreeMap<>();
+
+       // immutable factories
+       private SchemaFactory schemaFactory;
+
+       /** Schema sources. */
+       private List<URL> sources = new ArrayList<>();
+
+       // cached
+       private Schema schema;
+       private DocumentBuilderFactory documentBuilderFactory;
+       private XSModel xsModel;
+       private SortedMap<QName, Map<QName, CrAttributeType>> types;
+
+       private boolean validating = false;
+       private boolean creatingXsModel = false;
+
+       private final static boolean limited = false;
+
+       public TypesManager() {
+               schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+               // types
+               types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR);
+
+       }
+
+       public void init() {
+               registerTypes(CmsContentNamespace.values());
+       }
+
+       public void registerTypes(ContentNamespace... namespaces) {
+//             if (prefixes.containsKey(defaultPrefix))
+//                     throw new IllegalStateException(
+//                                     "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix));
+//             prefixes.put(defaultPrefix, namespace);
+               for (ContentNamespace contentNamespace : namespaces) {
+                       RuntimeNamespaceContext.register(contentNamespace.getNamespaceURI(), contentNamespace.getDefaultPrefix());
+
+                       if (contentNamespace.getSchemaResource() != null) {
+                               sources.add(contentNamespace.getSchemaResource());
+                               log.debug(() -> "Registered types " + contentNamespace.getNamespaceURI() + " from "
+                                               + contentNamespace.getSchemaResource().toExternalForm());
+                       }
+               }
+               reload();
+       }
+
+       public Set<QName> listTypes() {
+               return types.keySet();
+       }
+
+       public Map<QName, CrAttributeType> getAttributeTypes(QName type) {
+               if (!types.containsKey(type))
+                       throw new IllegalArgumentException("Unkown type");
+               return types.get(type);
+       }
+
+       private synchronized void reload() {
+               try {
+                       // schema
+                       if (validating) {
+                               List<StreamSource> sourcesToUse = new ArrayList<>();
+                               for (URL sourceUrl : sources) {
+                                       sourcesToUse.add(new StreamSource(sourceUrl.toExternalForm()));
+                               }
+                               schema = schemaFactory.newSchema(sourcesToUse.toArray(new Source[sourcesToUse.size()]));
+//                             for (StreamSource source : sourcesToUse) {
+//                                     try {
+//                                             source.getInputStream().close();
+//                                     } catch (IOException e) {
+//                                             // TODO Auto-generated catch block
+//                                             e.printStackTrace();
+//                                     }
+//                             }
+                       }
+
+                       // document builder factory
+                       // we force usage of Xerces for predictability
+                       documentBuilderFactory = limited ? DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
+                       documentBuilderFactory.setNamespaceAware(true);
+                       if (!limited) {
+                               documentBuilderFactory.setXIncludeAware(true);
+                               if (validating) {
+                                       documentBuilderFactory.setSchema(getSchema());
+                                       documentBuilderFactory.setValidating(validating);
+                               }
+                       }
+
+                       if (creatingXsModel) {
+                               // XS model
+                               // TODO use JVM implementation?
+//                     DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
+//                     XSImplementation implementation = (XSImplementation) registry.getDOMImplementation("XS-Loader");
+                               XSImplementation xsImplementation = new XSImplementationImpl();
+                               XSLoader xsLoader = xsImplementation.createXSLoader(null);
+                               List<String> systemIds = new ArrayList<>();
+                               for (URL sourceUrl : sources) {
+                                       systemIds.add(sourceUrl.toExternalForm());
+                               }
+                               StringList sl = new StringListImpl(systemIds.toArray(new String[systemIds.size()]), systemIds.size());
+                               xsModel = xsLoader.loadURIList(sl);
+
+                               // types
+//                     XSNamedMap map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+//                     for (int i = 0; i < map.getLength(); i++) {
+//                             XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+//                             QName type = new QName(eDec.getNamespace(), eDec.getName());
+//                             types.add(type);
+//                     }
+                               collectTypes();
+                               
+                               log.debug("Created XS model");
+                       }
+               } catch (XSException | SAXException e) {
+                       throw new IllegalStateException("Cannot reload types", e);
+               }
+       }
+
+       private void collectTypes() {
+               types.clear();
+               // elements
+               XSNamedMap topLevelElements = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+               for (int i = 0; i < topLevelElements.getLength(); i++) {
+                       XSElementDeclaration eDec = (XSElementDeclaration) topLevelElements.item(i);
+                       collectElementDeclaration("", eDec);
+               }
+
+               // types
+               XSNamedMap topLevelTypes = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
+               for (int i = 0; i < topLevelTypes.getLength(); i++) {
+                       XSTypeDefinition tDef = (XSTypeDefinition) topLevelTypes.item(i);
+                       collectType(tDef, null, null);
+               }
+
+       }
+
+       private void collectType(XSTypeDefinition tDef, String namespace, String nameHint) {
+               if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
+                       XSComplexTypeDefinition ctDef = (XSComplexTypeDefinition) tDef;
+                       if (ctDef.getContentType() != XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
+                                       || ctDef.getAttributeUses().getLength() > 0 || ctDef.getAttributeWildcard() != null) {
+                               collectComplexType("", null, ctDef);
+                       } else {
+                               throw new IllegalArgumentException("Unsupported type " + tDef.getTypeCategory());
+                       }
+               }
+       }
+
+       private void collectComplexType(String prefix, QName parent, XSComplexTypeDefinition ctDef) {
+               if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE) {
+
+                       // content with attributes and a string value
+
+                       XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
+                       // QName name = new QName(stDef.getNamespace(), stDef.getName());
+                       // log.warn(prefix + "Simple " + ctDef + " - " + attributes);
+//                     System.err.println(prefix + "Simple from " + parent + " - " + attributes);
+//
+//                     if (parentAttributes != null) {
+//                             for (QName attr : attributes.keySet()) {
+//                                     if (!parentAttributes.containsKey(attr))
+//                                             System.err.println(prefix + " - " + attr + " not available in parent");
+//
+//                             }
+//                     }
+
+               } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_ELEMENT
+                               || ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED) {
+                       XSParticle p = ctDef.getParticle();
+
+                       collectParticle(prefix, p, false);
+               } else if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_EMPTY) {
+                       // Parent only contains attributes
+//                     if (parent != null)
+//                             System.err.println(prefix + "Empty from " + parent + " - " + attributes);
+//                     if (parentAttributes != null) {
+//                             for (QName attr : attributes.keySet()) {
+//                                     if (!parentAttributes.containsKey(attr))
+//                                             System.err.println(prefix + " - " + attr + " not available in parent");
+//
+//                             }
+//                     }
+//                     log.debug(prefix + "Empty " + ctDef.getNamespace() + ":" + ctDef.getName() + " - " + attributes);
+               } else {
+                       throw new IllegalArgumentException("Unsupported type " + ctDef.getTypeCategory());
+               }
+       }
+
+       private void collectParticle(String prefix, XSParticle particle, boolean multipleFromAbove) {
+               boolean orderable = false;
+
+               XSTerm term = particle.getTerm();
+
+               if (particle.getMaxOccurs() == 0) {
+                       return;
+               }
+
+               boolean mandatory = false;
+               if (particle.getMinOccurs() > 0) {
+                       mandatory = true;
+               }
+
+               boolean multiple = false;
+               if (particle.getMaxOccurs() > 1 || particle.getMaxOccursUnbounded()) {
+                       multiple = true;
+               }
+               if (!multiple && multipleFromAbove)
+                       multiple = true;
+
+               if (term.getType() == XSConstants.ELEMENT_DECLARATION) {
+                       XSElementDeclaration eDec = (XSElementDeclaration) term;
+
+                       collectElementDeclaration(prefix, eDec);
+                       // If this particle is a wildcard (an <xs:any> )then it
+                       // is converted into a node def.
+               } else if (term.getType() == XSConstants.WILDCARD) {
+                       // TODO can be anything
+
+                       // If this particle is a model group (one of
+                       // <xs:sequence>, <xs:choice> or <xs:all>) then
+                       // it subparticles must be processed.
+               } else if (term.getType() == XSConstants.MODEL_GROUP) {
+                       XSModelGroup mg = (XSModelGroup) term;
+
+                       if (mg.getCompositor() == XSModelGroup.COMPOSITOR_SEQUENCE) {
+                               orderable = true;
+                       }
+                       XSObjectList list = mg.getParticles();
+                       for (int i = 0; i < list.getLength(); i++) {
+                               XSParticle pp = (XSParticle) list.item(i);
+                               collectParticle(prefix + "  ", pp, multiple);
+                       }
+               }
+       }
+
+       private void collectElementDeclaration(String prefix, XSElementDeclaration eDec) {
+               QName name = new QName(eDec.getNamespace(), eDec.getName());
+               XSTypeDefinition tDef = eDec.getTypeDefinition();
+
+               XSComplexTypeDefinition ctDef = null;
+               Map<QName, CrAttributeType> attributes = new HashMap<>();
+               if (tDef.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) {
+                       XSSimpleTypeDefinition stDef = (XSSimpleTypeDefinition) tDef;
+//                     System.err.println(prefix + "Simple element " + name);
+               } else if (tDef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) {
+                       ctDef = (XSComplexTypeDefinition) tDef;
+                       if (ctDef.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_SIMPLE
+                                       && ctDef.getAttributeUses().getLength() == 0 && ctDef.getAttributeWildcard() == null) {
+                               XSSimpleTypeDefinition stDef = ctDef.getSimpleType();
+//                             System.err.println(prefix + "Simplified element " + name);
+                       } else {
+                               if (!types.containsKey(name)) {
+//                                     System.out.println(prefix + "Element " + name);
+
+                                       XSObjectList list = ctDef.getAttributeUses();
+                                       for (int i = 0; i < list.getLength(); i++) {
+                                               XSAttributeUse au = (XSAttributeUse) list.item(i);
+                                               XSAttributeDeclaration ad = au.getAttrDeclaration();
+                                               QName attrName = new QName(ad.getNamespace(), ad.getName());
+                                               // Get the simple type def for this attribute
+                                               XSSimpleTypeDefinition std = ad.getTypeDefinition();
+                                               attributes.put(attrName, xsToCrType(std.getBuiltInKind()));
+//                                             System.out.println(prefix + " - " + attrName + " = " + attributes.get(attrName));
+                                       }
+                                       // REGISTER
+                                       types.put(name, attributes);
+                                       if (ctDef != null)
+                                               collectComplexType(prefix + " ", name, ctDef);
+                               }
+                       }
+               }
+
+       }
+
+       private CrAttributeType xsToCrType(short kind) {
+               CrAttributeType propertyType;
+               switch (kind) {
+               case XSConstants.ANYSIMPLETYPE_DT:
+               case XSConstants.STRING_DT:
+               case XSConstants.ID_DT:
+               case XSConstants.ENTITY_DT:
+               case XSConstants.NOTATION_DT:
+               case XSConstants.NORMALIZEDSTRING_DT:
+               case XSConstants.TOKEN_DT:
+               case XSConstants.LANGUAGE_DT:
+               case XSConstants.NMTOKEN_DT:
+                       propertyType = CrAttributeType.STRING;
+                       break;
+               case XSConstants.BOOLEAN_DT:
+                       propertyType = CrAttributeType.BOOLEAN;
+                       break;
+               case XSConstants.DECIMAL_DT:
+               case XSConstants.FLOAT_DT:
+               case XSConstants.DOUBLE_DT:
+                       propertyType = CrAttributeType.DOUBLE;
+                       break;
+               case XSConstants.DURATION_DT:
+               case XSConstants.DATETIME_DT:
+               case XSConstants.TIME_DT:
+               case XSConstants.DATE_DT:
+               case XSConstants.GYEARMONTH_DT:
+               case XSConstants.GYEAR_DT:
+               case XSConstants.GMONTHDAY_DT:
+               case XSConstants.GDAY_DT:
+               case XSConstants.GMONTH_DT:
+                       propertyType = CrAttributeType.DATE_TIME;
+                       break;
+               case XSConstants.HEXBINARY_DT:
+               case XSConstants.BASE64BINARY_DT:
+               case XSConstants.ANYURI_DT:
+                       propertyType = CrAttributeType.ANY_URI;
+                       break;
+               case XSConstants.QNAME_DT:
+               case XSConstants.NAME_DT:
+               case XSConstants.NCNAME_DT:
+                       // TODO support QName?
+                       propertyType = CrAttributeType.STRING;
+                       break;
+               case XSConstants.IDREF_DT:
+                       // TODO support references?
+                       propertyType = CrAttributeType.STRING;
+                       break;
+               case XSConstants.INTEGER_DT:
+               case XSConstants.NONPOSITIVEINTEGER_DT:
+               case XSConstants.NEGATIVEINTEGER_DT:
+               case XSConstants.LONG_DT:
+               case XSConstants.INT_DT:
+               case XSConstants.SHORT_DT:
+               case XSConstants.BYTE_DT:
+               case XSConstants.NONNEGATIVEINTEGER_DT:
+               case XSConstants.UNSIGNEDLONG_DT:
+               case XSConstants.UNSIGNEDINT_DT:
+               case XSConstants.UNSIGNEDSHORT_DT:
+               case XSConstants.UNSIGNEDBYTE_DT:
+               case XSConstants.POSITIVEINTEGER_DT:
+                       propertyType = CrAttributeType.LONG;
+                       break;
+               case XSConstants.LISTOFUNION_DT:
+               case XSConstants.LIST_DT:
+               case XSConstants.UNAVAILABLE_DT:
+                       propertyType = CrAttributeType.STRING;
+                       break;
+               default:
+                       propertyType = CrAttributeType.STRING;
+                       break;
+               }
+               return propertyType;
+       }
+
+       public DocumentBuilder newDocumentBuilder() {
+               try {
+                       DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
+                       dBuilder.setErrorHandler(new ErrorHandler() {
+
+                               @Override
+                               public void warning(SAXParseException exception) throws SAXException {
+                                       log.warn(exception);
+                               }
+
+                               @Override
+                               public void fatalError(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+
+                               @Override
+                               public void error(SAXParseException exception) throws SAXException {
+                                       log.error(exception);
+                               }
+                       });
+                       return dBuilder;
+               } catch (ParserConfigurationException e) {
+                       throw new IllegalStateException("Cannot create document builder", e);
+               }
+       }
+
+       public void printTypes() {
+               if (xsModel != null)
+                       try {
+
+                               // Convert top level complex type definitions to node types
+                               log.debug("\n## TYPES");
+                               XSNamedMap map = xsModel.getComponents(XSConstants.TYPE_DEFINITION);
+                               for (int i = 0; i < map.getLength(); i++) {
+                                       XSTypeDefinition tDef = (XSTypeDefinition) map.item(i);
+                                       log.debug(tDef);
+                               }
+                               // Convert local (anonymous) complex type defs found in top level
+                               // element declarations
+                               log.debug("\n## ELEMENTS");
+                               map = xsModel.getComponents(XSConstants.ELEMENT_DECLARATION);
+                               for (int i = 0; i < map.getLength(); i++) {
+                                       XSElementDeclaration eDec = (XSElementDeclaration) map.item(i);
+                                       XSTypeDefinition tDef = eDec.getTypeDefinition();
+                                       log.debug(eDec + ", " + tDef);
+                               }
+                               log.debug("\n## ATTRIBUTES");
+                               map = xsModel.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
+                               for (int i = 0; i < map.getLength(); i++) {
+                                       XSAttributeDeclaration eDec = (XSAttributeDeclaration) map.item(i);
+                                       XSTypeDefinition tDef = eDec.getTypeDefinition();
+                                       log.debug(eDec.getNamespace() + ":" + eDec.getName() + ", " + tDef);
+                               }
+                       } catch (ClassCastException | XSException e) {
+                               throw new RuntimeException(e);
+                       }
+
+       }
+
+       public void validate(Source source) throws IOException {
+               if (!validating)
+                       return;
+               Validator validator;
+               synchronized (this) {
+                       validator = schema.newValidator();
+               }
+               try {
+                       validator.validate(source);
+               } catch (SAXException e) {
+                       log.error(source + " is not valid " + e);
+                       // throw new IllegalArgumentException("Provided source is not valid", e);
+               }
+       }
+
+//     public Map<String, String> getPrefixes() {
+//             return prefixes;
+//     }
+
+//     public List<Source> getSources() {
+//             return sources;
+//     }
+
+       public Schema getSchema() {
+               return schema;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java
new file mode 100644 (file)
index 0000000..96e6eea
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.cms.acr.dav;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavContent extends AbstractContent {
+       private final DavContentProvider provider;
+       private final URI uri;
+
+       private Set<QName> keyNames;
+       private Optional<Map<QName, String>> values;
+
+       public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set<QName> keyNames) {
+               this(session, provider, uri, keyNames, Optional.empty());
+       }
+
+       public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set<QName> keyNames,
+                       Optional<Map<QName, String>> values) {
+               super(session);
+               this.provider = provider;
+               this.uri = uri;
+               this.keyNames = keyNames;
+               this.values = values;
+       }
+
+       @Override
+       public QName getName() {
+               String fileName = ContentUtils.getParentPath(uri.getPath())[1];
+               ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName);
+               return name;
+       }
+
+       @Override
+       public Content getParent() {
+               try {
+                       String parentPath = ContentUtils.getParentPath(uri.getPath())[0];
+                       URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null);
+                       return provider.getDavContent(getSession(), parentUri);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot create parent", e);
+               }
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               Iterator<DavResponse> responses = provider.getDavClient().listChildren(uri);
+               return new DavResponseIterator(responses);
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               return keyNames;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               if (values.isEmpty()) {
+                       DavResponse response = provider.getDavClient().get(uri);
+                       values = Optional.of(response.getProperties());
+               }
+               String valueStr = values.get().get(key);
+               if (valueStr == null)
+                       return Optional.empty();
+               // TODO convert
+               return Optional.of((A) valueStr);
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+       class DavResponseIterator implements Iterator<Content> {
+               private final Iterator<DavResponse> responses;
+
+               public DavResponseIterator(Iterator<DavResponse> responses) {
+                       this.responses = responses;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return responses.hasNext();
+               }
+
+               @Override
+               public Content next() {
+                       DavResponse response = responses.next();
+                       String relativePath = response.getHref();
+                       URI contentUri = provider.relativePathToUri(relativePath);
+                       return new DavContent(getSession(), provider, contentUri, response.getPropertyNames(HttpStatus.OK));
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java
new file mode 100644 (file)
index 0000000..374fceb
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.cms.acr.dav;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.dav.DavClient;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavContentProvider implements ContentProvider {
+       private String mountPath;
+       private URI baseUri;
+
+       private DavClient davClient;
+
+       public DavContentProvider(String mountPath, URI baseUri) {
+               this.mountPath = mountPath;
+               this.baseUri = baseUri;
+               if (!baseUri.getPath().endsWith("/"))
+                       throw new IllegalArgumentException("Base URI " + baseUri + " path does not end with /");
+               this.davClient = new DavClient();
+       }
+
+       @Override
+       public String getNamespaceURI(String prefix) {
+               // FIXME retrieve mappings from WebDav
+               return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix);
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               // FIXME retrieve mappings from WebDav
+               return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI);
+       }
+
+       @Override
+       public ProvidedContent get(ProvidedSession session, String relativePath) {
+               URI contentUri = relativePathToUri(relativePath);
+               return getDavContent(session, contentUri);
+       }
+
+       DavContent getDavContent(ProvidedSession session, URI uri) {
+               DavResponse response = davClient.get(uri);
+               return new DavContent(session, this, uri, response.getPropertyNames(HttpStatus.OK));
+       }
+
+       @Override
+       public boolean exists(ProvidedSession session, String relativePath) {
+               URI contentUri = relativePathToUri(relativePath);
+               return davClient.exists(contentUri);
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
+       DavClient getDavClient() {
+               return davClient;
+       }
+
+       URI relativePathToUri(String relativePath) {
+               try {
+                       // TODO check last slash
+                       String path = relativePath.startsWith("/") ? relativePath : baseUri.getPath() + relativePath;
+                       URI uri = new URI(baseUri.getScheme(), baseUri.getHost(), path, baseUri.getFragment());
+                       return uri;
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot build URI for " + relativePath + " relatively to " + baseUri, e);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/AbstractDirectoryContent.java
new file mode 100644 (file)
index 0000000..b737b50
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+
+abstract class AbstractDirectoryContent extends AbstractContent {
+       protected final DirectoryContentProvider provider;
+
+       public AbstractDirectoryContent(ProvidedSession session, DirectoryContentProvider provider) {
+               super(session);
+               this.provider = provider;
+       }
+
+       abstract Dictionary<String, Object> doGetProperties();
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               String attrName = key.getLocalPart();
+               Object value = doGetProperties().get(attrName);
+               if (value == null)
+                       return Optional.empty();
+               Optional<A> res = CrAttributeType.cast(clss, value);
+               if (res.isEmpty())
+                       return Optional.of((A) value);
+               else
+                       return res;
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               Dictionary<String, Object> properties = doGetProperties();
+               Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
+               keys: for (Enumeration<String> it = properties.keys(); it.hasMoreElements();) {
+                       String key = it.nextElement();
+                       if (key.equalsIgnoreCase(LdapAttr.objectClass.name()))
+                               continue keys;
+                       if (key.equalsIgnoreCase(LdapAttr.objectClasses.name()))
+                               continue keys;
+                       ContentName name = new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, key, provider);
+                       keys.add(name);
+               }
+               return keys;
+       }
+
+       @Override
+       public List<QName> getContentClasses() {
+               Dictionary<String, Object> properties = doGetProperties();
+               List<QName> contentClasses = new ArrayList<>();
+               String objectClass = properties.get(LdapAttr.objectClass.name()).toString();
+               contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, objectClass, provider));
+
+               String[] objectClasses = properties.get(LdapAttr.objectClasses.name()).toString().split("\\n");
+               objectClasses: for (String oc : objectClasses) {
+                       if (LdapObj.top.name().equalsIgnoreCase(oc))
+                               continue objectClasses;
+                       if (objectClass.equalsIgnoreCase(oc))
+                               continue objectClasses;
+                       contentClasses.add(new ContentName(ArgeoNamespace.LDAP_NAMESPACE_URI, oc, provider));
+               }
+               return contentClasses;
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               provider.getUserManager().edit(() -> doGetProperties().put(key.getLocalPart(), value));
+               return previous;
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               doGetProperties().remove(key.getLocalPart());
+       }
+
+       @Override
+       public ContentProvider getProvider() {
+               return provider;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java
new file mode 100644 (file)
index 0000000..6e39280
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+class DirectoryContent extends AbstractDirectoryContent {
+       private CmsDirectory directory;
+
+       public DirectoryContent(ProvidedSession session, DirectoryContentProvider provider, CmsDirectory directory) {
+               super(session, provider);
+               this.directory = directory;
+       }
+
+       @Override
+       Dictionary<String, Object> doGetProperties() {
+               return directory.getProperties();
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               List<Content> res = new ArrayList<>();
+               for (Iterator<HierarchyUnit> it = directory.getDirectHierarchyUnits(false).iterator(); it.hasNext();) {
+                       res.add(new HierarchyUnitContent(getSession(), provider, it.next()));
+               }
+               return res.iterator();
+       }
+
+       @Override
+       public QName getName() {
+               return new ContentName(directory.getName());
+       }
+
+       @Override
+       public Content getParent() {
+               return provider.getRootContent(getSession());
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> A adapt(Class<A> clss) {
+               if (clss.equals(HierarchyUnit.class))
+                       return (A) directory;
+               if (clss.equals(CmsDirectory.class))
+                       return (A) directory;
+               return super.adapt(clss);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContentProvider.java
new file mode 100644 (file)
index 0000000..8b6eb6b
--- /dev/null
@@ -0,0 +1,155 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.osgi.service.useradmin.User;
+
+public class DirectoryContentProvider implements ContentProvider {
+       private String mountPath;
+       private String mountName;
+
+       private CmsUserManager userManager;
+
+       public DirectoryContentProvider(String mountPath, CmsUserManager userManager) {
+               this.mountPath = mountPath;
+               List<String> mountSegments = ContentUtils.toPathSegments(mountPath);
+               this.mountName = mountSegments.get(mountSegments.size() - 1);
+               this.userManager = userManager;
+       }
+
+       @Override
+       public ProvidedContent get(ProvidedSession session, String relativePath) {
+               List<String> segments = ContentUtils.toPathSegments(relativePath);
+               if (segments.size() == 0)
+                       return new UserManagerContent(session);
+               String userDirectoryName = segments.get(0);
+               UserDirectory userDirectory = null;
+               userDirectories: for (UserDirectory ud : userManager.getUserDirectories()) {
+                       if (userDirectoryName.equals(ud.getName())) {
+                               userDirectory = ud;
+                               break userDirectories;
+                       }
+               }
+               if (userDirectory == null)
+                       throw new ContentNotFoundException(session, mountPath + "/" + relativePath,
+                                       "Cannot find user directory " + userDirectoryName);
+               if (segments.size() == 1) {
+                       return new DirectoryContent(session, this, userDirectory);
+               } else {
+                       List<String> relSegments = new ArrayList<>(segments);
+                       relSegments.remove(0);
+                       String pathWithinUserDirectory = ContentUtils.toPath(relSegments);
+//                     LdapName dn;
+//                     try {
+//                             dn = LdapNameUtils.toLdapName(userDirectoryDn);
+//                             for (int i = 1; i < segments.size(); i++) {
+//                                     dn.add(segments.get(i));
+//                             }
+//                     } catch (InvalidNameException e) {
+//                             throw new IllegalStateException("Cannot interpret " + segments + " as DN", e);
+//                     }
+                       User user = (User) userDirectory.getRoleByPath(pathWithinUserDirectory);
+                       if (user != null) {
+                               HierarchyUnit parent = userDirectory.getHierarchyUnit(user);
+                               return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user);
+                       }
+                       HierarchyUnit hierarchyUnit = userDirectory.getHierarchyUnit(pathWithinUserDirectory);
+                       if (hierarchyUnit == null)
+                               throw new ContentNotFoundException(session,
+                                               mountPath + "/" + relativePath + "/" + pathWithinUserDirectory,
+                                               "Cannot find " + pathWithinUserDirectory + " within " + userDirectoryName);
+                       return new HierarchyUnitContent(session, this, hierarchyUnit);
+               }
+       }
+
+       @Override
+       public boolean exists(ProvidedSession session, String relativePath) {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
+       @Override
+       public String getNamespaceURI(String prefix) {
+               if (ArgeoNamespace.LDAP_DEFAULT_PREFIX.equals(prefix))
+                       return ArgeoNamespace.LDAP_NAMESPACE_URI;
+               throw new IllegalArgumentException("Only prefix " + ArgeoNamespace.LDAP_DEFAULT_PREFIX + " is supported");
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               if (ArgeoNamespace.LDAP_NAMESPACE_URI.equals(namespaceURI))
+                       return Collections.singletonList(ArgeoNamespace.LDAP_DEFAULT_PREFIX).iterator();
+               throw new IllegalArgumentException("Only namespace URI " + ArgeoNamespace.LDAP_NAMESPACE_URI + " is supported");
+       }
+
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+
+       public CmsUserManager getUserManager() {
+               return userManager;
+       }
+
+       UserManagerContent getRootContent(ProvidedSession session) {
+               return new UserManagerContent(session);
+       }
+
+       /*
+        * COMMON UTILITIES
+        */
+       class UserManagerContent extends AbstractContent {
+
+               public UserManagerContent(ProvidedSession session) {
+                       super(session);
+               }
+
+               @Override
+               public ContentProvider getProvider() {
+                       return DirectoryContentProvider.this;
+               }
+
+               @Override
+               public QName getName() {
+                       return new ContentName(mountName);
+               }
+
+               @Override
+               public Content getParent() {
+                       return null;
+               }
+
+               @Override
+               public Iterator<Content> iterator() {
+                       List<Content> res = new ArrayList<>();
+                       for (UserDirectory userDirectory : userManager.getUserDirectories()) {
+                               DirectoryContent content = new DirectoryContent(getSession(), DirectoryContentProvider.this,
+                                               userDirectory);
+                               res.add(content);
+                       }
+                       return res.iterator();
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java
new file mode 100644 (file)
index 0000000..5acf8ab
--- /dev/null
@@ -0,0 +1,92 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.osgi.service.useradmin.Role;
+
+class HierarchyUnitContent extends AbstractDirectoryContent {
+       private HierarchyUnit hierarchyUnit;
+
+       public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider,
+                       HierarchyUnit hierarchyUnit) {
+               super(session, provider);
+               Objects.requireNonNull(hierarchyUnit);
+               this.hierarchyUnit = hierarchyUnit;
+       }
+
+       @Override
+       Dictionary<String, Object> doGetProperties() {
+               return hierarchyUnit.getProperties();
+       }
+
+       @Override
+       public QName getName() {
+//             if (hierarchyUnit.getParent() == null) {// base DN
+//                     String baseDn = hierarchyUnit.getBasePath();
+//                     return new ContentName(baseDn);
+//             }
+               String name = hierarchyUnit.getHierarchyUnitName();
+               return new ContentName(name);
+       }
+
+       @Override
+       public Content getParent() {
+               HierarchyUnit parentHu = hierarchyUnit.getParent();
+               if (parentHu instanceof CmsDirectory) {
+                       return new DirectoryContent(getSession(), provider, hierarchyUnit.getDirectory());
+               }
+               return new HierarchyUnitContent(getSession(), provider, parentHu);
+       }
+
+       @Override
+       public Iterator<Content> iterator() {
+               List<Content> lst = new ArrayList<>();
+               for (HierarchyUnit hu : hierarchyUnit.getDirectHierarchyUnits(false))
+                       lst.add(new HierarchyUnitContent(getSession(), provider, hu));
+
+               for (Role role : ((UserDirectory) hierarchyUnit.getDirectory()).getHierarchyUnitRoles(hierarchyUnit, null,
+                               false))
+                       lst.add(new RoleContent(getSession(), provider, this, role));
+               return lst.iterator();
+       }
+
+       /*
+        * TYPING
+        */
+       @Override
+       public List<QName> getContentClasses() {
+               List<QName> contentClasses = super.getContentClasses();
+               contentClasses.add(DName.collection.qName());
+               return contentClasses;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> A adapt(Class<A> clss) {
+               if (clss.equals(HierarchyUnit.class))
+                       return (A) hierarchyUnit;
+               return super.adapt(clss);
+       }
+
+       /*
+        * ACCESSOR
+        */
+       HierarchyUnit getHierarchyUnit() {
+               return hierarchyUnit;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/RoleContent.java
new file mode 100644 (file)
index 0000000..64feb1d
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.cms.acr.directory;
+
+import java.util.Dictionary;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.osgi.service.useradmin.Role;
+
+class RoleContent extends AbstractDirectoryContent {
+
+       private HierarchyUnitContent parent;
+       private Role role;
+
+       public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
+                       Role role) {
+               super(session, provider);
+               this.parent = parent;
+               this.role = role;
+       }
+
+       @Override
+       Dictionary<String, Object> doGetProperties() {
+               return role.getProperties();
+       }
+
+       @Override
+       public QName getName() {
+               String name = ((UserDirectory) parent.getHierarchyUnit().getDirectory()).getRoleSimpleName(role);
+               return new ContentName(name);
+       }
+
+       @Override
+       public Content getParent() {
+               return parent;
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> A adapt(Class<A> clss) {
+               if (Role.class.isAssignableFrom(clss))
+                       return (A) role;
+               return super.adapt(clss);
+       }
+
+}
index bfcd0118d76e6348fff11911cf71821dec183425..43cae85721bf6b96c3f577f157f64b56845c11b2 100644 (file)
 package org.argeo.cms.acr.fs;
 
+import java.io.Closeable;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
 
+import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrAttributeType;
 import org.argeo.api.acr.CrName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
-import org.argeo.util.FsUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.util.FsUtils;
 
+/** Content persisted as a filesystem {@link Path}. */
 public class FsContent extends AbstractContent implements ProvidedContent {
-       private final static String USER_ = "user:";
+       private CmsLog log = CmsLog.getLog(FsContent.class);
+
+       final static String USER_ = "user:";
 
        private static final Map<QName, String> BASIC_KEYS;
        private static final Map<QName, String> POSIX_KEYS;
        static {
                BASIC_KEYS = new HashMap<>();
-               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
-               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
-               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
-               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+               BASIC_KEYS.put(DName.creationdate.qName(), "basic:creationTime");
+               BASIC_KEYS.put(DName.getlastmodified.qName(), "basic:lastModifiedTime");
+               BASIC_KEYS.put(DName.getcontentlength.qName(), "basic:size");
+
+               BASIC_KEYS.put(CrName.fileKey.qName(), "basic:fileKey");
 
                POSIX_KEYS = new HashMap<>(BASIC_KEYS);
-               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
-               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
-               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+               POSIX_KEYS.put(DName.owner.qName(), "owner:owner");
+               POSIX_KEYS.put(DName.group.qName(), "posix:group");
+               POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions");
        }
 
-       private final ProvidedSession session;
        private final FsContentProvider provider;
        private final Path path;
-       private final boolean isRoot;
+       private final boolean isMountBase;
        private final QName name;
 
        protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
-               this.session = session;
+               super(session);
                this.provider = contentProvider;
                this.path = path;
-               this.isRoot = contentProvider.isRoot(path);
+               this.isMountBase = contentProvider.isMountBase(path);
                // TODO check file names with ':' ?
-               if (isRoot)
-                       this.name = CrName.ROOT.get();
-               else
-                       this.name = session.parsePrefixedName(path.getFileName().toString());
+               if (isMountBase) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) {
+                               Content mountPoint = session.getMountPoint(mountPath);
+                               this.name = mountPoint.getName();
+                       } else {
+                               this.name = CrName.root.qName();
+                       }
+               } else {
+
+                       // TODO should we support prefixed name for known types?
+                       QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+//                     QName providerName = new QName(path.getFileName().toString());
+                       // TODO remove extension if mounted?
+                       this.name = new ContentName(providerName, session);
+               }
        }
 
        protected FsContent(FsContent context, Path path) {
@@ -80,12 +114,32 @@ public class FsContent extends AbstractContent implements ProvidedContent {
         * ATTRIBUTES
         */
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<A> get(QName key, Class<A> clss) {
                Object value;
                try {
                        // We need to add user: when accessing via Files#getAttribute
-                       value = Files.getAttribute(path, toFsAttributeKey(key));
+
+                       if (POSIX_KEYS.containsKey(key)) {
+                               value = Files.getAttribute(path, toFsAttributeKey(key));
+                       } else {
+                               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path,
+                                               UserDefinedFileAttributeView.class);
+                               String prefixedName = NamespaceUtils.toPrefixedName(provider, key);
+                               if (!udfav.list().contains(prefixedName))
+                                       return Optional.empty();
+                               ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName));
+                               udfav.read(prefixedName, buf);
+                               buf.flip();
+                               if (buf.hasArray())
+                                       value = buf.array();
+                               else {
+                                       byte[] arr = new byte[buf.remaining()];
+                                       buf.get(arr);
+                                       value = arr;
+                               }
+                       }
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
                }
@@ -99,15 +153,41 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        }
                        // TODO perform trivial file conversion to other formats
                }
+
+               // TODO better deal with multiple types
                if (value instanceof byte[]) {
-                       res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
-               }
-               if (res == null)
-                       try {
-                               res = (A) value;
-                       } catch (ClassCastException e) {
-                               return Optional.empty();
+                       String str = new String((byte[]) value, StandardCharsets.UTF_8);
+                       String[] arr = str.split("\n");
+
+                       if (arr.length == 1) {
+                               if (clss.isAssignableFrom(String.class)) {
+                                       res = (A) arr[0];
+                               } else {
+                                       res = (A) CrAttributeType.parse(arr[0]);
+                               }
+                       } else {
+                               List<Object> lst = new ArrayList<>();
+                               for (String s : arr) {
+                                       lst.add(CrAttributeType.parse(s));
+                               }
+                               res = (A) lst;
                        }
+               }
+               if (res == null) {
+                       if (isDefaultAttrTypeRequested(clss))
+                               return Optional.of((A) CrAttributeType.parse(value.toString()));
+                       if (clss.isAssignableFrom(value.getClass()))
+                               return Optional.of((A) value);
+                       if (clss.isAssignableFrom(String.class))
+                               return Optional.of((A) value.toString());
+                       log.warn("Cannot interpret " + key + " in " + this);
+                       return Optional.empty();
+//                     try {
+//                             res = (A) value;
+//                     } catch (ClassCastException e) {
+//                             return Optional.empty();
+//                     }
+               }
                return Optional.of(res);
        }
 
@@ -118,7 +198,11 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                if (udfav != null) {
                        try {
                                for (String name : udfav.list()) {
-                                       result.add(session.parsePrefixedName(name));
+                                       QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
+                                       if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+                                               continue; // skip prefix mapping
+                                       QName sessionName = new ContentName(providerName, getSession());
+                                       result.add(sessionName);
                                }
                        } catch (IOException e) {
                                throw new ContentResourceException("Cannot list attributes for " + path, e);
@@ -131,7 +215,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        protected void removeAttr(QName key) {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                try {
-                       udfav.delete(session.toPrefixedName(key));
+                       udfav.delete(NamespaceUtils.toPrefixedName(provider, key));
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -140,10 +224,22 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        @Override
        public Object put(QName key, Object value) {
                Object previous = get(key);
+
+               String toWrite;
+               if (value instanceof List) {
+                       StringJoiner sj = new StringJoiner("\n");
+                       for (Object obj : (List<?>) value) {
+                               sj.add(obj.toString());
+                       }
+                       toWrite = sj.toString();
+               } else {
+                       toWrite = value.toString();
+               }
+
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
-               ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+               ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
                try {
-                       int size = udfav.write(session.toPrefixedName(key), bb);
+                       udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -154,7 +250,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                if (POSIX_KEYS.containsKey(key))
                        return POSIX_KEYS.get(key);
                else
-                       return USER_ + session.toPrefixedName(key);
+                       return USER_ + NamespaceUtils.toPrefixedName(provider, key);
        }
 
        /*
@@ -164,7 +260,19 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        public Iterator<Content> iterator() {
                if (Files.isDirectory(path)) {
                        try {
-                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+                               return Files.list(path).map((p) -> {
+                                       FsContent fsContent = new FsContent(this, p);
+                                       Optional<String> isMount = fsContent.get(CrName.mount.qName(), String.class);
+                                       if (isMount.orElse("false").equals("true")) {
+                                               QName[] classes = null;
+                                               ContentProvider contentProvider = getSession().getRepository()
+                                                               .getMountContentProvider(fsContent, false, classes);
+                                               Content mountedContent = contentProvider.get(getSession(), "");
+                                               return mountedContent;
+                                       } else {
+                                               return (Content) fsContent;
+                                       }
+                               }).iterator();
                        } catch (IOException e) {
                                throw new ContentResourceException("Cannot list " + path, e);
                        }
@@ -175,9 +283,10 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content add(QName name, QName... classes) {
+               FsContent fsContent;
                try {
-                       Path newPath = path.resolve(session.toPrefixedName(name));
-                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
+                       Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
+                       if (ContentName.contains(classes, DName.collection.qName()))
                                Files.createDirectory(newPath);
                        else
                                Files.createFile(newPath);
@@ -185,10 +294,23 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 //             for(ContentClass clss:classes) {
 //                     Files.setAttribute(newPath, name, newPath, null)
 //             }
-                       return new FsContent(this, newPath);
+                       fsContent = new FsContent(this, newPath);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot create new content", e);
                }
+
+               if (classes.length > 0)
+                       fsContent.addContentClasses(classes);
+               if (getSession().getRepository().shouldMount(classes)) {
+                       ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+                                       classes);
+                       Content mountedContent = contentProvider.get(getSession(), "");
+                       fsContent.put(CrName.mount.qName(), "true");
+                       return mountedContent;
+
+               } else {
+                       return fsContent;
+               }
        }
 
        @Override
@@ -198,22 +320,114 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content getParent() {
-               if (isRoot)
-                       return null;// TODO deal with mounts
+               if (isMountBase) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath == null || mountPath.equals("/"))
+                               return null;
+                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       return getSession().get(parent[0]);
+               }
                return new FsContent(this, path.getParent());
        }
 
+       @SuppressWarnings("unchecked")
+       @Override
+       public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
+               if (InputStream.class.isAssignableFrom(clss)) {
+                       if (Files.isDirectory(path))
+                               throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+                       return (C) Files.newInputStream(path);
+               } else if (OutputStream.class.isAssignableFrom(clss)) {
+                       if (Files.isDirectory(path))
+                               throw new UnsupportedOperationException("Cannot open " + path + " as stream, since it is a directory");
+                       return (C) Files.newOutputStream(path);
+               }
+               return super.open(clss);
+       }
+
        /*
-        * ACCESSORS
+        * MOUNT MANAGEMENT
         */
        @Override
-       public ProvidedSession getSession() {
-               return session;
+       public ProvidedContent getMountPoint(String relativePath) {
+               Path childPath = path.resolve(relativePath);
+               // TODO check that it is a mount
+               return new FsContent(this, childPath);
        }
 
+       /*
+        * TYPING
+        */
+
+       @Override
+       public List<QName> getContentClasses() {
+               List<QName> res = new ArrayList<>();
+               List<String> value = getMultiple(DName.resourcetype.qName(), String.class);
+               for (String s : value) {
+                       QName name = NamespaceUtils.parsePrefixedName(provider, s);
+                       res.add(name);
+               }
+               if (Files.isDirectory(path))
+                       res.add(DName.collection.qName());
+               return res;
+       }
+
+       @Override
+       public void addContentClasses(QName... contentClass) {
+               List<String> toWrite = new ArrayList<>();
+               for (QName cc : getContentClasses()) {
+                       if (cc.equals(DName.collection.qName()))
+                               continue; // skip
+                       toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+               }
+               for (QName cc : contentClass) {
+                       toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+               }
+               put(DName.resourcetype.qName(), toWrite);
+       }
+
+       /*
+        * ACCESSORS
+        */
+
        @Override
        public FsContentProvider getProvider() {
                return provider;
        }
 
+       /*
+        * READ / WRITE
+        */
+       @SuppressWarnings("unchecked")
+       public <A> CompletableFuture<A> write(Class<A> clss) {
+               if (isContentClass(DName.collection.qName())) {
+                       throw new IllegalStateException("Cannot directly write to a collection");
+               }
+               if (InputStream.class.isAssignableFrom(clss)) {
+                       CompletableFuture<InputStream> res = new CompletableFuture<>();
+                       res.thenAccept((in) -> {
+                               try {
+                                       Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot write to " + path, e);
+                               }
+                       });
+                       return (CompletableFuture<A>) res;
+               } else if (Source.class.isAssignableFrom(clss)) {
+                       CompletableFuture<Source> res = new CompletableFuture<Source>();
+                       res.thenAccept((source) -> {
+//                             Path targetPath = path.getParent().resolve(path.getFileName()+".xml");
+                               Path targetPath = path;
+                               try (OutputStream out = Files.newOutputStream(targetPath)) {
+                                       StreamResult result = new StreamResult(out);
+                                       TransformerFactory.newDefaultInstance().newTransformer().transform(source, result);
+                               } catch (IOException | TransformerException e) {
+                                       throw new RuntimeException("Cannot write to " + path, e);
+                               }
+                       });
+                       return (CompletableFuture<A>) res;
+               } else {
+                       return super.write(clss);
+               }
+       }
 }
index 99ed3a8ca61689c11d89f139aad9f2ad8984e5ff..9b1b9668303f066e2106de3aa4a296edcabbf55d 100644 (file)
 package org.argeo.cms.acr.fs;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
 
-import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ArgeoNamespace;
 import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.RuntimeNamespaceContext;
 import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
 
+/** Access a file system as a {@link ContentProvider}. */
 public class FsContentProvider implements ContentProvider {
-       private final Path rootPath;
+       final static String XMLNS_ = "xmlns:";
 
-       public FsContentProvider(Path rootPath) {
-               super();
+       protected String mountPath;
+       protected Path rootPath;
+
+       private NavigableMap<String, String> prefixes = new TreeMap<>();
+
+       public FsContentProvider(String mountPath, Path rootPath) {
+               Objects.requireNonNull(mountPath);
+               Objects.requireNonNull(rootPath);
+
+               this.mountPath = mountPath;
                this.rootPath = rootPath;
+               // FIXME make it more robust
+               initNamespaces();
+       }
+
+       protected FsContentProvider() {
+
        }
 
-       boolean isRoot(Path path) {
+       protected void initNamespaces() {
+               try {
+                       UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
+                                       UserDefinedFileAttributeView.class);
+                       if (udfav == null)
+                               return;
+                       for (String name : udfav.list()) {
+                               if (name.startsWith(XMLNS_)) {
+                                       ByteBuffer buf = ByteBuffer.allocate(udfav.size(name));
+                                       udfav.read(name, buf);
+                                       buf.flip();
+                                       String namespace = StandardCharsets.UTF_8.decode(buf).toString();
+                                       String prefix = name.substring(XMLNS_.length());
+                                       prefixes.put(prefix, namespace);
+                               }
+                       }
+
+                       // defaults
+                       addDefaultNamespace(udfav, ArgeoNamespace.CR_DEFAULT_PREFIX, ArgeoNamespace.CR_NAMESPACE_URI);
+                       addDefaultNamespace(udfav, "basic", ArgeoNamespace.CR_NAMESPACE_URI);
+                       addDefaultNamespace(udfav, "owner", ArgeoNamespace.CR_NAMESPACE_URI);
+                       addDefaultNamespace(udfav, "posix", ArgeoNamespace.CR_NAMESPACE_URI);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot read namespaces from " + rootPath, e);
+               }
+
+       }
+
+       protected void addDefaultNamespace(UserDefinedFileAttributeView udfav, String prefix, String namespace)
+                       throws IOException {
+               if (!prefixes.containsKey(prefix)) {
+                       ByteBuffer bb = ByteBuffer.wrap(namespace.getBytes(StandardCharsets.UTF_8));
+                       udfav.write(XMLNS_ + prefix, bb);
+                       prefixes.put(prefix, namespace);
+               }
+       }
+
+       public void registerPrefix(String prefix, String namespace) {
+               if (prefixes.containsKey(prefix))
+                       prefixes.remove(prefix);
+               try {
+                       UserDefinedFileAttributeView udfav = Files.getFileAttributeView(rootPath,
+                                       UserDefinedFileAttributeView.class);
+                       addDefaultNamespace(udfav, prefix, namespace);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot register namespace " + prefix + " " + namespace + " on " + rootPath, e);
+               }
+
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
+       boolean isMountBase(Path path) {
                try {
                        return Files.isSameFile(rootPath, path);
                } catch (IOException e) {
@@ -26,7 +108,39 @@ public class FsContentProvider implements ContentProvider {
        }
 
        @Override
-       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+       public ProvidedContent get(ProvidedSession session, String relativePath) {
                return new FsContent(session, this, rootPath.resolve(relativePath));
        }
+
+       @Override
+       public boolean exists(ProvidedSession session, String relativePath) {
+               return Files.exists(rootPath.resolve(relativePath));
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       @Override
+       public String getNamespaceURI(String prefix) {
+               return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix);
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> res = NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream()
+                               .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()),
+                               namespaceURI);
+               if (!res.hasNext()) {
+                       String prefix = RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI);
+                       if (prefix != null) {
+                               registerPrefix(prefix, namespaceURI);
+                               return getPrefixes(namespaceURI);
+                       } else {
+                               throw new IllegalArgumentException("Unknown namespace " + namespaceURI);
+                       }
+               }
+               return res;
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProviderService.java
new file mode 100644 (file)
index 0000000..4e986f6
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Map;
+import java.util.Objects;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsState;
+
+public class FsContentProviderService extends FsContentProvider {
+       private CmsState cmsState;
+
+       public void start(Map<String, String> properties) {
+               mountPath = properties.get(CmsConstants.ACR_MOUNT_PATH);
+               Objects.requireNonNull(mountPath);
+               if (!mountPath.startsWith("/"))
+                       throw new IllegalArgumentException("Mount path must start with /");
+
+               String relPath = mountPath.substring(1);
+               rootPath = cmsState.getDataPath(relPath);
+               try {
+                       Files.createDirectories(rootPath);
+               } catch (IOException e) {
+                       throw new IllegalStateException(
+                                       "Cannot initialize FS content provider " + mountPath + " with base" + rootPath, e);
+               }
+
+               initNamespaces();
+       }
+
+       public void stop() {
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/DSMLv2.xsd
new file mode 100644 (file)
index 0000000..320da74
--- /dev/null
@@ -0,0 +1,463 @@
+<xsd:schema targetNamespace="urn:oasis:names:tc:DSML:2:0:core" \r
+                      xmlns="urn:oasis:names:tc:DSML:2:0:core" \r
+                      xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">\r
+<!-- Copyright (C) The Organization for the Advancement of Structured Information Standards [OASIS] 2001. All Rights Reserved. -->\r
+       <!-- DSML Requests -->\r
+       <xsd:group name="DSMLRequests">\r
+               <xsd:choice>\r
+                       <xsd:element name="authRequest" type="AuthRequest"/>\r
+                       <xsd:group ref="BatchRequests"/>\r
+               </xsd:choice>\r
+       </xsd:group>\r
+       <xsd:group name="BatchRequests">\r
+               <xsd:choice>\r
+                       <xsd:element name="searchRequest" type="SearchRequest"/>\r
+                       <xsd:element name="modifyRequest" type="ModifyRequest"/>\r
+                       <xsd:element name="addRequest" type="AddRequest"/>\r
+                       <xsd:element name="delRequest" type="DelRequest"/>\r
+                       <xsd:element name="modDNRequest" type="ModifyDNRequest"/>\r
+                       <xsd:element name="compareRequest" type="CompareRequest"/>\r
+                       <xsd:element name="abandonRequest" type="AbandonRequest"/>\r
+                       <xsd:element name="extendedRequest" type="ExtendedRequest"/>\r
+               </xsd:choice>\r
+       </xsd:group>\r
+       <!-- DSML Responses -->\r
+       <xsd:group name="DSMLResponses">\r
+               <xsd:choice>\r
+                       <xsd:element name="authResponse" type="LDAPResult"/>\r
+                       <xsd:element name="searchResultEntry" type="SearchResultEntry"/>\r
+                       <xsd:element name="searchResultReference" type="SearchResultReference"/>\r
+                       <xsd:element name="searchResultDone" type="LDAPResult"/>\r
+                       <xsd:element name="modifyResponse" type="LDAPResult"/>\r
+                       <xsd:element name="addResponse" type="LDAPResult"/>\r
+                       <xsd:element name="delResponse" type="LDAPResult"/>\r
+                       <xsd:element name="modDNResponse" type="LDAPResult"/>\r
+                       <xsd:element name="compareResponse" type="LDAPResult"/>\r
+                       <xsd:element name="extendedResponse" type="ExtendedResponse"/>\r
+                       <xsd:element name="errorResponse" type="ErrorResponse"/>\r
+               </xsd:choice>\r
+       </xsd:group>\r
+       <!--  *************** Batch Envelopes ********************* -->\r
+       <xsd:element name="batchRequest" type="BatchRequest"/>\r
+       <xsd:element name="batchResponse" type="BatchResponse"/>\r
+       <!-- **** Batch Request Envelope **** -->\r
+       <xsd:complexType name="BatchRequest">\r
+               <xsd:sequence>\r
+                       <xsd:element name="authRequest" type="AuthRequest" minOccurs="0" maxOccurs="1"/>\r
+                       <xsd:group ref="BatchRequests" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+               <xsd:attribute name="processing" use="optional" default="sequential">\r
+                       <xsd:simpleType>\r
+                               <xsd:restriction base="xsd:string">\r
+                                       <xsd:enumeration value="sequential"/>\r
+                                       <xsd:enumeration value="parallel"/>\r
+                               </xsd:restriction>\r
+                       </xsd:simpleType>\r
+               </xsd:attribute>\r
+               <xsd:attribute name="responseOrder" use="optional" default="sequential">\r
+                       <xsd:simpleType>\r
+                               <xsd:restriction base="xsd:string">\r
+                                       <xsd:enumeration value="sequential"/>\r
+                                       <xsd:enumeration value="unordered"/>\r
+                               </xsd:restriction>\r
+                       </xsd:simpleType>\r
+               </xsd:attribute>\r
+               <xsd:attribute name="onError" use="optional" default="exit">\r
+                       <xsd:simpleType>\r
+                               <xsd:restriction base="xsd:string">\r
+                                       <xsd:enumeration value="resume"/>\r
+                                       <xsd:enumeration value="exit"/>\r
+                               </xsd:restriction>\r
+                       </xsd:simpleType>\r
+               </xsd:attribute>\r
+       </xsd:complexType>\r
+       <!-- **** Batch Response Envelope **** -->\r
+       <xsd:complexType name="BatchResponse">\r
+               <xsd:sequence>\r
+                       <xsd:group ref="BatchResponses" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+       </xsd:complexType>\r
+       <!-- **** Batch Responses **** -->\r
+       <xsd:group name="BatchResponses">\r
+               <xsd:choice>\r
+                       <xsd:element name="searchResponse" type="SearchResponse"/>\r
+                       <xsd:element name="authResponse" type="LDAPResult"/>\r
+                       <xsd:element name="modifyResponse" type="LDAPResult"/>\r
+                       <xsd:element name="addResponse" type="LDAPResult"/>\r
+                       <xsd:element name="delResponse" type="LDAPResult"/>\r
+                       <xsd:element name="modDNResponse" type="LDAPResult"/>\r
+                       <xsd:element name="compareResponse" type="LDAPResult"/>\r
+                       <xsd:element name="extendedResponse" type="ExtendedResponse"/>\r
+                       <xsd:element name="errorResponse" type="ErrorResponse"/>\r
+               </xsd:choice>\r
+       </xsd:group>\r
+       <!-- **** Search Response **** -->\r
+       <xsd:complexType name="SearchResponse">\r
+               <xsd:sequence>\r
+                       <xsd:element name="searchResultEntry" type="SearchResultEntry"\r
+                                     minOccurs="0" maxOccurs="unbounded"/>\r
+                       <xsd:element name="searchResultReference" type="SearchResultReference"\r
+                                     minOccurs="0" maxOccurs="unbounded"/>\r
+                       <xsd:element name="searchResultDone" type="LDAPResult"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+       </xsd:complexType>\r
+       <!-- ***** DsmlDN ***** -->\r
+       <xsd:simpleType name="DsmlDN">\r
+               <xsd:restriction base="xsd:string"/>\r
+       </xsd:simpleType>\r
+       <!-- ***** DsmlRDN ***** -->\r
+       <xsd:simpleType name="DsmlRDN">\r
+               <xsd:restriction base="xsd:string"/>\r
+       </xsd:simpleType>\r
+       <!-- ***** Request ID ***** -->\r
+       <xsd:simpleType name="RequestID">\r
+               <xsd:restriction base="xsd:string"/>\r
+       </xsd:simpleType>\r
+       <!-- ***** AttributeDescriptionValue ***** -->\r
+       <xsd:simpleType name="AttributeDescriptionValue">\r
+               <xsd:restriction base="xsd:string">\r
+                       <xsd:pattern value="((([0-2](\.[0-9]+)+)|([a-zA-Z]+([a-zA-Z0-9]|[-])*))(;([a-zA-Z0-9]|[-])+)*)"/>\r
+               </xsd:restriction>\r
+       </xsd:simpleType>\r
+       <xsd:simpleType name="NumericOID">\r
+               <xsd:restriction base="xsd:string">\r
+                       <xsd:pattern value="[0-2]\.[0-9]+(\.[0-9]+)*"/>\r
+               </xsd:restriction>\r
+       </xsd:simpleType>\r
+       <!-- ***** MAX Integer ***** -->\r
+       <xsd:simpleType name="MAXINT">\r
+               <xsd:restriction base="xsd:unsignedInt">\r
+                       <xsd:maxInclusive value="2147483647"/>\r
+               </xsd:restriction>\r
+       </xsd:simpleType>\r
+       <!-- **** DSML Value **** -->\r
+       <xsd:simpleType name="DsmlValue">\r
+               <xsd:union memberTypes="xsd:string xsd:base64Binary xsd:anyURI"/>\r
+       </xsd:simpleType>\r
+       <!-- **** DSML Control **** -->\r
+       <xsd:complexType name="Control">\r
+               <xsd:sequence>\r
+                       <xsd:element name="controlValue" type="xsd:anyType" minOccurs="0"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="type" type="NumericOID" use="required"/>\r
+               <xsd:attribute name="criticality" type="xsd:boolean" use="optional" default="false"/>\r
+       </xsd:complexType>\r
+       <!-- **** DSML Filter **** -->\r
+       <xsd:complexType name="Filter">\r
+               <xsd:group ref="FilterGroup"/>\r
+       </xsd:complexType>\r
+       <xsd:group name="FilterGroup">\r
+               <xsd:sequence>\r
+                       <xsd:choice>\r
+                               <xsd:element name="and" type="FilterSet"/>\r
+                               <xsd:element name="or" type="FilterSet"/>\r
+                               <xsd:element name="not" type="Filter"/>\r
+                               <xsd:element name="equalityMatch" type="AttributeValueAssertion"/>\r
+                               <xsd:element name="substrings" type="SubstringFilter"/>\r
+                               <xsd:element name="greaterOrEqual" type="AttributeValueAssertion"/>\r
+                               <xsd:element name="lessOrEqual" type="AttributeValueAssertion"/>\r
+                               <xsd:element name="present" type="AttributeDescription"/>\r
+                               <xsd:element name="approxMatch" type="AttributeValueAssertion"/>\r
+                               <xsd:element name="extensibleMatch" type="MatchingRuleAssertion"/>\r
+                       </xsd:choice>\r
+               </xsd:sequence>\r
+       </xsd:group>\r
+       <xsd:complexType name="FilterSet">\r
+               <xsd:sequence>\r
+                       <xsd:group ref="FilterGroup" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="AttributeValueAssertion">\r
+               <xsd:sequence>\r
+                       <xsd:element name="value" type="DsmlValue"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="AttributeDescription">\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="SubstringFilter">\r
+               <xsd:sequence>\r
+                       <xsd:element name="initial" type="DsmlValue" minOccurs="0"/>\r
+                       <xsd:element name="any" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+                       <xsd:element name="final" type="DsmlValue" minOccurs="0"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="MatchingRuleAssertion">\r
+               <xsd:sequence>\r
+                       <xsd:element name="value" type="DsmlValue"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="dnAttributes" type="xsd:boolean" use="optional" default="false"/>\r
+               <xsd:attribute name="matchingRule" type="xsd:string" use="optional"/>\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="optional"/>\r
+       </xsd:complexType>\r
+       <!--  *************** DSML MESSAGE ******************** -->\r
+       <xsd:complexType name="DsmlMessage">\r
+               <xsd:sequence>\r
+                       <xsd:element name="control" type="Control" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+       </xsd:complexType>\r
+       <!--  *************** LDAP RESULT ********************* -->\r
+       <xsd:simpleType name="LDAPResultCode">\r
+               <xsd:restriction base="xsd:string">\r
+                       <xsd:enumeration value="success"/>\r
+                       <xsd:enumeration value="operationsError"/>\r
+                       <xsd:enumeration value="protocolError"/>\r
+                       <xsd:enumeration value="timeLimitExceeded"/>\r
+                       <xsd:enumeration value="sizeLimitExceeded"/>\r
+                       <xsd:enumeration value="compareFalse"/>\r
+                       <xsd:enumeration value="compareTrue"/>\r
+                       <xsd:enumeration value="authMethodNotSupported"/>\r
+                       <xsd:enumeration value="strongAuthRequired"/>\r
+                       <xsd:enumeration value="referral"/>\r
+                       <xsd:enumeration value="adminLimitExceeded"/>\r
+                       <xsd:enumeration value="unavailableCriticalExtension"/>\r
+                       <xsd:enumeration value="confidentialityRequired"/>\r
+                       <xsd:enumeration value="saslBindInProgress"/>\r
+                       <xsd:enumeration value="noSuchAttribute"/>\r
+                       <xsd:enumeration value="undefinedAttributeType"/>\r
+                       <xsd:enumeration value="inappropriateMatching"/>\r
+                       <xsd:enumeration value="constraintViolation"/>\r
+                       <xsd:enumeration value="attributeOrValueExists"/>\r
+                       <xsd:enumeration value="invalidAttributeSyntax"/>\r
+                       <xsd:enumeration value="noSuchObject"/>\r
+                       <xsd:enumeration value="aliasProblem"/>\r
+                       <xsd:enumeration value="invalidDNSyntax"/>\r
+                       <xsd:enumeration value="aliasDerefencingProblem"/>\r
+                       <xsd:enumeration value="inappropriateAuthentication"/>\r
+                       <xsd:enumeration value="invalidCredentials"/>\r
+                       <xsd:enumeration value="insufficientAccessRights"/>\r
+                       <xsd:enumeration value="busy"/>\r
+                       <xsd:enumeration value="unavailable"/>\r
+                       <xsd:enumeration value="unwillingToPerform"/>\r
+                       <xsd:enumeration value="loopDetect"/>\r
+                       <xsd:enumeration value="namingViolation"/>\r
+                       <xsd:enumeration value="objectClassViolation"/>\r
+                       <xsd:enumeration value="notAllowedOnNonLeaf"/>\r
+                       <xsd:enumeration value="notAllowedOnRDN"/>\r
+                       <xsd:enumeration value="entryAlreadyExists"/>\r
+                       <xsd:enumeration value="objectClassModsProhibited"/>\r
+                       <xsd:enumeration value="affectMultipleDSAs"/>\r
+                       <xsd:enumeration value="other"/>\r
+               </xsd:restriction>\r
+       </xsd:simpleType>\r
+       <xsd:complexType name="ResultCode">\r
+               <xsd:attribute name="code" type="xsd:int" use="required"/>\r
+               <xsd:attribute name="descr" type="LDAPResultCode" use="optional"/>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="LDAPResult">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="resultCode" type="ResultCode"/>\r
+                                       <xsd:element name="errorMessage" type="xsd:string" minOccurs="0"/>\r
+                                       <xsd:element name="referral" type="xsd:anyURI" minOccurs="0" maxOccurs="unbounded"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="matchedDN" type="DsmlDN" use="optional"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="ErrorResponse">\r
+               <xsd:sequence>\r
+                       <xsd:element name="message" type="xsd:string" minOccurs="0"/>\r
+                       <xsd:element name="detail" minOccurs="0">\r
+                               <xsd:complexType>\r
+                                       <xsd:sequence>\r
+                                               <xsd:any/>\r
+                                       </xsd:sequence>\r
+                               </xsd:complexType>\r
+                       </xsd:element>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="requestID" type="RequestID" use="optional"/>\r
+               <xsd:attribute name="type">\r
+                       <xsd:simpleType>\r
+                               <xsd:restriction base="xsd:string">\r
+                                       <xsd:enumeration value="notAttempted"/>\r
+                                       <xsd:enumeration value="couldNotConnect"/>\r
+                                       <xsd:enumeration value="connectionClosed"/>\r
+                                       <xsd:enumeration value="malformedRequest"/>\r
+                                       <xsd:enumeration value="gatewayInternalError"/>\r
+                                       <xsd:enumeration value="authenticationFailed"/>\r
+                                       <xsd:enumeration value="unresolvableURI"/>\r
+                                       <xsd:enumeration value="other"/>\r
+                               </xsd:restriction>\r
+                       </xsd:simpleType>\r
+               </xsd:attribute>\r
+       </xsd:complexType>\r
+       <!-- *************** Auth ********************* -->\r
+       <xsd:complexType name="AuthRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:attribute name="principal" type="xsd:string" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- *************** Search ********************* -->\r
+       <xsd:complexType name="AttributeDescriptions">\r
+               <xsd:sequence minOccurs="0" maxOccurs="unbounded">\r
+                       <xsd:element name="attribute" type="AttributeDescription"/>\r
+               </xsd:sequence>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="SearchRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="filter" type="Filter"/>\r
+                                       <xsd:element name="attributes" type="AttributeDescriptions" minOccurs="0"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                               <xsd:attribute name="scope" use="required">\r
+                                       <xsd:simpleType>\r
+                                               <xsd:restriction base="xsd:string">\r
+                                                       <xsd:enumeration value="baseObject"/>\r
+                                                       <xsd:enumeration value="singleLevel"/>\r
+                                                       <xsd:enumeration value="wholeSubtree"/>\r
+                                               </xsd:restriction>\r
+                                       </xsd:simpleType>\r
+                               </xsd:attribute>\r
+                               <xsd:attribute name="derefAliases" use="required">\r
+                                       <xsd:simpleType>\r
+                                               <xsd:restriction base="xsd:string">\r
+                                                       <xsd:enumeration value="neverDerefAliases"/>\r
+                                                       <xsd:enumeration value="derefInSearching"/>\r
+                                                       <xsd:enumeration value="derefFindingBaseObj"/>\r
+                                                       <xsd:enumeration value="derefAlways"/>\r
+                                               </xsd:restriction>\r
+                                       </xsd:simpleType>\r
+                               </xsd:attribute>\r
+                               <xsd:attribute name="sizeLimit" type="MAXINT" use="optional" default="0"/>\r
+                               <xsd:attribute name="timeLimit" type="MAXINT" use="optional" default="0"/>\r
+                               <xsd:attribute name="typesOnly" type="xsd:boolean" use="optional" default="false"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ***** Search Result Entry ***** -->\r
+       <xsd:complexType name="SearchResultEntry">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="attr" type="DsmlAttr" minOccurs="0" maxOccurs="unbounded"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="DsmlAttr">\r
+               <xsd:sequence>\r
+                       <xsd:element name="value" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="DsmlModification">\r
+               <xsd:sequence>\r
+                       <xsd:element name="value" type="DsmlValue" minOccurs="0" maxOccurs="unbounded"/>\r
+               </xsd:sequence>\r
+               <xsd:attribute name="name" type="AttributeDescriptionValue" use="required"/>\r
+               <xsd:attribute name="operation" use="required">\r
+                       <xsd:simpleType>\r
+                               <xsd:restriction base="xsd:string">\r
+                                       <xsd:enumeration value="add"/>\r
+                                       <xsd:enumeration value="delete"/>\r
+                                       <xsd:enumeration value="replace"/>\r
+                               </xsd:restriction>\r
+                       </xsd:simpleType>\r
+               </xsd:attribute>\r
+       </xsd:complexType>\r
+       <!-- ***** Search Result Reference ***** -->\r
+       <xsd:complexType name="SearchResultReference">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="ref" type="xsd:anyURI" maxOccurs="unbounded"/>\r
+                               </xsd:sequence>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ************* MODIFY ******************** -->\r
+       <xsd:complexType name="ModifyRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="modification" type="DsmlModification" minOccurs="0" maxOccurs="unbounded"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!--  *************** ADD ********************* -->\r
+       <xsd:complexType name="AddRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="attr" type="DsmlAttr" minOccurs="0" maxOccurs="unbounded"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- *************** DELETE ********************* -->\r
+       <xsd:complexType name="DelRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- *************** MODIFY DN ********************* -->\r
+       <xsd:complexType name="ModifyDNRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                               <xsd:attribute name="newrdn" type="DsmlRDN" use="required"/>\r
+                               <xsd:attribute name="deleteoldrdn" type="xsd:boolean" use="optional" default="true"/>\r
+                               <xsd:attribute name="newSuperior" type="DsmlDN" use="optional"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ************* COMPARE ******************** -->\r
+       <xsd:complexType name="CompareRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="assertion" type="AttributeValueAssertion"/>\r
+                               </xsd:sequence>\r
+                               <xsd:attribute name="dn" type="DsmlDN" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ***** ABANDON ***** -->\r
+       <xsd:complexType name="AbandonRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:attribute name="abandonID" type="RequestID" use="required"/>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ************* EXTENDED OPERATION ******************** -->\r
+       <xsd:complexType name="ExtendedRequest">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="DsmlMessage">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="requestName" type="NumericOID"/>\r
+                                       <xsd:element name="requestValue" type="xsd:anyType" minOccurs="0"/>\r
+                               </xsd:sequence>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <xsd:complexType name="ExtendedResponse">\r
+               <xsd:complexContent>\r
+                       <xsd:extension base="LDAPResult">\r
+                               <xsd:sequence>\r
+                                       <xsd:element name="responseName" type="NumericOID" minOccurs="0"/>\r
+                                       <xsd:element name="response" type="xsd:anyType" minOccurs="0"/>\r
+                               </xsd:sequence>\r
+                       </xsd:extension>\r
+               </xsd:complexContent>\r
+       </xsd:complexType>\r
+       <!-- ********************END base SCHEMA ********************* -->\r
+</xsd:schema>\r
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/SVG.xsd
new file mode 100644 (file)
index 0000000..7cb1689
--- /dev/null
@@ -0,0 +1,2975 @@
+<?xml version="1.0"?>\r
+<!-- edited with XML Spy v4.0.1 U (http://www.xmlspy.com) by Chris Lilley (W3C Staff) -->\r
+<schema targetNamespace="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" elementFormDefault="unqualified" attributeFormDefault="unqualified" xml:lang="en">\r
+  <!-- don't declare the XML namespace; it is predeclared and redeclaring it upsets some software -->\r
+  <import namespace="http://www.w3.org/1999/xlink" schemaLocation="xlink.xsd"/>\r
+  <import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>\r
+  <!-- simpleTypes -->\r
+  <simpleType name="BaselineShiftValueType">\r
+    <annotation>\r
+      <documentation>The actual definition is\r
+                       baseline | sub | super | &lt;percentage&gt; | &lt;length&gt; | inherit \r
+                       not sure that union can do this\r
+                       </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- SVG BooleanType not needed, already defined by XML Schema -->\r
+  <simpleType name="ClassListType">\r
+    <annotation>\r
+      <documentation>Space-separated list of classes</documentation>\r
+    </annotation>\r
+    <list itemType="string"/>\r
+  </simpleType>\r
+  <simpleType name="ClipValueType">\r
+    <annotation>\r
+      <documentation> &lt;shape&gt; | auto | inherit  </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="ClipPathValueType">\r
+    <annotation>\r
+      <documentation>&lt;uri&gt; | none | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="ClipFillRuleType">\r
+    <annotation>\r
+      <documentation>'clip-rule' or fill-rule property/attribute value </documentation>\r
+    </annotation>\r
+    <restriction base="string">\r
+      <enumeration value="evenodd"/>\r
+      <enumeration value="nonzero"/>\r
+      <enumeration value="inherit"/>\r
+    </restriction>\r
+  </simpleType>\r
+  <simpleType name="ContentTypeType">\r
+    <annotation>\r
+      <documentation source="http://www.ietf.org/rfc/rfc2045.txt">media type, as per [RFC2045]</documentation>\r
+      <documentation>media type, as per [RFC2045] </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="CoordinateType">\r
+    <annotation>\r
+      <documentation source="http://www.w3.org/TR/SVG/types.html#DataTypeCoordinate">a &lt;co-ordinate&gt;</documentation>\r
+      <documentation>a coordinate, which is a number optionally followed immediately by a unit identifier. Perhaps it is possible to represent this as a union by declaring unit idenifiers as a type?</documentation>\r
+    </annotation>\r
+    <restriction base="string">\r
+      <pattern value="((((\+|\-)?((\d+)))|((\+|\-)?(((((\d+)?\.(\d+))|((\d+)\.))([eE](\+|\-)?(\d+))?)|((\d+)([eE](\+|\-)?(\d+))))))(em|ex|px|pt|pc|cm|mm|in|%)?)"/>\r
+    </restriction>\r
+  </simpleType>\r
+  <simpleType name="CoordinatesType">\r
+    <annotation>\r
+      <documentation>a space separated list of CoordinateType. Punt to 'string' for now</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="ColorType">\r
+    <annotation>\r
+      <documentation source="http://www.w3.org/TR/SVG/types.html#DataTypeColor">a CSS2 Color </documentation>\r
+      <documentation>Color as defined in CSS2 and XSL 1.0 plus additional recognised color keyword names (the 'X11 colors')</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="CursorValueType">\r
+    <annotation>\r
+      <documentation>Value is an optional comma-separated list orf uri references followed by one token from an enumerated list.\r
+</documentation>\r
+      <documentation> [ [&lt;uri&gt; ,]* [ auto | crosshair | default | pointer | move | e-resize | ne-resize | nw-resize | n-resize | se-resize | sw-resize | s-resize | w-resize| text | wait | help ] ] | inherit  </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="EnableBackgroundValueType">\r
+    <annotation>\r
+      <documentation>accumulate | new [ &lt;x&gt; &lt;y&gt; &lt;width&gt; &lt;height&gt; ] | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="ExtensionListType">\r
+    <annotation>\r
+      <documentation>extension list specification </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="FeatureListType">\r
+    <annotation>\r
+      <documentation>feature list specification </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="FilterValueType">\r
+    <annotation>\r
+      <documentation>&lt;uri&gt; | none | inherit\r
+</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="FontFamilyValueType">\r
+    <annotation>\r
+      <documentation>[[ &lt;family-name&gt; | &lt;generic-family&gt; ],]* [&lt;family-name&gt; | &lt;generic-family&gt;] | inherit</documentation>\r
+      <documentation>'font-family' property/attribute value (i.e., list of fonts) </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="FontSizeValueType">\r
+    <annotation>\r
+      <documentation>'font-size' property/attribute value </documentation>\r
+      <documentation>&lt;absolute-size&gt; | &lt;relative-size&gt; | &lt;length&gt; | &lt;percentage&gt; | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="FontSizeAdjustValueType">\r
+    <annotation>\r
+      <documentation>'font-size-adjust' property/attribute value </documentation>\r
+      <documentation>&lt;number&gt; | none | inherit </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="GlyphOrientationHorizontalValueType">\r
+    <annotation>\r
+      <documentation>'glyph-orientation-horizontal' property/attribute value (e.g., &lt;angle&gt;)</documentation>\r
+      <documentation>&lt;angle&gt; | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="GlyphOrientationVerticalValueType">\r
+    <annotation>\r
+      <documentation>'glyph-orientation-vertical' property/attribute value (e.g., 'auto', &lt;angle&gt;)</documentation>\r
+      <documentation>auto | &lt;angle&gt; | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- no need to declare IntegerType as XML Schema defines integers -->\r
+  <simpleType name="KerningValue">\r
+    <annotation>\r
+      <documentation>'kerning' property/attribute value (e.g., auto | &lt;length&gt;)</documentation>\r
+      <documentation>auto | &lt;length&gt; | inherit </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="LanguageCodeType">\r
+    <annotation>\r
+      <documentation>a language code, as per [RFC3066]</documentation>\r
+      <documentation source="http://www.ietf.org/rfc/rfc3066.txt"/>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="LanguageCodesType">\r
+    <annotation>\r
+      <documentation>a comma-separated list of language codes, as per [RFC3066]</documentation>\r
+      <documentation source="http://www.ietf.org/rfc/rfc3066.txt"/>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="LengthType">\r
+    <annotation>\r
+      <documentation>a &lt;length&gt;</documentation>\r
+    </annotation>\r
+    <restriction base="string">\r
+      <pattern value="((((\+|\-)?((\d+)))|((\+|\-)?(((((\d+)?\.(\d+))|((\d+)\.))([eE](\+|\-)?(\d+))?)|((\d+)([eE](\+|\-)?(\d+))))))(em|ex|px|pt|pc|cm|mm|in|%)?)"/>\r
+    </restriction>\r
+  </simpleType>\r
+  <simpleType name="LengthsType">\r
+    <annotation>\r
+      <documentation>a list of &lt;length&gt;s</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+    <!-- make a regexp for this one -->\r
+  </simpleType>\r
+  <simpleType name="LinkTargetType">\r
+    <annotation>\r
+      <documentation>link to this target</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="MarkerValueType">\r
+    <annotation>\r
+      <documentation>'marker' property/attribute value (e.g., 'none', %URI;)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+    <!-- need to check this one, its a shorthand value -->\r
+  </simpleType>\r
+  <simpleType name="MaskValueType">\r
+    <annotation>\r
+      <documentation>'mask' property/attribute value (e.g., 'none', %URI;)</documentation>\r
+      <documentation>&lt;uri&gt; | none | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="MediaDescType">\r
+    <annotation>\r
+      <documentation>comma-separated list of media descriptors.</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- no need to define NumberType as XML Schema has double -->\r
+  <simpleType name="NumberOptionalNumberType">\r
+    <annotation>\r
+      <documentation>list of &lt;number&gt;s, but at least one and at most two</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="NumberOrPercentageType">\r
+    <annotation>\r
+      <documentation>a &lt;number&gt; or a  &lt;percentage&gt;  </documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="NumbersType">\r
+    <annotation>\r
+      <documentation>list of &lt;number&gt;s</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="OpacityValueType">\r
+    <annotation>\r
+      <documentation>opacity value (e.g., &lt;number&gt;) </documentation>\r
+      <documentation>&lt;alphavalue&gt; | inherit</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="PaintType">\r
+    <annotation>\r
+      <documentation>a 'fill' or 'stroke' property/attribute value</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="PathDataType">\r
+    <annotation>\r
+      <documentation>a path data specification</documentation>\r
+      <documentation source="http://www.w3.org/TR/SVG/paths.html"/>\r
+      <documentation>Yes, of course this was generated by a program!</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="PointsType">\r
+    <annotation>\r
+      <documentation>a list of points</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="PreserveAspectRatioSpecType">\r
+    <annotation>\r
+      <documentation>'preserveAspectRatio' attribute specification</documentation>\r
+    </annotation>\r
+    <restriction base="string">\r
+      <!-- HTMLBook Note: Revised pattern to be more accurate and match default values -->\r
+      <pattern value="\s*x(Min|Mid|Max)Y(Min|Mid|Max)(\s+(meet|slice)\s*)?"/>\r
+    </restriction>\r
+  </simpleType>\r
+  <simpleType name="ScriptType">\r
+    <annotation>\r
+      <documentation>script expression</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="SpacingValueType">\r
+    <annotation>\r
+      <documentation>'letter-spacing' or 'word-spacing' property/attribute value (e.g., normal | &lt;length&gt;)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="StrokeDashArrayValueType">\r
+    <annotation>\r
+      <documentation>'stroke-dasharray' property/attribute value (e.g., 'none', list of &lt;number&gt;s)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="StrokeDashOffsetValueType">\r
+    <annotation>\r
+      <documentation>'stroke-dashoffset' property/attribute value (e.g., 'none', &gt;length&gt;)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="StrokeMiterLimitValueType">\r
+    <annotation>\r
+      <documentation>'stroke-miterlimit' property/attribute value (e.g., &lt;number&gt;)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="StrokeWidthValueType">\r
+    <annotation>\r
+      <documentation>'stroke-width' property/attribute value (e.g., &lt;length&gt;)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- <simpleType name="StructuredTextType" base="string"/> expanded -->\r
+  <simpleType name="StyleSheetType">\r
+    <annotation>\r
+      <documentation>style sheet data</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="SVGColorType">\r
+    <annotation>\r
+      <documentation>An SVG color value (sRGB plus optional ICC)</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- <simpleType name="TextType" base="string"/> not necessary (string) -->\r
+  <simpleType name="TextDecorationValueType">\r
+    <annotation>\r
+      <documentation>'text-decoration' property/attribute value (e.g., 'none', 'underline')</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <simpleType name="TransformListType">\r
+    <annotation>\r
+      <documentation>Yes, of course this was generated by a program!</documentation>\r
+      <documentation>list of transforms</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <!-- <simpleType name="URIType" base="string"/> not necessary (use AnyURI) -->\r
+  <simpleType name="ViewBoxSpecType">\r
+    <annotation>\r
+      <documentation>'viewBox' attribute specification</documentation>\r
+    </annotation>\r
+    <restriction base="string"/>\r
+  </simpleType>\r
+  <attributeGroup name="stdAttrs">\r
+    <annotation>\r
+      <documentation>All elements have an ID</documentation>\r
+    </annotation>\r
+    <attribute name="id" type="ID" use="optional"/>\r
+    <!-- HTMLBook Note: dropped invalid type attr on below element -->\r
+    <attribute ref="xml:base" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="langSpaceAttrs">\r
+    <annotation>\r
+      <documentation>Common attributes for elements that might contain character data content</documentation>\r
+    </annotation>\r
+    <attribute ref="xml:lang" use="optional"/>\r
+    <attribute ref="xml:space" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="testAttrs">\r
+    <annotation>\r
+      <documentation>Common attributes to check for system capabilities</documentation>\r
+    </annotation>\r
+    <attribute name="requiredFeatures" type="svg:FeatureListType" use="optional"/>\r
+    <attribute name="requiredExtensions" type="svg:ExtensionListType" use="optional"/>\r
+    <attribute name="systemLanguage" type="svg:LanguageCodesType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="xlinkRefAttrs">\r
+    <annotation>\r
+      <documentation>For most uses of URI referencing: standard XLink attributes other than xlink:href</documentation>\r
+    </annotation>\r
+    <attribute ref="xlink:type" fixed="simple"/>\r
+    <attribute ref="xlink:role"/>\r
+    <attribute ref="xlink:arcrole"/>\r
+    <attribute ref="xlink:title"/>\r
+    <attribute ref="xlink:show" default="other"/>\r
+    <attribute ref="xlink:actuate"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="xlinkRefAttrsEmbed">\r
+    <annotation>\r
+      <documentation>Standard XLink attributes for uses of URI referencing where xlink:show is 'embed'</documentation>\r
+    </annotation>\r
+    <attribute ref="xlink:type" fixed="simple"/>\r
+    <attribute ref="xlink:role"/>\r
+    <attribute ref="xlink:arcrole"/>\r
+    <attribute ref="xlink:title"/>\r
+    <attribute ref="xlink:show"/>\r
+    <attribute ref="xlink:actuate"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="graphicsElementEvents">\r
+    <attribute name="onfocusin" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onfocusout" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onactivate" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onclick" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onmousedown" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onmouseup" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onmouseover" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onmousemove" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onmouseout" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onload" type="svg:ScriptType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="documentEvents">\r
+    <attribute name="onunload" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onabort" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onerror" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onresize" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onscroll" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onzoom" type="svg:ScriptType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="animationEvents">\r
+    <attribute name="onbegin" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onend" type="svg:ScriptType" use="optional"/>\r
+    <attribute name="onrepeat" type="svg:ScriptType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Color">\r
+    <annotation>\r
+      <documentation>The following presentation attributes have to do with specifying color.</documentation>\r
+    </annotation>\r
+    <attribute name="color" type="svg:ColorType" use="optional"/>\r
+    <attribute name="color-interpolation" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="sRGB"/>\r
+          <enumeration value="linearRGB"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="color-rendering" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="optimizeSpeed"/>\r
+          <enumeration value="optimizeQuality"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Containers">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to container elements</documentation>\r
+    </annotation>\r
+    <attribute name="enable-background" type="svg:EnableBackgroundValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-feFlood">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to 'feFlood' elements</documentation>\r
+    </annotation>\r
+    <attribute name="flood-color" type="svg:SVGColorType" use="optional"/>\r
+    <attribute name="flood-opacity" type="svg:OpacityValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-FilterPrimitives">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to filter primitives</documentation>\r
+    </annotation>\r
+    <attribute name="color-interpolation-filters" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="sRGB"/>\r
+          <enumeration value="linearRGB"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-FillStroke">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to filling and stroking operations</documentation>\r
+    </annotation>\r
+    <attribute name="fill" type="svg:PaintType" use="optional"/>\r
+    <attribute name="fill-opacity" type="svg:OpacityValueType" use="optional"/>\r
+    <attribute name="fill-rule" type="svg:ClipFillRuleType" use="optional"/>\r
+    <attribute name="stroke" type="svg:PaintType" use="optional"/>\r
+    <attribute name="stroke-dasharray" type="svg:StrokeDashArrayValueType" use="optional"/>\r
+    <attribute name="stroke-dashoffset" type="svg:StrokeDashOffsetValueType" use="optional"/>\r
+    <attribute name="stroke-linecap" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="butt"/>\r
+          <enumeration value="round"/>\r
+          <enumeration value="square"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="stroke-linejoin" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="miter"/>\r
+          <enumeration value="round"/>\r
+          <enumeration value="bevel"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="stroke-miterlimit" type="svg:StrokeMiterLimitValueType" use="optional"/>\r
+    <attribute name="stroke-opacity" type="svg:OpacityValueType" use="optional"/>\r
+    <attribute name="stroke-width" type="svg:StrokeWidthValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-FontSpecification">\r
+    <annotation>\r
+      <documentation>The following presentation attributes have to do with selecting a font to use</documentation>\r
+    </annotation>\r
+    <attribute name="font-family" type="svg:FontFamilyValueType" use="optional"/>\r
+    <attribute name="font-size" type="svg:FontSizeValueType" use="optional"/>\r
+    <attribute name="font-size-adjust" type="svg:FontSizeAdjustValueType" use="optional"/>\r
+    <attribute name="font-stretch" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="wider"/>\r
+          <enumeration value="narrower"/>\r
+          <enumeration value="ultra-condensed"/>\r
+          <enumeration value="extra-condensed"/>\r
+          <enumeration value="condensed"/>\r
+          <enumeration value="semi-condensed"/>\r
+          <enumeration value="semi-expanded"/>\r
+          <enumeration value="expanded"/>\r
+          <enumeration value="extra-expanded"/>\r
+          <enumeration value="ultra-expanded"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="font-style" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="italic"/>\r
+          <enumeration value="oblique"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="font-variant" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="small-caps"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="font-weight" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="bold"/>\r
+          <enumeration value="bolder"/>\r
+          <enumeration value="lighter"/>\r
+          <enumeration value="100"/>\r
+          <enumeration value="200"/>\r
+          <enumeration value="300"/>\r
+          <enumeration value="400"/>\r
+          <enumeration value="500"/>\r
+          <enumeration value="600"/>\r
+          <enumeration value="700"/>\r
+          <enumeration value="800"/>\r
+          <enumeration value="900"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Gradients">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to gradient 'stop' elements</documentation>\r
+    </annotation>\r
+    <attribute name="stop-color" type="svg:SVGColorType" use="optional"/>\r
+    <attribute name="stop-opacity" type="svg:OpacityValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Graphics">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to graphics elements</documentation>\r
+    </annotation>\r
+    <attribute name="clip-path" type="svg:ClipPathValueType" use="optional"/>\r
+    <attribute name="clip-rule" type="svg:ClipFillRuleType" use="optional"/>\r
+    <attribute name="cursor" type="svg:CursorValueType" use="optional"/>\r
+    <attribute name="display" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="inline"/>\r
+          <enumeration value="block"/>\r
+          <enumeration value="list-item"/>\r
+          <enumeration value="run-in"/>\r
+          <enumeration value="compact"/>\r
+          <enumeration value="marker"/>\r
+          <enumeration value="table"/>\r
+          <enumeration value="inline-table"/>\r
+          <enumeration value="table-row-group"/>\r
+          <enumeration value="table-header-group"/>\r
+          <enumeration value="table-footer-group"/>\r
+          <enumeration value="table-row"/>\r
+          <enumeration value="table-column-group"/>\r
+          <enumeration value="table-column"/>\r
+          <enumeration value="table-cell"/>\r
+          <enumeration value="table-caption"/>\r
+          <enumeration value="none"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="filter" type="svg:FilterValueType" use="optional"/>\r
+    <attribute name="image-rendering" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="optimizeSpeed"/>\r
+          <enumeration value="optimizeQuality"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="mask" type="svg:MaskValueType" use="optional"/>\r
+    <attribute name="opacity" type="svg:OpacityValueType" use="optional"/>\r
+    <attribute name="pointer-events" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="visiblePainted"/>\r
+          <enumeration value="visibleFill"/>\r
+          <enumeration value="visibleStroke"/>\r
+          <enumeration value="visibleFillStroke"/>\r
+          <enumeration value="visible"/>\r
+          <enumeration value="painted"/>\r
+          <enumeration value="fill"/>\r
+          <enumeration value="stroke"/>\r
+          <enumeration value="fillstroke"/>\r
+          <enumeration value="all"/>\r
+          <enumeration value="none"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="shape-rendering" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="optimizeSpeed"/>\r
+          <enumeration value="crispEdges"/>\r
+          <enumeration value="geometricPrecision"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="text-rendering" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="optimizeSpeed"/>\r
+          <enumeration value="optimizeLegibility"/>\r
+          <enumeration value="geometricPrecision"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="visibility" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="visible"/>\r
+          <enumeration value="hidden"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Images">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to 'image' elements</documentation>\r
+    </annotation>\r
+    <attribute name="color-profile" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-LightingEffects">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to 'feDiffuseLighting' and 'feSpecularLighting' elements</documentation>\r
+    </annotation>\r
+    <attribute name="lighting-color" type="svg:SVGColorType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Markers">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to marker operations</documentation>\r
+    </annotation>\r
+    <attribute name="marker-start" type="svg:MarkerValueType" use="optional"/>\r
+    <attribute name="marker-mid" type="svg:MarkerValueType" use="optional"/>\r
+    <attribute name="marker-end" type="svg:MarkerValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-TextContentElements">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to text content elements</documentation>\r
+    </annotation>\r
+    <attribute name="alignment-baseline" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="baseline"/>\r
+          <enumeration value="top"/>\r
+          <enumeration value="before-edge"/>\r
+          <enumeration value="text-top"/>\r
+          <enumeration value="text-before-edge"/>\r
+          <enumeration value="middle"/>\r
+          <enumeration value="bottom"/>\r
+          <enumeration value="after-edge"/>\r
+          <enumeration value="text-bottom"/>\r
+          <enumeration value="text-after-edge"/>\r
+          <enumeration value="ideographic"/>\r
+          <enumeration value="lower"/>\r
+          <enumeration value="hanging"/>\r
+          <enumeration value="mathematical"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="baseline-shift" type="svg:BaselineShiftValueType" use="optional"/>\r
+    <attribute name="direction" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="ltr"/>\r
+          <enumeration value="rtl"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="dominant-baseline" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="autosense-script"/>\r
+          <enumeration value="no-change"/>\r
+          <enumeration value="reset"/>\r
+          <enumeration value="ideographic"/>\r
+          <enumeration value="lower"/>\r
+          <enumeration value="hanging"/>\r
+          <enumeration value="mathematical"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="glyph-orientation-horizontal" type="svg:GlyphOrientationHorizontalValueType" use="optional"/>\r
+    <attribute name="glyph-orientation-vertical" type="svg:GlyphOrientationVerticalValueType" use="optional"/>\r
+    <attribute name="letter-spacing" type="svg:SpacingValueType" use="optional"/>\r
+    <attribute name="text-anchor" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="start"/>\r
+          <enumeration value="middle"/>\r
+          <enumeration value="end"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="text-decoration" type="svg:TextDecorationValueType" use="optional"/>\r
+    <attribute name="unicode-bidi" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="embed"/>\r
+          <enumeration value="bidi-override"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="word-spacing" type="svg:SpacingValueType" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-TextElements">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to 'text' elements</documentation>\r
+    </annotation>\r
+    <attribute name="writing-mode" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="lr-tb"/>\r
+          <enumeration value="rl-tb"/>\r
+          <enumeration value="tb-rl"/>\r
+          <enumeration value="lr"/>\r
+          <enumeration value="rl"/>\r
+          <enumeration value="tb"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-Viewports">\r
+    <annotation>\r
+      <documentation>The following presentation attributes apply to elements that establish viewports</documentation>\r
+    </annotation>\r
+    <attribute name="clip" type="svg:ClipValueType" use="optional"/>\r
+    <attribute name="overflow" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="visible"/>\r
+          <enumeration value="hidden"/>\r
+          <enumeration value="scroll"/>\r
+          <enumeration value="auto"/>\r
+          <enumeration value="inherit"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="PresentationAttributes-All">\r
+    <annotation>\r
+      <documentation>The following represents the complete list of presentation attributes</documentation>\r
+    </annotation>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Containers"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-feFlood"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FilterPrimitives"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Gradients"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Images"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Viewports"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="filter_primitive_attributes">\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="optional"/>\r
+    <attribute name="height" type="svg:LengthType" use="optional"/>\r
+    <attribute name="result" type="string" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="filter_primitive_attributes_with_in">\r
+    <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+    <attribute name="in" type="string" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="component_transfer_function_attributes">\r
+    <attribute name="type" use="required">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="identity"/>\r
+          <enumeration value="table"/>\r
+          <enumeration value="discrete"/>\r
+          <enumeration value="linear"/>\r
+          <enumeration value="gamma"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="tableValues" type="string" use="optional"/>\r
+    <attribute name="slope" type="double" use="optional"/>\r
+    <attribute name="intercept" type="double" use="optional"/>\r
+    <attribute name="amplitude" type="double" use="optional"/>\r
+    <attribute name="exponent" type="double" use="optional"/>\r
+    <attribute name="offset" type="double" use="optional"/>\r
+    <!-- here -->\r
+  </attributeGroup>\r
+  <attributeGroup name="animElementAttrs">\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <!-- HTMLBook Note: Dropped invalid type attr on below element -->\r
+    <attribute ref="xlink:href" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="animAttributeAttrs">\r
+    <attribute name="attributeName" type="string" use="required"/>\r
+    <attribute name="attributeType" type="string" use="optional"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="animTargetAttrs">\r
+    <attributeGroup ref="svg:animElementAttrs"/>\r
+    <attributeGroup ref="svg:animAttributeAttrs"/>\r
+  </attributeGroup>\r
+  <attributeGroup name="animTimingAttrs">\r
+    <attribute name="begin" type="string" use="optional"/>\r
+    <attribute name="dur" type="string" use="optional"/>\r
+    <attribute name="end" type="string" use="optional"/>\r
+    <attribute name="min" type="string" use="optional"/>\r
+    <attribute name="max" type="string" use="optional"/>\r
+    <attribute name="restart" default="always">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="always"/>\r
+          <enumeration value="never"/>\r
+          <enumeration value="whenNotActive"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="repeatCount" type="string" use="optional"/>\r
+    <attribute name="repeatDur" type="string" use="optional"/>\r
+    <attribute name="fill" default="remove">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="remove"/>\r
+          <enumeration value="freeze"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <attributeGroup name="animValueAttrs">\r
+    <attribute name="calcMode" default="linear">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="discrete"/>\r
+          <enumeration value="linear"/>\r
+          <enumeration value="paced"/>\r
+          <enumeration value="spline"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="values" type="string" use="optional"/>\r
+    <attribute name="keyTimes" type="string" use="optional"/>\r
+    <attribute name="keySplines" type="string" use="optional"/>\r
+    <attribute name="from" type="string" use="optional"/>\r
+    <attribute name="to" type="string" use="optional"/>\r
+    <attribute name="by" type="string" use="optional"/>\r
+    <!-- could add a pattern facet here -->\r
+  </attributeGroup>\r
+  <attributeGroup name="animAdditionAttrs">\r
+    <attribute name="additive" default="replace">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="replace"/>\r
+          <enumeration value="sum"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="accumulate" default="none">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="none"/>\r
+          <enumeration value="sum"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </attributeGroup>\r
+  <group name="descTitleMetadata">\r
+    <annotation>\r
+      <!-- HTMLBook Note: switched here from invalid xs:all based content model (which can't be used for particles as was being done)\r
+           to xs:choice-based content model, which allows for one or more desc/title/metadata elements at the beginning of all content models in which \r
+           descTitleMetadata is referenced -->\r
+      <documentation>A bit simpler than the DTD, but see commented-out alternative</documentation>\r
+    </annotation>\r
+    <choice>\r
+      <element name="desc" type="svg:descType"/>\r
+      <element name="title" type="svg:titleType"/>\r
+      <element name="metadata" type="svg:metadataType"/>\r
+    </choice>\r
+  </group>\r
+  <!--\r
+       <group name="descTitleMetadata">\r
+       <annotation>\r
+                       <documentation>Captures the ordering restrictions of the DTD, but suffers from over complexity. No easy way to express this without a wrapper element.</documentation>\r
+               </annotation>\r
+               <choice minOccurs="0" maxOccurs="1">\r
+                       <sequence>\r
+                               <element name="desc" type="svg:descType"/>\r
+                               <choice minOccurs="0" maxOccurs="1">\r
+                                       <sequence>\r
+                                               <element name="title" type="svg:titleType"/>\r
+                                               <element name="metadata" type="svg:metadataType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                                       <sequence>\r
+                                               <element name="metadata" type="svg:metadataType"/>\r
+                                               <element name="title" type="svg:titleType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                               </choice>\r
+                       </sequence>\r
+                       <sequence>\r
+                               <element name="title" type="svg:titleType"/>\r
+                               <choice minOccurs="0" maxOccurs="1">\r
+                                       <sequence>\r
+                                               <element name="desc" type="svg:descType"/>\r
+                                               <element name="metadata" type="svg:metadataType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                                       <sequence>\r
+                                               <element name="metadata" type="svg:metadataType"/>\r
+                                               <element name="desc" type="svg:descType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                               </choice>\r
+                       </sequence>\r
+                       <sequence>\r
+                               <element name="metadata" type="svg:metadataType"/>\r
+                               <choice minOccurs="0" maxOccurs="1">\r
+                                       <sequence>\r
+                                               <element name="desc" type="svg:descType"/>\r
+                                               <element name="title" type="svg:titleType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                                       <sequence>\r
+                                               <element name="title" type="svg:titleType"/>\r
+                                               <element name="desc" type="svg:descType" minOccurs="0" maxOccurs="1"/>\r
+                                       </sequence>\r
+                               </choice>\r
+                       </sequence>\r
+               </choice>\r
+       </group>\r
+-->\r
+  <element name="svg" type="svg:svgType"/>\r
+  <element name="g" type="svg:gType"/>\r
+  <element name="defs" type="svg:defsType"/>\r
+  <element name="desc" type="svg:descType"/>\r
+  <element name="title" type="svg:titleType"/>\r
+  <element name="symbol" type="svg:symbolType"/>\r
+  <element name="use" type="svg:useType"/>\r
+  <element name="image" type="svg:imageType"/>\r
+  <element name="switch" type="svg:switchType"/>\r
+  <element name="style" type="svg:styleType"/>\r
+  <element name="path" type="svg:pathType"/>\r
+  <element name="rect" type="svg:rectType"/>\r
+  <element name="circle" type="svg:circleType"/>\r
+  <element name="ellipse" type="svg:ellipseType"/>\r
+  <element name="line" type="svg:lineType"/>\r
+  <element name="polyline" type="svg:polylineType"/>\r
+  <element name="polygon" type="svg:polygonType"/>\r
+  <element name="text" type="svg:textType"/>\r
+  <element name="tspan" type="svg:tspanType"/>\r
+  <element name="tref" type="svg:trefType"/>\r
+  <element name="textPath" type="svg:textPathType"/>\r
+  <element name="altGlyph" type="svg:altGlyphType"/>\r
+  <element name="altGlyphDef" type="svg:altGlyphDefType"/>\r
+  <element name="altGlyphItem" type="svg:altGlyphItemType"/>\r
+  <element name="glyphRef" type="svg:glyphRefType"/>\r
+  <element name="marker" type="svg:markerType"/>\r
+  <element name="color-profile" type="svg:color-profileType"/>\r
+  <element name="linearGradient" type="svg:linearGradientType"/>\r
+  <element name="radialGradient" type="svg:radialGradientType"/>\r
+  <element name="stop" type="svg:stopType"/>\r
+  <element name="pattern" type="svg:patternType"/>\r
+  <element name="clipPath" type="svg:clipPathType"/>\r
+  <element name="mask" type="svg:maskType"/>\r
+  <element name="filter" type="svg:filterType"/>\r
+  <element name="feDistantLight" type="svg:feDistantLightType"/>\r
+  <element name="fePointLight" type="svg:fePointLightType"/>\r
+  <element name="feSpotLight" type="svg:feSpotLightType"/>\r
+  <element name="feBlend" type="svg:feBlendType"/>\r
+  <element name="feColorMatrix" type="svg:feColorMatrixType"/>\r
+  <element name="feComponentTransfer" type="svg:feComponentTransferType"/>\r
+  <element name="feFuncR" type="svg:feFuncRType"/>\r
+  <element name="feFuncG" type="svg:feFuncGType"/>\r
+  <element name="feFuncB" type="svg:feFuncBType"/>\r
+  <element name="feFuncA" type="svg:feFuncAType"/>\r
+  <element name="feComposite" type="svg:feCompositeType"/>\r
+  <element name="feConvolveMatrix" type="svg:feConvolveMatrixType"/>\r
+  <element name="feDiffuseLighting" type="svg:feDiffuseLightingType"/>\r
+  <element name="feDisplacementMap" type="svg:feDisplacementMapType"/>\r
+  <element name="feFlood" type="svg:feFloodType"/>\r
+  <element name="feGaussianBlur" type="svg:feGaussianBlurType"/>\r
+  <element name="feImage" type="svg:feImageType"/>\r
+  <element name="feMerge" type="svg:feMergeType"/>\r
+  <element name="feMergeNode" type="svg:feMergeNodeType"/>\r
+  <element name="feMorphology" type="svg:feMorphologyType"/>\r
+  <element name="feOffset" type="svg:feOffsetType"/>\r
+  <element name="feSpecularLighting" type="svg:feSpecularLightingType"/>\r
+  <element name="feTile" type="svg:feTileType"/>\r
+  <element name="feTurbulence" type="svg:feTurbulenceType"/>\r
+  <element name="cursor" type="svg:cursorType"/>\r
+  <element name="a" type="svg:aType"/>\r
+  <element name="view" type="svg:viewType"/>\r
+  <element name="script" type="svg:scriptType"/>\r
+  <element name="animate" type="svg:animateType"/>\r
+  <element name="set" type="svg:setType"/>\r
+  <element name="animateMotion" type="svg:animateMotionType"/>\r
+  <element name="mpath" type="svg:mpathType"/>\r
+  <element name="animateColor" type="svg:animateColorType"/>\r
+  <element name="animateTransform" type="svg:animateTransformType"/>\r
+  <element name="font" type="svg:fontType"/>\r
+  <element name="glyph" type="svg:glyphType"/>\r
+  <element name="missing-glyph" type="svg:missing-glyphType"/>\r
+  <element name="hkern" type="svg:hkernType"/>\r
+  <element name="vkern" type="svg:vkernType"/>\r
+  <element name="font-face" type="svg:font-faceType"/>\r
+  <element name="font-face-src" type="svg:font-face-srcType"/>\r
+  <element name="font-face-uri" type="svg:font-face-uriType"/>\r
+  <element name="font-face-format" type="svg:font-face-formatType"/>\r
+  <element name="font-face-name" type="svg:font-face-nameType"/>\r
+  <element name="definition-src" type="svg:definition-srcType"/>\r
+  <element name="metadata" type="svg:metadataType"/>\r
+  <element name="foreignObject" type="svg:foreignObjectType"/>\r
+  <complexType name="svgType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+      <!-- should this be done with named child element collections? Especially for modularisation. -->\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+    <attribute name="version" type="double" use="optional"/>\r
+    <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+    <attribute name="zoomAndPan" default="magnify">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="disable"/>\r
+          <enumeration value="magnify"/>\r
+          <enumeration value="zoom"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attributeGroup ref="svg:documentEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="required"/>\r
+    <attribute name="height" type="svg:LengthType" use="required"/>\r
+    <attribute name="contentScriptType" type="svg:ContentTypeType" default="text/ecmascript"/>\r
+    <attribute name="contentStyleType" type="svg:ContentTypeType" default="text/css"/>\r
+  </complexType>\r
+  <complexType name="gType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+  </complexType>\r
+  <complexType name="defsType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+  </complexType>\r
+  <complexType name="descType" mixed="true">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attribute name="content" type="string" fixed="structured text"/>\r
+  </complexType>\r
+  <complexType name="titleType" mixed="true">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attribute name="content" type="string" fixed="structured text"/>\r
+  </complexType>\r
+  <complexType name="symbolType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+    <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+  </complexType>\r
+  <complexType name="useType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrsEmbed"/>\r
+    <attribute ref="xlink:href" use="required"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="optional"/>\r
+    <attribute name="height" type="svg:LengthType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="imageType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+        <!-- this should probably be a named element group -->\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Viewports"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="required"/>\r
+    <attribute name="height" type="svg:LengthType" use="required"/>\r
+  </complexType>\r
+  <complexType name="switchType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:path"/>\r
+        <element ref="svg:text"/>\r
+        <element ref="svg:rect"/>\r
+        <element ref="svg:circle"/>\r
+        <element ref="svg:ellipse"/>\r
+        <element ref="svg:line"/>\r
+        <element ref="svg:polyline"/>\r
+        <element ref="svg:polygon"/>\r
+        <element ref="svg:use"/>\r
+        <element ref="svg:image"/>\r
+        <element ref="svg:svg"/>\r
+        <element ref="svg:g"/>\r
+        <element ref="svg:switch"/>\r
+        <element ref="svg:a"/>\r
+        <element ref="svg:foreignObject"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+  </complexType>\r
+  <complexType name="styleType" mixed="true">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute ref="xml:space" fixed="preserve"/>\r
+    <attribute name="type" type="svg:ContentTypeType" use="required"/>\r
+    <attribute name="media" type="svg:MediaDescType" use="optional"/>\r
+    <attribute name="title" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="pathType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="d" type="svg:PathDataType" use="required"/>\r
+    <attribute name="pathLength" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="rectType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="required"/>\r
+    <attribute name="height" type="svg:LengthType" use="required"/>\r
+    <attribute name="rx" type="svg:LengthType" use="optional"/>\r
+    <attribute name="ry" type="svg:LengthType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="circleType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="r" type="svg:LengthType" use="required"/>\r
+  </complexType>\r
+  <complexType name="ellipseType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="rx" type="svg:LengthType" use="required"/>\r
+    <attribute name="ry" type="svg:LengthType" use="required"/>\r
+  </complexType>\r
+  <complexType name="lineType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x1" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y1" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="x2" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y2" type="svg:CoordinateType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="polylineType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="points" type="svg:PointsType" use="required"/>\r
+  </complexType>\r
+  <complexType name="polygonType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Markers"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="points" type="svg:PointsType" use="required"/>\r
+  </complexType>\r
+  <complexType name="textType" mixed="true">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:tspan"/>\r
+      <element ref="svg:tref"/>\r
+      <element ref="svg:textPath"/>\r
+      <element ref="svg:altGlyph"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+    <attribute name="lengthAdjust" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="spacing"/>\r
+          <enumeration value="spacingAndGlyphs"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="tspanType" mixed="true">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:tspan"/>\r
+      <element ref="svg:tref"/>\r
+      <element ref="svg:altGlyph"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateColor"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="rotate" type="string" use="optional"/>\r
+    <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+    <attribute name="lengthAdjust" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="spacing"/>\r
+          <enumeration value="spacingAndGlyphs"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="trefType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateColor"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="rotate" type="string" use="optional"/>\r
+    <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+    <attribute name="lengthAdjust" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="spacing"/>\r
+          <enumeration value="spacingAndGlyphs"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="textPathType" mixed="true">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:tspan"/>\r
+      <element ref="svg:tref"/>\r
+      <element ref="svg:altGlyph"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateColor"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="startOffset" type="string" use="optional"/>\r
+    <attribute name="textLength" type="svg:LengthType" use="optional"/>\r
+    <attribute name="lengthAdjust" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="spacing"/>\r
+          <enumeration value="spacingAndGlyphs"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="method" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="align"/>\r
+          <enumeration value="stretch"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="spacing" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="exact"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="altGlyphType" mixed="true">\r
+    <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="glyphRef" type="string" use="optional"/>\r
+    <attribute name="format" type="string" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Color"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="rotate" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="altGlyphDefType">\r
+    <choice maxOccurs="unbounded">\r
+      <element ref="svg:altGlyphItem"/>\r
+      <element ref="svg:glyphRef"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+  </complexType>\r
+  <complexType name="altGlyphItemType">\r
+    <sequence maxOccurs="unbounded">\r
+      <element ref="svg:glyphRef"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+  </complexType>\r
+  <complexType name="glyphRefType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attribute name="glyphRef" type="string" use="required"/>\r
+    <attribute name="format" type="string" use="required"/>\r
+    <attribute name="x" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinatesType" use="optional"/>\r
+    <attribute name="dx" type="svg:LengthsType" use="optional"/>\r
+    <attribute name="dy" type="svg:LengthsType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="markerType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+    <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+    <attribute name="refX" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="refY" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="markerUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="strokeWidth"/>\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="markerWidth" type="svg:LengthType" use="optional"/>\r
+    <attribute name="markerHeight" type="svg:LengthType" use="optional"/>\r
+    <attribute name="orient" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="color-profileType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="local" type="string" use="optional"/>\r
+    <attribute name="name" type="string" use="required"/>\r
+    <attribute name="rendering-intent" default="auto">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="auto"/>\r
+          <enumeration value="perceptual"/>\r
+          <enumeration value="relative-colorimetric"/>\r
+          <enumeration value="saturation"/>\r
+          <enumeration value="absolute-colorimetric"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="linearGradientType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:stop"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attribute name="gradientUnits" use="optional"><!-- @@ need to add more attributes here @@ -->\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="gradientTransform" type="svg:TransformListType" use="optional"/>\r
+    <attribute name="x1" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y1" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="x2" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y2" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="spreadMethod" default="pad">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="pad"/>\r
+          <enumeration value="reflect"/>\r
+          <enumeration value="repeat"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="radialGradientType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:stop"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="gradientUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="gradientTransform" type="svg:TransformListType" use="optional"/>\r
+    <attribute name="cx" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="cy" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="r" type="svg:LengthType" use="optional"/>\r
+    <attribute name="fx" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="fy" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="spreadMethod" default="pad">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="pad"/>\r
+          <enumeration value="reflect"/>\r
+          <enumeration value="repeat"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="stopType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateColor"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Gradients"/>\r
+    <attribute name="offset" type="svg:LengthType" use="required"/>\r
+  </complexType>\r
+  <complexType name="patternType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+    <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+    <attribute name="patternUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="patternTransform" type="svg:TransformListType" use="optional"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="required"/>\r
+    <attribute name="height" type="svg:LengthType" use="required"/>\r
+  </complexType>\r
+  <complexType name="clipPathType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:path"/>\r
+        <element ref="svg:text"/>\r
+        <element ref="svg:rect"/>\r
+        <element ref="svg:circle"/>\r
+        <element ref="svg:ellipse"/>\r
+        <element ref="svg:line"/>\r
+        <element ref="svg:polyline"/>\r
+        <element ref="svg:polygon"/>\r
+        <element ref="svg:use"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FillStroke"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-FontSpecification"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-Graphics"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextContentElements"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-TextElements"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attribute name="clipPathUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="maskType">\r
+    <sequence>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:desc"/>\r
+        <element ref="svg:title"/>\r
+        <element ref="svg:metadata"/>\r
+        <element ref="svg:defs"/>\r
+        <element ref="svg:path"/>\r
+        <element ref="svg:text"/>\r
+        <element ref="svg:rect"/>\r
+        <element ref="svg:circle"/>\r
+        <element ref="svg:ellipse"/>\r
+        <element ref="svg:line"/>\r
+        <element ref="svg:polyline"/>\r
+        <element ref="svg:polygon"/>\r
+        <element ref="svg:use"/>\r
+        <element ref="svg:image"/>\r
+        <element ref="svg:svg"/>\r
+        <element ref="svg:g"/>\r
+        <element ref="svg:view"/>\r
+        <element ref="svg:switch"/>\r
+        <element ref="svg:a"/>\r
+        <element ref="svg:altGlyphDef"/>\r
+        <element ref="svg:script"/>\r
+        <element ref="svg:style"/>\r
+        <element ref="svg:symbol"/>\r
+        <element ref="svg:marker"/>\r
+        <element ref="svg:clipPath"/>\r
+        <element ref="svg:mask"/>\r
+        <element ref="svg:linearGradient"/>\r
+        <element ref="svg:radialGradient"/>\r
+        <element ref="svg:pattern"/>\r
+        <element ref="svg:filter"/>\r
+        <element ref="svg:cursor"/>\r
+        <element ref="svg:font"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateMotion"/>\r
+        <element ref="svg:animateColor"/>\r
+        <element ref="svg:animateTransform"/>\r
+        <element ref="svg:color-profile"/>\r
+        <element ref="svg:font-face"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attribute name="maskUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="optional"/>\r
+    <attribute name="height" type="svg:LengthType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="filterType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:feBlend"/>\r
+        <element ref="svg:feFlood"/>\r
+        <element ref="svg:feColorMatrix"/>\r
+        <element ref="svg:feComponentTransfer"/>\r
+        <element ref="svg:feComposite"/>\r
+        <element ref="svg:feConvolveMatrix"/>\r
+        <element ref="svg:feDiffuseLighting"/>\r
+        <element ref="svg:feDisplacementMap"/>\r
+        <element ref="svg:feGaussianBlur"/>\r
+        <element ref="svg:feImage"/>\r
+        <element ref="svg:feMerge"/>\r
+        <element ref="svg:feMorphology"/>\r
+        <element ref="svg:feOffset"/>\r
+        <element ref="svg:feSpecularLighting"/>\r
+        <element ref="svg:feTile"/>\r
+        <element ref="svg:feTurbulence"/>\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="filterUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="primitiveUnits" use="optional">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="userSpaceOnUse"/>\r
+          <enumeration value="userSpace"/>\r
+          <enumeration value="objectBoundingBox"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="optional"/>\r
+    <attribute name="height" type="svg:LengthType" use="optional"/>\r
+    <attribute name="filterRes" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feDistantLightType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="azimuth" type="double" use="optional"/>\r
+    <attribute name="elevation" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="fePointLightType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="x" type="double" use="optional"/>\r
+    <attribute name="y" type="double" use="optional"/>\r
+    <attribute name="z" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feSpotLightType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="x" type="double" use="optional"/>\r
+    <attribute name="y" type="double" use="optional"/>\r
+    <attribute name="z" type="double" use="optional"/>\r
+    <attribute name="pointsAtX" type="double" use="optional"/>\r
+    <attribute name="pointsAtY" type="double" use="optional"/>\r
+    <attribute name="pointsAtZ" type="double" use="optional"/>\r
+    <attribute name="specularExponent" type="double" use="optional"/>\r
+    <attribute name="limitingConeAngle" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feBlendType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="in2" type="string" use="required"/>\r
+    <attribute name="mode" default="normal">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="normal"/>\r
+          <enumeration value="multiply"/>\r
+          <enumeration value="screen"/>\r
+          <enumeration value="darken"/>\r
+          <enumeration value="lighten"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="feColorMatrixType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="type" default="matrix">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="matrix"/>\r
+          <enumeration value="saturate"/>\r
+          <enumeration value="hueRotate"/>\r
+          <enumeration value="luminanceToAlpha"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="values" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feComponentTransferType">\r
+    <sequence>\r
+      <element ref="svg:feFuncR" minOccurs="0"/>\r
+      <element ref="svg:feFuncG" minOccurs="0"/>\r
+      <element ref="svg:feFuncB" minOccurs="0"/>\r
+      <element ref="svg:feFuncA" minOccurs="0"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+  </complexType>\r
+  <complexType name="feFuncRType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+  </complexType>\r
+  <complexType name="feFuncGType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+  </complexType>\r
+  <complexType name="feFuncBType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+  </complexType>\r
+  <complexType name="feFuncAType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:component_transfer_function_attributes"/>\r
+  </complexType>\r
+  <complexType name="feCompositeType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="in2" type="string" use="required"/>\r
+    <attribute name="operator" default="over">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="over"/>\r
+          <enumeration value="in"/>\r
+          <enumeration value="out"/>\r
+          <enumeration value="atop"/>\r
+          <enumeration value="xor"/>\r
+          <enumeration value="arithmetic"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="k1" type="double" use="optional"/>\r
+    <attribute name="k2" type="double" use="optional"/>\r
+    <attribute name="k3" type="double" use="optional"/>\r
+    <attribute name="k4" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feConvolveMatrixType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="order" type="string" use="required"/>\r
+    <attribute name="kernelMatrix" type="string" use="required"/>\r
+    <attribute name="divisor" type="double" use="optional"/>\r
+    <attribute name="bias" type="double" use="optional"/>\r
+    <attribute name="targetX" type="integer" use="optional"/>\r
+    <attribute name="targetY" type="integer" use="optional"/>\r
+    <attribute name="edgeMode" default="duplicate">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="duplicate"/>\r
+          <enumeration value="wrap"/>\r
+          <enumeration value="none"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="kernelUnitLength" type="string" use="optional"/>\r
+    <attribute name="preserveAlpha" type="boolean" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feDiffuseLightingType">\r
+    <sequence>\r
+      <choice>\r
+        <element ref="svg:feDistantLight"/>\r
+        <element ref="svg:fePointLight"/>\r
+        <element ref="svg:feSpotLight"/>\r
+      </choice>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateColor"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="surfaceScale" type="double" use="optional"/>\r
+    <attribute name="diffuseConstant" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feDisplacementMapType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="in2" type="string" use="required"/>\r
+    <attribute name="scale" type="double" use="optional"/>\r
+    <attribute name="xChannelSelector" default="A">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="R"/>\r
+          <enumeration value="G"/>\r
+          <enumeration value="B"/>\r
+          <enumeration value="A"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="yChannelSelector" default="A">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="R"/>\r
+          <enumeration value="G"/>\r
+          <enumeration value="B"/>\r
+          <enumeration value="A"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="feFloodType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateColor"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-feFlood"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+  </complexType>\r
+  <complexType name="feGaussianBlurType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="stdDeviation" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feImageType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateTransform"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+  </complexType>\r
+  <complexType name="feMergeType">\r
+    <sequence minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:feMergeNode"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+  </complexType>\r
+  <complexType name="feMergeNodeType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="in" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feMorphologyType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="operator" default="erode">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="erode"/>\r
+          <enumeration value="dilate"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="radius" type="svg:LengthType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feOffsetType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="dx" type="svg:LengthType" use="optional"/>\r
+    <attribute name="dy" type="svg:LengthType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feSpecularLightingType">\r
+    <sequence>\r
+      <choice>\r
+        <element ref="svg:feDistantLight"/>\r
+        <element ref="svg:fePointLight"/>\r
+        <element ref="svg:feSpotLight"/>\r
+      </choice>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:animate"/>\r
+        <element ref="svg:set"/>\r
+        <element ref="svg:animateColor"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-LightingEffects"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+    <attribute name="surfaceScale" type="double" use="optional"/>\r
+    <attribute name="specularConstant" type="double" use="optional"/>\r
+    <attribute name="specularExponent" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="feTileType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes_with_in"/>\r
+  </complexType>\r
+  <complexType name="feTurbulenceType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:filter_primitive_attributes"/>\r
+    <attribute name="baseFrequency" type="string" use="optional"/>\r
+    <attribute name="numOctaves" type="integer" use="optional"/>\r
+    <attribute name="seed" type="double" use="optional"/>\r
+    <attribute name="stitchTiles" default="noStitch">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="stitch"/>\r
+          <enumeration value="noStitch"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="type" default="turbulence">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="fractalNoise"/>\r
+          <enumeration value="turbulence"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="cursorType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+  </complexType>\r
+  <complexType name="aType" mixed="true">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute ref="xlink:type" fixed="simple"/>\r
+    <attribute ref="xlink:role"/>\r
+    <attribute ref="xlink:arcrole"/>\r
+    <attribute ref="xlink:title"/>\r
+    <attribute ref="xlink:show"/>\r
+    <attribute ref="xlink:actuate" fixed="onRequest"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="target" type="NMTOKEN" use="optional"/>\r
+    <!-- don't use attribute declarations to declare namespaces \r
+               attribute ref="xmlns:xlink" type="string" fixed="http://www.w3.org/1999/xlink"/>\r
+               -->\r
+    <!-- change from string to URI -->\r
+  </complexType>\r
+  <complexType name="viewType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="viewBox" type="svg:ViewBoxSpecType" use="optional"/>\r
+    <attribute name="preserveAspectRatio" type="svg:PreserveAspectRatioSpecType" default="xMidYMid meet"/>\r
+    <attribute name="zoomAndPan" default="magnify">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="disable"/>\r
+          <enumeration value="magnify"/>\r
+          <enumeration value="zoom"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+    <attribute name="viewTarget" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="scriptType" mixed="true">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="type" type="svg:ContentTypeType" use="required"/>\r
+  </complexType>\r
+  <complexType name="animateType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attributeGroup ref="svg:animationEvents"/>\r
+    <attributeGroup ref="svg:animTargetAttrs"/>\r
+    <attributeGroup ref="svg:animTimingAttrs"/>\r
+    <attributeGroup ref="svg:animValueAttrs"/>\r
+    <attributeGroup ref="svg:animAdditionAttrs"/>\r
+  </complexType>\r
+  <complexType name="setType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attributeGroup ref="svg:animationEvents"/>\r
+    <attributeGroup ref="svg:animTargetAttrs"/>\r
+    <attributeGroup ref="svg:animTimingAttrs"/>\r
+    <attribute name="to" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="animateMotionType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <element ref="svg:mpath" minOccurs="0"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attributeGroup ref="svg:animationEvents"/>\r
+    <attributeGroup ref="svg:animElementAttrs"/>\r
+    <attributeGroup ref="svg:animTimingAttrs"/>\r
+    <attributeGroup ref="svg:animValueAttrs"/>\r
+    <attributeGroup ref="svg:animAdditionAttrs"/>\r
+    <attribute name="path" type="string" use="optional"/>\r
+    <attribute name="keyPoints" type="string" use="optional"/>\r
+    <attribute name="rotate" type="string" use="optional"/>\r
+    <attribute name="origin" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="mpathType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+  </complexType>\r
+  <complexType name="animateColorType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attributeGroup ref="svg:animationEvents"/>\r
+    <attributeGroup ref="svg:animTargetAttrs"/>\r
+    <attributeGroup ref="svg:animTimingAttrs"/>\r
+    <attributeGroup ref="svg:animValueAttrs"/>\r
+    <attributeGroup ref="svg:animAdditionAttrs"/>\r
+  </complexType>\r
+  <complexType name="animateTransformType">\r
+    <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attributeGroup ref="svg:animationEvents"/>\r
+    <attributeGroup ref="svg:animTargetAttrs"/>\r
+    <attributeGroup ref="svg:animTimingAttrs"/>\r
+    <attributeGroup ref="svg:animValueAttrs"/>\r
+    <attributeGroup ref="svg:animAdditionAttrs"/>\r
+    <attribute name="type" default="translate">\r
+      <simpleType>\r
+        <restriction base="string">\r
+          <enumeration value="translate"/>\r
+          <enumeration value="scale"/>\r
+          <enumeration value="rotate"/>\r
+          <enumeration value="skewX"/>\r
+          <enumeration value="skewY"/>\r
+        </restriction>\r
+      </simpleType>\r
+    </attribute>\r
+  </complexType>\r
+  <complexType name="fontType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <element ref="svg:font-face"/>\r
+      <element ref="svg:missing-glyph"/>\r
+      <choice minOccurs="0" maxOccurs="unbounded">\r
+        <element ref="svg:glyph"/>\r
+        <element ref="svg:hkern"/>\r
+        <element ref="svg:vkern"/>\r
+      </choice>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="horiz-origin-x" type="double" use="optional"/>\r
+    <attribute name="horiz-origin-y" type="double" use="optional"/>\r
+    <attribute name="horiz-adv-x" type="double" use="required"/>\r
+    <attribute name="vert-origin-x" type="double" use="optional"/>\r
+    <attribute name="vert-origin-y" type="double" use="optional"/>\r
+    <attribute name="vert-adv-y" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="glyphType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="unicode" type="string" use="optional"/>\r
+    <attribute name="glyph-name" type="string" use="optional"/>\r
+    <attribute name="d" type="svg:PathDataType" use="optional"/>\r
+    <attribute name="vert-text-orient" type="string" use="optional"/>\r
+    <attribute name="arabic" type="string" use="optional"/>\r
+    <attribute name="han" type="string" use="optional"/>\r
+    <attribute name="horiz-adv-x" type="double" use="optional"/>\r
+    <attribute name="vert-adv-y" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="missing-glyphType">\r
+    <choice minOccurs="0" maxOccurs="unbounded">\r
+      <element ref="svg:desc"/>\r
+      <element ref="svg:title"/>\r
+      <element ref="svg:metadata"/>\r
+      <element ref="svg:defs"/>\r
+      <element ref="svg:path"/>\r
+      <element ref="svg:text"/>\r
+      <element ref="svg:rect"/>\r
+      <element ref="svg:circle"/>\r
+      <element ref="svg:ellipse"/>\r
+      <element ref="svg:line"/>\r
+      <element ref="svg:polyline"/>\r
+      <element ref="svg:polygon"/>\r
+      <element ref="svg:use"/>\r
+      <element ref="svg:image"/>\r
+      <element ref="svg:svg"/>\r
+      <element ref="svg:g"/>\r
+      <element ref="svg:view"/>\r
+      <element ref="svg:switch"/>\r
+      <element ref="svg:a"/>\r
+      <element ref="svg:altGlyphDef"/>\r
+      <element ref="svg:script"/>\r
+      <element ref="svg:style"/>\r
+      <element ref="svg:symbol"/>\r
+      <element ref="svg:marker"/>\r
+      <element ref="svg:clipPath"/>\r
+      <element ref="svg:mask"/>\r
+      <element ref="svg:linearGradient"/>\r
+      <element ref="svg:radialGradient"/>\r
+      <element ref="svg:pattern"/>\r
+      <element ref="svg:filter"/>\r
+      <element ref="svg:cursor"/>\r
+      <element ref="svg:font"/>\r
+      <element ref="svg:animate"/>\r
+      <element ref="svg:set"/>\r
+      <element ref="svg:animateMotion"/>\r
+      <element ref="svg:animateColor"/>\r
+      <element ref="svg:animateTransform"/>\r
+      <element ref="svg:color-profile"/>\r
+      <element ref="svg:font-face"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="d" type="svg:PathDataType" use="optional"/>\r
+    <attribute name="horiz-adv-x" type="double" use="optional"/>\r
+    <attribute name="vert-adv-y" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="hkernType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="u1" type="string" use="optional"/>\r
+    <attribute name="g1" type="string" use="optional"/>\r
+    <attribute name="u2" type="string" use="optional"/>\r
+    <attribute name="g2" type="string" use="optional"/>\r
+    <attribute name="k" type="double" use="required"/>\r
+  </complexType>\r
+  <complexType name="vkernType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="u1" type="string" use="optional"/>\r
+    <attribute name="g1" type="string" use="optional"/>\r
+    <attribute name="u2" type="string" use="optional"/>\r
+    <attribute name="g2" type="string" use="optional"/>\r
+    <attribute name="k" type="double" use="required"/>\r
+  </complexType>\r
+  <complexType name="font-faceType">\r
+    <sequence>\r
+      <group ref="svg:descTitleMetadata" minOccurs="0" maxOccurs="unbounded"/>\r
+      <element ref="svg:font-face-src"/>\r
+      <element ref="svg:definition-src"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="font-family" type="svg:FontFamilyValueType" use="optional"/>\r
+    <attribute name="font-style" type="string" use="optional"/>\r
+    <attribute name="font-variant" type="string" use="optional"/>\r
+    <attribute name="font-weight" type="string" use="optional"/>\r
+    <attribute name="font-stretch" type="string" use="optional"/>\r
+    <attribute name="font-size" type="svg:FontSizeValueType" use="optional"/>\r
+    <attribute name="unicode-range" type="string" use="optional"/>\r
+    <attribute name="units-per-em" type="double" use="optional"/>\r
+    <attribute name="panose-1" type="string" use="optional"/>\r
+    <attribute name="stemv" type="double" use="optional"/>\r
+    <attribute name="stemh" type="double" use="optional"/>\r
+    <attribute name="slope" type="double" use="optional"/>\r
+    <attribute name="cap-height" type="double" use="optional"/>\r
+    <attribute name="x-height" type="double" use="optional"/>\r
+    <attribute name="accent-height" type="double" use="optional"/>\r
+    <attribute name="ascent" type="double" use="optional"/>\r
+    <attribute name="descent" type="double" use="optional"/>\r
+    <attribute name="widths" type="string" use="optional"/>\r
+    <attribute name="bbox" type="string" use="optional"/>\r
+    <attribute name="ideographic" type="double" use="optional"/>\r
+    <attribute name="baseline" type="double" use="optional"/>\r
+    <attribute name="centerline" type="double" use="optional"/>\r
+    <attribute name="mathline" type="double" use="optional"/>\r
+    <attribute name="hanging" type="double" use="optional"/>\r
+    <attribute name="topline" type="double" use="optional"/>\r
+    <attribute name="underline-position" type="double" use="optional"/>\r
+    <attribute name="underline-thickness" type="double" use="optional"/>\r
+    <attribute name="strikethrough-position" type="double" use="optional"/>\r
+    <attribute name="strikethrough-thickness" type="double" use="optional"/>\r
+    <attribute name="overline-position" type="double" use="optional"/>\r
+    <attribute name="overline-thickness" type="double" use="optional"/>\r
+  </complexType>\r
+  <complexType name="font-face-srcType">\r
+    <choice maxOccurs="unbounded">\r
+      <element ref="svg:font-face-uri"/>\r
+      <element ref="svg:font-face-name"/>\r
+    </choice>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+  </complexType>\r
+  <complexType name="font-face-uriType">\r
+    <sequence>\r
+      <element ref="svg:font-face-format"/>\r
+    </sequence>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+  </complexType>\r
+  <complexType name="font-face-formatType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="string" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="font-face-nameType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attribute name="name" type="string" use="optional"/>\r
+  </complexType>\r
+  <complexType name="definition-srcType">\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:xlinkRefAttrs"/>\r
+    <attribute ref="xlink:href" use="optional"/>\r
+  </complexType>\r
+  <complexType name="metadataType" mixed="true">\r
+    <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+  </complexType>\r
+  <complexType name="foreignObjectType" mixed="true">\r
+    <sequence minOccurs="0" maxOccurs="unbounded"/>\r
+    <attributeGroup ref="svg:stdAttrs"/>\r
+    <attributeGroup ref="svg:testAttrs"/>\r
+    <attributeGroup ref="svg:langSpaceAttrs"/>\r
+    <attribute name="externalResourcesRequired" type="boolean" use="optional"/>\r
+    <attribute name="class" type="svg:ClassListType" use="optional"/>\r
+    <attribute name="style" type="svg:StyleSheetType" use="optional"/>\r
+    <attributeGroup ref="svg:PresentationAttributes-All"/>\r
+    <attribute name="transform" type="svg:TransformListType" use="optional"/>\r
+    <attributeGroup ref="svg:graphicsElementEvents"/>\r
+    <attribute name="x" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="y" type="svg:CoordinateType" use="optional"/>\r
+    <attribute name="width" type="svg:LengthType" use="required"/>\r
+    <attribute name="height" type="svg:LengthType" use="required"/>\r
+    <attribute name="content" type="string" fixed="structured text"/>\r
+  </complexType>\r
+</schema>\r
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.dtd
new file mode 100644 (file)
index 0000000..e8e8f76
--- /dev/null
@@ -0,0 +1,402 @@
+<!-- DTD for XML Schemas: Part 1: Structures
+     Public Identifier: "-//W3C//DTD XMLSCHEMA 200102//EN"
+     Official Location: http://www.w3.org/2001/XMLSchema.dtd -->
+<!-- $Id: XMLSchema.dtd,v 1.31 2001/10/24 15:50:16 ht Exp $ -->
+<!-- Note this DTD is NOT normative, or even definitive. -->           <!--d-->
+<!-- prose copy in the structures REC is the definitive version -->    <!--d-->
+<!-- (which shouldn't differ from this one except for this -->         <!--d-->
+<!-- comment and entity expansions, but just in case) -->              <!--d-->
+<!-- With the exception of cases with multiple namespace
+     prefixes for the XML Schema namespace, any XML document which is
+     not valid per this DTD given redefinitions in its internal subset of the
+     'p' and 's' parameter entities below appropriate to its namespace
+     declaration of the XML Schema namespace is almost certainly not
+     a valid schema. -->
+
+<!-- The simpleType element and its constituent parts
+     are defined in XML Schema: Part 2: Datatypes -->
+<!ENTITY % xs-datatypes PUBLIC 'datatypes' 'datatypes.dtd' >
+
+<!ENTITY % p 'xs:'> <!-- can be overriden in the internal subset of a
+                         schema document to establish a different
+                         namespace prefix -->
+<!ENTITY % s ':xs'> <!-- if %p is defined (e.g. as foo:) then you must
+                         also define %s as the suffix for the appropriate
+                         namespace declaration (e.g. :foo) -->
+<!ENTITY % nds 'xmlns%s;'>
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % schema "%p;schema">
+<!ENTITY % complexType "%p;complexType">
+<!ENTITY % complexContent "%p;complexContent">
+<!ENTITY % simpleContent "%p;simpleContent">
+<!ENTITY % extension "%p;extension">
+<!ENTITY % element "%p;element">
+<!ENTITY % unique "%p;unique">
+<!ENTITY % key "%p;key">
+<!ENTITY % keyref "%p;keyref">
+<!ENTITY % selector "%p;selector">
+<!ENTITY % field "%p;field">
+<!ENTITY % group "%p;group">
+<!ENTITY % all "%p;all">
+<!ENTITY % choice "%p;choice">
+<!ENTITY % sequence "%p;sequence">
+<!ENTITY % any "%p;any">
+<!ENTITY % anyAttribute "%p;anyAttribute">
+<!ENTITY % attribute "%p;attribute">
+<!ENTITY % attributeGroup "%p;attributeGroup">
+<!ENTITY % include "%p;include">
+<!ENTITY % import "%p;import">
+<!ENTITY % redefine "%p;redefine">
+<!ENTITY % notation "%p;notation">
+
+<!-- annotation elements -->
+<!ENTITY % annotation "%p;annotation">
+<!ENTITY % appinfo "%p;appinfo">
+<!ENTITY % documentation "%p;documentation">
+
+<!-- Customisation entities for the ATTLIST of each element type.
+     Define one of these if your schema takes advantage of the
+     anyAttribute='##other' in the schema for schemas -->
+
+<!ENTITY % schemaAttrs ''>
+<!ENTITY % complexTypeAttrs ''>
+<!ENTITY % complexContentAttrs ''>
+<!ENTITY % simpleContentAttrs ''>
+<!ENTITY % extensionAttrs ''>
+<!ENTITY % elementAttrs ''>
+<!ENTITY % groupAttrs ''>
+<!ENTITY % allAttrs ''>
+<!ENTITY % choiceAttrs ''>
+<!ENTITY % sequenceAttrs ''>
+<!ENTITY % anyAttrs ''>
+<!ENTITY % anyAttributeAttrs ''>
+<!ENTITY % attributeAttrs ''>
+<!ENTITY % attributeGroupAttrs ''>
+<!ENTITY % uniqueAttrs ''>
+<!ENTITY % keyAttrs ''>
+<!ENTITY % keyrefAttrs ''>
+<!ENTITY % selectorAttrs ''>
+<!ENTITY % fieldAttrs ''>
+<!ENTITY % includeAttrs ''>
+<!ENTITY % importAttrs ''>
+<!ENTITY % redefineAttrs ''>
+<!ENTITY % notationAttrs ''>
+<!ENTITY % annotationAttrs ''>
+<!ENTITY % appinfoAttrs ''>
+<!ENTITY % documentationAttrs ''>
+
+<!ENTITY % complexDerivationSet "CDATA">
+      <!-- #all or space-separated list drawn from derivationChoice -->
+<!ENTITY % blockSet "CDATA">
+      <!-- #all or space-separated list drawn from
+                      derivationChoice + 'substitution' -->
+
+<!ENTITY % mgs '%all; | %choice; | %sequence;'>
+<!ENTITY % cs '%choice; | %sequence;'>
+<!ENTITY % formValues '(qualified|unqualified)'>
+
+
+<!ENTITY % attrDecls    '((%attribute;| %attributeGroup;)*,(%anyAttribute;)?)'>
+
+<!ENTITY % particleAndAttrs '((%mgs; | %group;)?, %attrDecls;)'>
+
+<!-- This is used in part2 -->
+<!ENTITY % restriction1 '((%mgs; | %group;)?)'>
+
+%xs-datatypes;
+
+<!-- the duplication below is to produce an unambiguous content model
+     which allows annotation everywhere -->
+<!ELEMENT %schema; ((%include; | %import; | %redefine; | %annotation;)*,
+                    ((%simpleType; | %complexType;
+                      | %element; | %attribute;
+                      | %attributeGroup; | %group;
+                      | %notation; ),
+                     (%annotation;)*)* )>
+<!ATTLIST %schema;
+   targetNamespace      %URIref;               #IMPLIED
+   version              CDATA                  #IMPLIED
+   %nds;                %URIref;               #FIXED 'http://www.w3.org/2001/XMLSchema'
+   xmlns                CDATA                  #IMPLIED
+   finalDefault         %complexDerivationSet; ''
+   blockDefault         %blockSet;             ''
+   id                   ID                     #IMPLIED
+   elementFormDefault   %formValues;           'unqualified'
+   attributeFormDefault %formValues;           'unqualified'
+   xml:lang             CDATA                  #IMPLIED
+   %schemaAttrs;>
+<!-- Note the xmlns declaration is NOT in the Schema for Schemas,
+     because at the Infoset level where schemas operate,
+     xmlns(:prefix) is NOT an attribute! -->
+<!-- The declaration of xmlns is a convenience for schema authors -->
+<!-- The id attribute here and below is for use in external references
+     from non-schemas using simple fragment identifiers.
+     It is NOT used for schema-to-schema reference, internal or
+     external. -->
+
+<!-- a type is a named content type specification which allows attribute
+     declarations-->
+<!-- -->
+
+<!ELEMENT %complexType; ((%annotation;)?,
+                         (%simpleContent;|%complexContent;|
+                          %particleAndAttrs;))>
+
+<!ATTLIST %complexType;
+          name      %NCName;                        #IMPLIED
+          id        ID                              #IMPLIED
+          abstract  %boolean;                       #IMPLIED
+          final     %complexDerivationSet;          #IMPLIED
+          block     %complexDerivationSet;          #IMPLIED
+          mixed (true|false) 'false'
+          %complexTypeAttrs;>
+
+<!-- particleAndAttrs is shorthand for a root type -->
+<!-- mixed is disallowed if simpleContent, overriden if complexContent
+     has one too. -->
+
+<!-- If anyAttribute appears in one or more referenced attributeGroups
+     and/or explicitly, the intersection of the permissions is used -->
+
+<!ELEMENT %complexContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %complexContent;
+          mixed (true|false) #IMPLIED
+          id    ID           #IMPLIED
+          %complexContentAttrs;>
+
+<!-- restriction should use the branch defined above, not the simple
+     one from part2; extension should use the full model  -->
+
+<!ELEMENT %simpleContent; ((%annotation;)?, (%restriction;|%extension;))>
+<!ATTLIST %simpleContent;
+          id    ID           #IMPLIED
+          %simpleContentAttrs;>
+
+<!-- restriction should use the simple branch from part2, not the 
+     one defined above; extension should have no particle  -->
+
+<!ELEMENT %extension; ((%annotation;)?, (%particleAndAttrs;))>
+<!ATTLIST %extension;
+          base  %QName;      #REQUIRED
+          id    ID           #IMPLIED
+          %extensionAttrs;>
+
+<!-- an element is declared by either:
+ a name and a type (either nested or referenced via the type attribute)
+ or a ref to an existing element declaration -->
+
+<!ELEMENT %element; ((%annotation;)?, (%complexType;| %simpleType;)?,
+                     (%unique; | %key; | %keyref;)*)>
+<!-- simpleType or complexType only if no type|ref attribute -->
+<!-- ref not allowed at top level -->
+<!ATTLIST %element;
+            name               %NCName;               #IMPLIED
+            id                 ID                     #IMPLIED
+            ref                %QName;                #IMPLIED
+            type               %QName;                #IMPLIED
+            minOccurs          %nonNegativeInteger;   #IMPLIED
+            maxOccurs          CDATA                  #IMPLIED
+            nillable           %boolean;              #IMPLIED
+            substitutionGroup  %QName;                #IMPLIED
+            abstract           %boolean;              #IMPLIED
+            final              %complexDerivationSet; #IMPLIED
+            block              %blockSet;             #IMPLIED
+            default            CDATA                  #IMPLIED
+            fixed              CDATA                  #IMPLIED
+            form               %formValues;           #IMPLIED
+            %elementAttrs;>
+<!-- type and ref are mutually exclusive.
+     name and ref are mutually exclusive, one is required -->
+<!-- In the absence of type AND ref, type defaults to type of
+     substitutionGroup, if any, else the ur-type, i.e. unconstrained -->
+<!-- default and fixed are mutually exclusive -->
+
+<!ELEMENT %group; ((%annotation;)?,(%mgs;)?)>
+<!ATTLIST %group; 
+          name        %NCName;               #IMPLIED
+          ref         %QName;                #IMPLIED
+          minOccurs   %nonNegativeInteger;   #IMPLIED
+          maxOccurs   CDATA                  #IMPLIED
+          id          ID                     #IMPLIED
+          %groupAttrs;>
+
+<!ELEMENT %all; ((%annotation;)?, (%element;)*)>
+<!ATTLIST %all;
+          minOccurs   (1)                    #IMPLIED
+          maxOccurs   (1)                    #IMPLIED
+          id          ID                     #IMPLIED
+          %allAttrs;>
+
+<!ELEMENT %choice; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %choice;
+          minOccurs   %nonNegativeInteger;   #IMPLIED
+          maxOccurs   CDATA                  #IMPLIED
+          id          ID                     #IMPLIED
+          %choiceAttrs;>
+
+<!ELEMENT %sequence; ((%annotation;)?, (%element;| %group;| %cs; | %any;)*)>
+<!ATTLIST %sequence;
+          minOccurs   %nonNegativeInteger;   #IMPLIED
+          maxOccurs   CDATA                  #IMPLIED
+          id          ID                     #IMPLIED
+          %sequenceAttrs;>
+
+<!-- an anonymous grouping in a model, or
+     a top-level named group definition, or a reference to same -->
+
+<!-- Note that if order is 'all', group is not allowed inside.
+     If order is 'all' THIS group must be alone (or referenced alone) at
+     the top level of a content model -->
+<!-- If order is 'all', minOccurs==maxOccurs==1 on element/any inside -->
+<!-- Should allow minOccurs=0 inside order='all' . . . -->
+
+<!ELEMENT %any; (%annotation;)?>
+<!ATTLIST %any;
+            namespace       CDATA                  '##any'
+            processContents (skip|lax|strict)      'strict'
+            minOccurs       %nonNegativeInteger;   '1'
+            maxOccurs       CDATA                  '1'
+            id              ID                     #IMPLIED
+            %anyAttrs;>
+
+<!-- namespace is interpreted as follows:
+                  ##any      - - any non-conflicting WFXML at all
+
+                  ##other    - - any non-conflicting WFXML from namespace other
+                                  than targetNamespace
+
+                  ##local    - - any unqualified non-conflicting WFXML/attribute
+                  one or     - - any non-conflicting WFXML from
+                  more URI        the listed namespaces
+                  references
+
+                  ##targetNamespace ##local may appear in the above list,
+                    with the obvious meaning -->
+
+<!ELEMENT %anyAttribute; (%annotation;)?>
+<!ATTLIST %anyAttribute;
+            namespace       CDATA              '##any'
+            processContents (skip|lax|strict)  'strict'
+            id              ID                 #IMPLIED
+            %anyAttributeAttrs;>
+<!-- namespace is interpreted as for 'any' above -->
+
+<!-- simpleType only if no type|ref attribute -->
+<!-- ref not allowed at top level, name iff at top level -->
+<!ELEMENT %attribute; ((%annotation;)?, (%simpleType;)?)>
+<!ATTLIST %attribute;
+          name      %NCName;      #IMPLIED
+          id        ID            #IMPLIED
+          ref       %QName;       #IMPLIED
+          type      %QName;       #IMPLIED
+          use       (prohibited|optional|required) #IMPLIED
+          default   CDATA         #IMPLIED
+          fixed     CDATA         #IMPLIED
+          form      %formValues;  #IMPLIED
+          %attributeAttrs;>
+<!-- type and ref are mutually exclusive.
+     name and ref are mutually exclusive, one is required -->
+<!-- default for use is optional when nested, none otherwise -->
+<!-- default and fixed are mutually exclusive -->
+<!-- type attr and simpleType content are mutually exclusive -->
+
+<!-- an attributeGroup is a named collection of attribute decls, or a
+     reference thereto -->
+<!ELEMENT %attributeGroup; ((%annotation;)?,
+                       (%attribute; | %attributeGroup;)*,
+                       (%anyAttribute;)?) >
+<!ATTLIST %attributeGroup;
+                 name       %NCName;       #IMPLIED
+                 id         ID             #IMPLIED
+                 ref        %QName;        #IMPLIED
+                 %attributeGroupAttrs;>
+
+<!-- ref iff no content, no name.  ref iff not top level -->
+
+<!-- better reference mechanisms -->
+<!ELEMENT %unique; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %unique;
+          name     %NCName;       #REQUIRED
+         id       ID             #IMPLIED
+         %uniqueAttrs;>
+
+<!ELEMENT %key;    ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %key;
+          name     %NCName;       #REQUIRED
+         id       ID             #IMPLIED
+         %keyAttrs;>
+
+<!ELEMENT %keyref; ((%annotation;)?, %selector;, (%field;)+)>
+<!ATTLIST %keyref;
+          name     %NCName;       #REQUIRED
+         refer    %QName;        #REQUIRED
+         id       ID             #IMPLIED
+         %keyrefAttrs;>
+
+<!ELEMENT %selector; ((%annotation;)?)>
+<!ATTLIST %selector;
+          xpath %XPathExpr; #REQUIRED
+          id    ID          #IMPLIED
+          %selectorAttrs;>
+<!ELEMENT %field; ((%annotation;)?)>
+<!ATTLIST %field;
+          xpath %XPathExpr; #REQUIRED
+          id    ID          #IMPLIED
+          %fieldAttrs;>
+
+<!-- Schema combination mechanisms -->
+<!ELEMENT %include; (%annotation;)?>
+<!ATTLIST %include;
+          schemaLocation %URIref; #REQUIRED
+          id             ID       #IMPLIED
+          %includeAttrs;>
+
+<!ELEMENT %import; (%annotation;)?>
+<!ATTLIST %import;
+          namespace      %URIref; #IMPLIED
+          schemaLocation %URIref; #IMPLIED
+          id             ID       #IMPLIED
+          %importAttrs;>
+
+<!ELEMENT %redefine; (%annotation; | %simpleType; | %complexType; |
+                      %attributeGroup; | %group;)*>
+<!ATTLIST %redefine;
+          schemaLocation %URIref; #REQUIRED
+          id             ID       #IMPLIED
+          %redefineAttrs;>
+
+<!ELEMENT %notation; (%annotation;)?>
+<!ATTLIST %notation;
+         name        %NCName;    #REQUIRED
+         id          ID          #IMPLIED
+         public      CDATA       #REQUIRED
+         system      %URIref;    #IMPLIED
+         %notationAttrs;>
+
+<!-- Annotation is either application information or documentation -->
+<!-- By having these here they are available for datatypes as well
+     as all the structures elements -->
+
+<!ELEMENT %annotation; (%appinfo; | %documentation;)*>
+<!ATTLIST %annotation; %annotationAttrs;>
+
+<!-- User must define annotation elements in internal subset for this
+     to work -->
+<!ELEMENT %appinfo; ANY>   <!-- too restrictive -->
+<!ATTLIST %appinfo;
+          source     %URIref;      #IMPLIED
+          id         ID         #IMPLIED
+          %appinfoAttrs;>
+<!ELEMENT %documentation; ANY>   <!-- too restrictive -->
+<!ATTLIST %documentation;
+          source     %URIref;   #IMPLIED
+          id         ID         #IMPLIED
+          xml:lang   CDATA      #IMPLIED
+          %documentationAttrs;>
+
+<!NOTATION XMLSchemaStructures PUBLIC
+           'structures' 'http://www.w3.org/2001/XMLSchema.xsd' >
+<!NOTATION XML PUBLIC
+           'REC-xml-1998-0210' 'http://www.w3.org/TR/1998/REC-xml-19980210' >
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/XMLSchema.xsd
new file mode 100644 (file)
index 0000000..12c2209
--- /dev/null
@@ -0,0 +1,2534 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- XML Schema schema for XML Schemas: Part 1: Structures -->
+<!-- Note this schema is NOT the normative structures schema. -->
+<!-- The prose copy in the structures REC is the normative -->
+<!-- version (which shouldn't differ from this one except for -->
+<!-- this comment and entity expansions, but just in case -->
+<!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" [
+
+<!-- provide ID type information even for parsers which only read the
+     internal subset -->
+<!ATTLIST xs:schema          id  ID  #IMPLIED>
+<!ATTLIST xs:complexType     id  ID  #IMPLIED>
+<!ATTLIST xs:complexContent  id  ID  #IMPLIED>
+<!ATTLIST xs:simpleContent   id  ID  #IMPLIED>
+<!ATTLIST xs:extension       id  ID  #IMPLIED>
+<!ATTLIST xs:element         id  ID  #IMPLIED>
+<!ATTLIST xs:group           id  ID  #IMPLIED> 
+<!ATTLIST xs:all             id  ID  #IMPLIED>
+<!ATTLIST xs:choice          id  ID  #IMPLIED>
+<!ATTLIST xs:sequence        id  ID  #IMPLIED>
+<!ATTLIST xs:any             id  ID  #IMPLIED>
+<!ATTLIST xs:anyAttribute    id  ID  #IMPLIED>
+<!ATTLIST xs:attribute       id  ID  #IMPLIED>
+<!ATTLIST xs:attributeGroup  id  ID  #IMPLIED>
+<!ATTLIST xs:unique          id  ID  #IMPLIED>
+<!ATTLIST xs:key             id  ID  #IMPLIED>
+<!ATTLIST xs:keyref          id  ID  #IMPLIED>
+<!ATTLIST xs:selector        id  ID  #IMPLIED>
+<!ATTLIST xs:field           id  ID  #IMPLIED>
+<!ATTLIST xs:include         id  ID  #IMPLIED>
+<!ATTLIST xs:import          id  ID  #IMPLIED>
+<!ATTLIST xs:redefine        id  ID  #IMPLIED>
+<!ATTLIST xs:notation        id  ID  #IMPLIED>
+<!--
+     keep this schema XML1.0 DTD valid
+  -->
+        <!ENTITY % schemaAttrs 'xmlns:hfp CDATA #IMPLIED'>
+
+        <!ELEMENT hfp:hasFacet EMPTY>
+        <!ATTLIST hfp:hasFacet
+                name NMTOKEN #REQUIRED>
+
+        <!ELEMENT hfp:hasProperty EMPTY>
+        <!ATTLIST hfp:hasProperty
+                name NMTOKEN #REQUIRED
+                value CDATA #REQUIRED>
+<!--
+        Make sure that processors that do not read the external
+        subset will know about the various IDs we declare
+  -->
+        <!ATTLIST xs:simpleType id ID #IMPLIED>
+        <!ATTLIST xs:maxExclusive id ID #IMPLIED>
+        <!ATTLIST xs:minExclusive id ID #IMPLIED>
+        <!ATTLIST xs:maxInclusive id ID #IMPLIED>
+        <!ATTLIST xs:minInclusive id ID #IMPLIED>
+        <!ATTLIST xs:totalDigits id ID #IMPLIED>
+        <!ATTLIST xs:fractionDigits id ID #IMPLIED>
+        <!ATTLIST xs:length id ID #IMPLIED>
+        <!ATTLIST xs:minLength id ID #IMPLIED>
+        <!ATTLIST xs:maxLength id ID #IMPLIED>
+        <!ATTLIST xs:enumeration id ID #IMPLIED>
+        <!ATTLIST xs:pattern id ID #IMPLIED>
+        <!ATTLIST xs:appinfo id ID #IMPLIED>
+        <!ATTLIST xs:documentation id ID #IMPLIED>
+        <!ATTLIST xs:list id ID #IMPLIED>
+        <!ATTLIST xs:union id ID #IMPLIED>
+        ]>
+<xs:schema targetNamespace="http://www.w3.org/2001/XMLSchema" blockDefault="#all" elementFormDefault="qualified" version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="EN" xmlns:hfp="http://www.w3.org/2001/XMLSchema-hasFacetAndProperty">
+ <xs:annotation>
+  <xs:documentation>
+    Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp 
+    Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp 
+  </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/2004/PER-xmlschema-1-20040318/structures.html">
+   The schema corresponding to this document is normative,
+   with respect to the syntactic constraints it expresses in the
+   XML Schema language.  The documentation (within &lt;documentation> elements)
+   below, is not normative, but rather highlights important aspects of
+   the W3C Recommendation of which this is a part</xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+   <xs:documentation>
+   The simpleType element and all of its members are defined
+      towards the end of this schema document</xs:documentation>
+ </xs:annotation>
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd">
+   <xs:annotation>
+     <xs:documentation>
+       Get access to the xml: attribute groups for xml:lang
+       as declared on 'schema' and 'documentation' below
+     </xs:documentation>
+   </xs:annotation>
+ </xs:import>
+
+ <xs:complexType name="openAttrs">
+   <xs:annotation>
+     <xs:documentation>
+       This type is extended by almost all schema types
+       to allow attributes from other namespaces to be
+       added to user schemas.
+     </xs:documentation>
+   </xs:annotation>
+   <xs:complexContent>
+     <xs:restriction base="xs:anyType">
+       <xs:anyAttribute namespace="##other" processContents="lax"/>
+     </xs:restriction>
+   </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="annotated">
+   <xs:annotation>
+     <xs:documentation>
+       This type is extended by all types which allow annotation
+       other than &lt;schema&gt; itself
+     </xs:documentation>
+   </xs:annotation>
+   <xs:complexContent>
+     <xs:extension base="xs:openAttrs">
+       <xs:sequence>
+         <xs:element ref="xs:annotation" minOccurs="0"/>
+       </xs:sequence>
+       <xs:attribute name="id" type="xs:ID"/>
+     </xs:extension>
+   </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="schemaTop">
+  <xs:annotation>
+   <xs:documentation>
+   This group is for the
+   elements which occur freely at the top level of schemas.
+   All of their types are based on the "annotated" type by extension.</xs:documentation>
+  </xs:annotation>
+  <xs:choice>
+   <xs:group ref="xs:redefinable"/>
+   <xs:element ref="xs:element"/>
+   <xs:element ref="xs:attribute"/>
+   <xs:element ref="xs:notation"/>
+  </xs:choice>
+ </xs:group>
+ <xs:group name="redefinable">
+  <xs:annotation>
+   <xs:documentation>
+   This group is for the
+   elements which can self-redefine (see &lt;redefine> below).</xs:documentation>
+  </xs:annotation>
+  <xs:choice>
+   <xs:element ref="xs:simpleType"/>
+   <xs:element ref="xs:complexType"/>
+   <xs:element ref="xs:group"/>
+   <xs:element ref="xs:attributeGroup"/>
+  </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="formChoice">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:NMTOKEN">
+   <xs:enumeration value="qualified"/>
+   <xs:enumeration value="unqualified"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="reducedDerivationControl">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:derivationControl">
+   <xs:enumeration value="extension"/>
+   <xs:enumeration value="restriction"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:simpleType name="derivationSet">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+   <xs:documentation>
+   #all or (possibly empty) subset of {extension, restriction}</xs:documentation>
+  </xs:annotation>
+  <xs:union>
+   <xs:simpleType>    
+    <xs:restriction base="xs:token">
+     <xs:enumeration value="#all"/>
+    </xs:restriction>
+   </xs:simpleType>
+   <xs:simpleType>
+    <xs:list itemType="xs:reducedDerivationControl"/>
+   </xs:simpleType>
+  </xs:union>
+ </xs:simpleType>
+
+ <xs:simpleType name="typeDerivationControl">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:derivationControl">
+   <xs:enumeration value="extension"/>
+   <xs:enumeration value="restriction"/>
+   <xs:enumeration value="list"/>
+   <xs:enumeration value="union"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+  <xs:simpleType name="fullDerivationSet">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+   <xs:documentation>
+   #all or (possibly empty) subset of {extension, restriction, list, union}</xs:documentation>
+  </xs:annotation>
+  <xs:union>
+   <xs:simpleType>    
+    <xs:restriction base="xs:token">
+     <xs:enumeration value="#all"/>
+    </xs:restriction>
+   </xs:simpleType>
+   <xs:simpleType>
+    <xs:list itemType="xs:typeDerivationControl"/>
+   </xs:simpleType>
+  </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="schema" id="schema">
+  <xs:annotation>
+    <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-schema"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:openAttrs">
+     <xs:sequence>
+      <xs:choice minOccurs="0" maxOccurs="unbounded">
+       <xs:element ref="xs:include"/>
+       <xs:element ref="xs:import"/>
+       <xs:element ref="xs:redefine"/>
+       <xs:element ref="xs:annotation"/>
+      </xs:choice>
+      <xs:sequence minOccurs="0" maxOccurs="unbounded">
+       <xs:group ref="xs:schemaTop"/>
+       <xs:element ref="xs:annotation" minOccurs="0" maxOccurs="unbounded"/>
+      </xs:sequence>
+     </xs:sequence>
+     <xs:attribute name="targetNamespace" type="xs:anyURI"/>
+     <xs:attribute name="version" type="xs:token"/>
+     <xs:attribute name="finalDefault" type="xs:fullDerivationSet" use="optional" default=""/>
+     <xs:attribute name="blockDefault" type="xs:blockSet" use="optional" default=""/>
+     <xs:attribute name="attributeFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+     <xs:attribute name="elementFormDefault" type="xs:formChoice" use="optional" default="unqualified"/>
+     <xs:attribute name="id" type="xs:ID"/>
+     <xs:attribute ref="xml:lang"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+
+  <xs:key name="element">
+   <xs:selector xpath="xs:element"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+
+  <xs:key name="attribute">
+   <xs:selector xpath="xs:attribute"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+
+  <xs:key name="type">
+   <xs:selector xpath="xs:complexType|xs:simpleType"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+  <xs:key name="group">
+   <xs:selector xpath="xs:group"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+  <xs:key name="attributeGroup">
+   <xs:selector xpath="xs:attributeGroup"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+  <xs:key name="notation">
+   <xs:selector xpath="xs:notation"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+
+  <xs:key name="identityConstraint">
+   <xs:selector xpath=".//xs:key|.//xs:unique|.//xs:keyref"/>
+   <xs:field xpath="@name"/>
+  </xs:key>
+
+ </xs:element>
+
+ <xs:simpleType name="allNNI">
+  <xs:annotation><xs:documentation>
+   for maxOccurs</xs:documentation></xs:annotation>
+  <xs:union memberTypes="xs:nonNegativeInteger">
+   <xs:simpleType>
+    <xs:restriction base="xs:NMTOKEN">
+     <xs:enumeration value="unbounded"/>
+    </xs:restriction>
+   </xs:simpleType>
+  </xs:union>
+ </xs:simpleType>
+
+ <xs:attributeGroup name="occurs">
+  <xs:annotation><xs:documentation>
+   for all particles</xs:documentation></xs:annotation>
+  <xs:attribute name="minOccurs" type="xs:nonNegativeInteger" use="optional" default="1"/>
+  <xs:attribute name="maxOccurs" type="xs:allNNI" use="optional" default="1"/>
+ </xs:attributeGroup>
+
+ <xs:attributeGroup name="defRef">
+  <xs:annotation><xs:documentation>
+   for element, group and attributeGroup,
+   which both define and reference</xs:documentation></xs:annotation>
+  <xs:attribute name="name" type="xs:NCName"/>
+  <xs:attribute name="ref" type="xs:QName"/>
+ </xs:attributeGroup>
+
+ <xs:group name="typeDefParticle">
+  <xs:annotation>
+    <xs:documentation>
+   'complexType' uses this</xs:documentation></xs:annotation>
+  <xs:choice>
+   <xs:element name="group" type="xs:groupRef"/>
+   <xs:element ref="xs:all"/>
+   <xs:element ref="xs:choice"/>
+   <xs:element ref="xs:sequence"/>
+  </xs:choice>
+ </xs:group>
+
+ <xs:group name="nestedParticle">
+  <xs:choice>
+   <xs:element name="element" type="xs:localElement"/>
+   <xs:element name="group" type="xs:groupRef"/>
+   <xs:element ref="xs:choice"/>
+   <xs:element ref="xs:sequence"/>
+   <xs:element ref="xs:any"/>
+  </xs:choice>
+ </xs:group>
+ <xs:group name="particle">
+  <xs:choice>
+   <xs:element name="element" type="xs:localElement"/>
+   <xs:element name="group" type="xs:groupRef"/>
+   <xs:element ref="xs:all"/>
+   <xs:element ref="xs:choice"/>
+   <xs:element ref="xs:sequence"/>
+   <xs:element ref="xs:any"/>
+  </xs:choice>
+ </xs:group>
+ <xs:complexType name="attribute">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:sequence>
+     <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+    </xs:sequence>
+    <xs:attributeGroup ref="xs:defRef"/>
+    <xs:attribute name="type" type="xs:QName"/>
+    <xs:attribute name="use" use="optional" default="optional">
+     <xs:simpleType>
+      <xs:restriction base="xs:NMTOKEN">
+       <xs:enumeration value="prohibited"/>
+       <xs:enumeration value="optional"/>
+       <xs:enumeration value="required"/>
+      </xs:restriction>
+     </xs:simpleType>
+    </xs:attribute>
+    <xs:attribute name="default" type="xs:string"/>
+    <xs:attribute name="fixed" type="xs:string"/>
+    <xs:attribute name="form" type="xs:formChoice"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="topLevelAttribute">
+  <xs:complexContent>
+   <xs:restriction base="xs:attribute">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:element name="simpleType" minOccurs="0" type="xs:localSimpleType"/>
+    </xs:sequence>
+    <xs:attribute name="ref" use="prohibited"/>
+    <xs:attribute name="form" use="prohibited"/>
+    <xs:attribute name="use" use="prohibited"/>
+    <xs:attribute name="name" use="required" type="xs:NCName"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="attrDecls">
+  <xs:sequence>
+   <xs:choice minOccurs="0" maxOccurs="unbounded">
+    <xs:element name="attribute" type="xs:attribute"/>
+    <xs:element name="attributeGroup" type="xs:attributeGroupRef"/>
+   </xs:choice>
+   <xs:element ref="xs:anyAttribute" minOccurs="0"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:element name="anyAttribute" type="xs:wildcard" id="anyAttribute">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-anyAttribute"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:group name="complexTypeModel">
+  <xs:choice>
+      <xs:element ref="xs:simpleContent"/>
+      <xs:element ref="xs:complexContent"/>
+      <xs:sequence>
+       <xs:annotation>
+        <xs:documentation>
+   This branch is short for
+   &lt;complexContent>
+   &lt;restriction base="xs:anyType">
+   ...
+   &lt;/restriction>
+   &lt;/complexContent></xs:documentation>
+       </xs:annotation>
+       <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+       <xs:group ref="xs:attrDecls"/>
+      </xs:sequence>
+  </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="complexType" abstract="true">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:group ref="xs:complexTypeModel"/>
+    <xs:attribute name="name" type="xs:NCName">
+     <xs:annotation>
+      <xs:documentation>
+      Will be restricted to required or forbidden</xs:documentation>
+     </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="mixed" type="xs:boolean" use="optional" default="false">
+     <xs:annotation>
+      <xs:documentation>
+      Not allowed if simpleContent child is chosen.
+      May be overriden by setting on complexContent child.</xs:documentation>
+    </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+    <xs:attribute name="final" type="xs:derivationSet"/>
+    <xs:attribute name="block" type="xs:derivationSet"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="topLevelComplexType">
+  <xs:complexContent>
+   <xs:restriction base="xs:complexType">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:complexTypeModel"/>
+    </xs:sequence>
+    <xs:attribute name="name" type="xs:NCName" use="required"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="localComplexType">
+  <xs:complexContent>
+   <xs:restriction base="xs:complexType">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:complexTypeModel"/>
+    </xs:sequence>
+    <xs:attribute name="name" use="prohibited"/>
+    <xs:attribute name="abstract" use="prohibited"/>
+    <xs:attribute name="final" use="prohibited"/>
+    <xs:attribute name="block" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="restrictionType">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:sequence>
+     <xs:choice minOccurs="0">
+      <xs:group ref="xs:typeDefParticle"/>
+      <xs:group ref="xs:simpleRestrictionModel"/>
+     </xs:choice>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:attribute name="base" type="xs:QName" use="required"/>
+   </xs:extension>
+  </xs:complexContent>       
+ </xs:complexType>
+
+ <xs:complexType name="complexRestrictionType">
+  <xs:complexContent>
+   <xs:restriction base="xs:restrictionType">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0">
+      <xs:annotation>
+       <xs:documentation>This choice is added simply to
+                   make this a valid restriction per the REC</xs:documentation>
+      </xs:annotation>
+      <xs:group ref="xs:typeDefParticle"/>
+     </xs:choice>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>       
+ </xs:complexType>
+
+ <xs:complexType name="extensionType">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:sequence>
+     <xs:group ref="xs:typeDefParticle" minOccurs="0"/>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:attribute name="base" type="xs:QName" use="required"/>
+   </xs:extension>
+  </xs:complexContent>       
+ </xs:complexType>
+
+ <xs:element name="complexContent" id="complexContent">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexContent"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:annotated">
+     <xs:choice>
+      <xs:element name="restriction" type="xs:complexRestrictionType"/>
+      <xs:element name="extension" type="xs:extensionType"/>
+     </xs:choice>     
+     <xs:attribute name="mixed" type="xs:boolean">
+      <xs:annotation>
+       <xs:documentation>
+       Overrides any setting on complexType parent.</xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="simpleRestrictionType">
+  <xs:complexContent>
+   <xs:restriction base="xs:restrictionType">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0">
+      <xs:annotation>
+       <xs:documentation>This choice is added simply to
+                   make this a valid restriction per the REC</xs:documentation>
+      </xs:annotation>
+      <xs:group ref="xs:simpleRestrictionModel"/>
+     </xs:choice>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="simpleExtensionType">
+  <xs:complexContent>
+   <xs:restriction base="xs:extensionType">
+    <xs:sequence>
+     <xs:annotation>
+      <xs:documentation>
+      No typeDefParticle group reference</xs:documentation>
+     </xs:annotation>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="simpleContent" id="simpleContent">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-simpleContent"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:annotated">
+     <xs:choice>
+      <xs:element name="restriction" type="xs:simpleRestrictionType"/>
+      <xs:element name="extension" type="xs:simpleExtensionType"/>
+     </xs:choice>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+ <xs:element name="complexType" type="xs:topLevelComplexType" id="complexType">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-complexType"/>
+  </xs:annotation>
+ </xs:element>
+
+
+  <xs:simpleType name="blockSet">
+   <xs:annotation>
+    <xs:documentation>
+    A utility type, not for public use</xs:documentation>
+    <xs:documentation>
+    #all or (possibly empty) subset of {substitution, extension,
+    restriction}</xs:documentation>
+   </xs:annotation>
+   <xs:union>
+    <xs:simpleType>    
+     <xs:restriction base="xs:token">
+      <xs:enumeration value="#all"/>
+     </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType>
+     <xs:list>
+      <xs:simpleType>
+       <xs:restriction base="xs:derivationControl">
+        <xs:enumeration value="extension"/>
+        <xs:enumeration value="restriction"/>
+        <xs:enumeration value="substitution"/>
+       </xs:restriction>
+      </xs:simpleType>
+     </xs:list>
+    </xs:simpleType>
+   </xs:union>  
+  </xs:simpleType>
+
+ <xs:complexType name="element" abstract="true">
+  <xs:annotation>
+   <xs:documentation>
+   The element element can be used either
+   at the top level to define an element-type binding globally,
+   or within a content model to either reference a globally-defined
+   element or type or declare an element-type binding locally.
+   The ref form is not allowed at the top level.</xs:documentation>
+  </xs:annotation>
+
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:sequence>
+     <xs:choice minOccurs="0">
+      <xs:element name="simpleType" type="xs:localSimpleType"/>
+      <xs:element name="complexType" type="xs:localComplexType"/>
+     </xs:choice>
+     <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attributeGroup ref="xs:defRef"/>
+    <xs:attribute name="type" type="xs:QName"/>
+    <xs:attribute name="substitutionGroup" type="xs:QName"/>
+    <xs:attributeGroup ref="xs:occurs"/>
+    <xs:attribute name="default" type="xs:string"/>
+    <xs:attribute name="fixed" type="xs:string"/>
+    <xs:attribute name="nillable" type="xs:boolean" use="optional" default="false"/>
+    <xs:attribute name="abstract" type="xs:boolean" use="optional" default="false"/>
+    <xs:attribute name="final" type="xs:derivationSet"/>
+    <xs:attribute name="block" type="xs:blockSet"/>
+    <xs:attribute name="form" type="xs:formChoice"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="topLevelElement">
+  <xs:complexContent>
+   <xs:restriction base="xs:element">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0">
+      <xs:element name="simpleType" type="xs:localSimpleType"/>
+      <xs:element name="complexType" type="xs:localComplexType"/>
+     </xs:choice>
+     <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="ref" use="prohibited"/>
+    <xs:attribute name="form" use="prohibited"/>
+    <xs:attribute name="minOccurs" use="prohibited"/>
+    <xs:attribute name="maxOccurs" use="prohibited"/>
+    <xs:attribute name="name" use="required" type="xs:NCName"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="localElement">
+  <xs:complexContent>
+   <xs:restriction base="xs:element">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0">
+      <xs:element name="simpleType" type="xs:localSimpleType"/>
+      <xs:element name="complexType" type="xs:localComplexType"/>
+     </xs:choice>
+     <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="substitutionGroup" use="prohibited"/>
+    <xs:attribute name="final" use="prohibited"/>
+    <xs:attribute name="abstract" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="element" type="xs:topLevelElement" id="element">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-element"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="group" abstract="true">
+  <xs:annotation>
+   <xs:documentation>
+   group type for explicit groups, named top-level groups and
+   group references</xs:documentation>
+  </xs:annotation>
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:group ref="xs:particle" minOccurs="0" maxOccurs="unbounded"/>
+    <xs:attributeGroup ref="xs:defRef"/>
+    <xs:attributeGroup ref="xs:occurs"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="realGroup">
+  <xs:complexContent>
+   <xs:restriction base="xs:group">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0" maxOccurs="1">
+      <xs:element ref="xs:all"/>
+      <xs:element ref="xs:choice"/>
+      <xs:element ref="xs:sequence"/>
+     </xs:choice>
+    </xs:sequence>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="namedGroup">
+  <xs:complexContent>
+   <xs:restriction base="xs:realGroup">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="1" maxOccurs="1">
+      <xs:element name="all">
+       <xs:complexType>
+        <xs:complexContent>
+         <xs:restriction base="xs:all">
+          <xs:group ref="xs:allModel"/>
+          <xs:attribute name="minOccurs" use="prohibited"/>
+          <xs:attribute name="maxOccurs" use="prohibited"/>
+          <xs:anyAttribute namespace="##other" processContents="lax"/>
+         </xs:restriction>
+        </xs:complexContent>
+       </xs:complexType>
+      </xs:element>
+      <xs:element name="choice" type="xs:simpleExplicitGroup"/>
+      <xs:element name="sequence" type="xs:simpleExplicitGroup"/>
+     </xs:choice>
+    </xs:sequence>
+    <xs:attribute name="name" use="required" type="xs:NCName"/>
+    <xs:attribute name="ref" use="prohibited"/>
+    <xs:attribute name="minOccurs" use="prohibited"/>
+    <xs:attribute name="maxOccurs" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="groupRef">
+  <xs:complexContent>
+   <xs:restriction base="xs:realGroup">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+    </xs:sequence>
+    <xs:attribute name="ref" use="required" type="xs:QName"/>
+    <xs:attribute name="name" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="explicitGroup">
+  <xs:annotation>
+   <xs:documentation>
+   group type for the three kinds of group</xs:documentation>
+  </xs:annotation>
+  <xs:complexContent>
+   <xs:restriction base="xs:group">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="name" type="xs:NCName" use="prohibited"/>
+    <xs:attribute name="ref" type="xs:QName" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="simpleExplicitGroup">
+  <xs:complexContent>
+   <xs:restriction base="xs:explicitGroup">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:nestedParticle" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="minOccurs" use="prohibited"/>
+    <xs:attribute name="maxOccurs" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:group name="allModel">
+  <xs:sequence>
+      <xs:element ref="xs:annotation" minOccurs="0"/>
+      <xs:choice minOccurs="0" maxOccurs="unbounded">
+       <xs:annotation>
+        <xs:documentation>This choice with min/max is here to
+                          avoid a pblm with the Elt:All/Choice/Seq
+                          Particle derivation constraint</xs:documentation>
+       </xs:annotation>
+       <xs:element name="element" type="xs:narrowMaxMin"/>
+      </xs:choice>
+     </xs:sequence>
+ </xs:group>
+ <xs:complexType name="narrowMaxMin">
+  <xs:annotation>
+   <xs:documentation>restricted max/min</xs:documentation>
+  </xs:annotation>
+  <xs:complexContent>
+   <xs:restriction base="xs:localElement">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:choice minOccurs="0">
+      <xs:element name="simpleType" type="xs:localSimpleType"/>
+      <xs:element name="complexType" type="xs:localComplexType"/>
+     </xs:choice>
+     <xs:group ref="xs:identityConstraint" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="minOccurs" use="optional" default="1">
+     <xs:simpleType>
+      <xs:restriction base="xs:nonNegativeInteger">
+       <xs:enumeration value="0"/>
+       <xs:enumeration value="1"/>
+      </xs:restriction>
+     </xs:simpleType>
+    </xs:attribute>
+    <xs:attribute name="maxOccurs" use="optional" default="1">
+     <xs:simpleType>
+      <xs:restriction base="xs:allNNI">
+       <xs:enumeration value="0"/>
+       <xs:enumeration value="1"/>
+      </xs:restriction>
+     </xs:simpleType>
+    </xs:attribute>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+  <xs:complexType name="all">
+   <xs:annotation>
+    <xs:documentation>
+   Only elements allowed inside</xs:documentation>
+   </xs:annotation>
+   <xs:complexContent>
+    <xs:restriction base="xs:explicitGroup">
+     <xs:group ref="xs:allModel"/>
+     <xs:attribute name="minOccurs" use="optional" default="1">
+      <xs:simpleType>
+       <xs:restriction base="xs:nonNegativeInteger">
+        <xs:enumeration value="0"/>
+        <xs:enumeration value="1"/>
+       </xs:restriction>
+      </xs:simpleType>
+     </xs:attribute>
+     <xs:attribute name="maxOccurs" use="optional" default="1">
+      <xs:simpleType>
+       <xs:restriction base="xs:allNNI">
+        <xs:enumeration value="1"/>
+       </xs:restriction>
+      </xs:simpleType>
+     </xs:attribute>
+     <xs:anyAttribute namespace="##other" processContents="lax"/>
+    </xs:restriction>
+   </xs:complexContent>
+  </xs:complexType>
+
+ <xs:element name="all" id="all" type="xs:all">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-all"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:element name="choice" type="xs:explicitGroup" id="choice">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-choice"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:element name="sequence" type="xs:explicitGroup" id="sequence">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-sequence"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:element name="group" type="xs:namedGroup" id="group">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-group"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="wildcard">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:attribute name="namespace" type="xs:namespaceList" use="optional" default="##any"/>
+    <xs:attribute name="processContents" use="optional" default="strict">
+     <xs:simpleType>
+      <xs:restriction base="xs:NMTOKEN">
+       <xs:enumeration value="skip"/>
+       <xs:enumeration value="lax"/>
+       <xs:enumeration value="strict"/>
+      </xs:restriction>
+     </xs:simpleType>
+    </xs:attribute>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="any" id="any">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-any"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:wildcard">
+     <xs:attributeGroup ref="xs:occurs"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+  <xs:annotation>
+   <xs:documentation>
+   simple type for the value of the 'namespace' attr of
+   'any' and 'anyAttribute'</xs:documentation>
+  </xs:annotation>
+  <xs:annotation>
+   <xs:documentation>
+   Value is
+              ##any      - - any non-conflicting WFXML/attribute at all
+
+              ##other    - - any non-conflicting WFXML/attribute from
+                              namespace other than targetNS
+
+              ##local    - - any unqualified non-conflicting WFXML/attribute 
+
+              one or     - - any non-conflicting WFXML/attribute from
+              more URI        the listed namespaces
+              references
+              (space separated)
+
+    ##targetNamespace or ##local may appear in the above list, to
+        refer to the targetNamespace of the enclosing
+        schema or an absent targetNamespace respectively</xs:documentation>
+  </xs:annotation>
+
+ <xs:simpleType name="namespaceList">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:union>
+   <xs:simpleType>
+    <xs:restriction base="xs:token">
+     <xs:enumeration value="##any"/>
+     <xs:enumeration value="##other"/>
+    </xs:restriction>
+   </xs:simpleType>
+   <xs:simpleType>
+    <xs:list>
+     <xs:simpleType>
+      <xs:union memberTypes="xs:anyURI">
+       <xs:simpleType>
+        <xs:restriction base="xs:token">
+         <xs:enumeration value="##targetNamespace"/>
+         <xs:enumeration value="##local"/>
+        </xs:restriction>
+       </xs:simpleType>
+      </xs:union>
+     </xs:simpleType>
+    </xs:list>
+   </xs:simpleType>
+  </xs:union>
+ </xs:simpleType>
+
+ <xs:element name="attribute" type="xs:topLevelAttribute" id="attribute">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attribute"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:complexType name="attributeGroup" abstract="true">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:group ref="xs:attrDecls"/>
+    <xs:attributeGroup ref="xs:defRef"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ <xs:complexType name="namedAttributeGroup">
+  <xs:complexContent>
+   <xs:restriction base="xs:attributeGroup">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+     <xs:group ref="xs:attrDecls"/>
+    </xs:sequence>
+    <xs:attribute name="name" use="required" type="xs:NCName"/>
+    <xs:attribute name="ref" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:complexType name="attributeGroupRef">
+  <xs:complexContent>
+   <xs:restriction base="xs:attributeGroup">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+    </xs:sequence>
+    <xs:attribute name="ref" use="required" type="xs:QName"/>
+    <xs:attribute name="name" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:element name="attributeGroup" type="xs:namedAttributeGroup" id="attributeGroup">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-attributeGroup"/>
+  </xs:annotation>
+ </xs:element>
+
+ <xs:element name="include" id="include">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-include"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:annotated">
+     <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:element name="redefine" id="redefine">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-redefine"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:openAttrs">
+     <xs:choice minOccurs="0" maxOccurs="unbounded">
+      <xs:element ref="xs:annotation"/>
+      <xs:group ref="xs:redefinable"/>
+     </xs:choice>
+     <xs:attribute name="schemaLocation" type="xs:anyURI" use="required"/>
+     <xs:attribute name="id" type="xs:ID"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:element name="import" id="import">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-import"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:annotated">
+     <xs:attribute name="namespace" type="xs:anyURI"/>
+     <xs:attribute name="schemaLocation" type="xs:anyURI"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:element name="selector" id="selector">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-selector"/>
+  </xs:annotation>
+  <xs:complexType>
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+     <xs:attribute name="xpath" use="required">
+      <xs:simpleType>
+       <xs:annotation>
+        <xs:documentation>A subset of XPath expressions for use
+in selectors</xs:documentation>
+        <xs:documentation>A utility type, not for public
+use</xs:documentation>
+       </xs:annotation>
+       <xs:restriction base="xs:token">
+        <xs:annotation>
+         <xs:documentation>The following pattern is intended to allow XPath
+                           expressions per the following EBNF:
+          Selector    ::=    Path ( '|' Path )*  
+          Path    ::=    ('.//')? Step ( '/' Step )*  
+          Step    ::=    '.' | NameTest  
+          NameTest    ::=    QName | '*' | NCName ':' '*'  
+                           child:: is also allowed
+         </xs:documentation>
+        </xs:annotation>
+        <xs:pattern value="(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*(\|(\.//)?(((child::)?((\i\c*:)?(\i\c*|\*)))|\.)(/(((child::)?((\i\c*:)?(\i\c*|\*)))|\.))*)*">
+        </xs:pattern>
+       </xs:restriction>
+      </xs:simpleType>
+     </xs:attribute>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:element name="field" id="field">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-field"/>
+  </xs:annotation>
+  <xs:complexType>
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+     <xs:attribute name="xpath" use="required">
+      <xs:simpleType>
+       <xs:annotation>
+        <xs:documentation>A subset of XPath expressions for use
+in fields</xs:documentation>
+        <xs:documentation>A utility type, not for public
+use</xs:documentation>
+       </xs:annotation>
+       <xs:restriction base="xs:token">
+        <xs:annotation>
+         <xs:documentation>The following pattern is intended to allow XPath
+                           expressions per the same EBNF as for selector,
+                           with the following change:
+          Path    ::=    ('.//')? ( Step '/' )* ( Step | '@' NameTest ) 
+         </xs:documentation>
+        </xs:annotation>
+        <xs:pattern value="(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*))))(\|(\.//)?((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)/)*((((child::)?((\i\c*:)?(\i\c*|\*)))|\.)|((attribute::|@)((\i\c*:)?(\i\c*|\*)))))*">
+        </xs:pattern>
+       </xs:restriction>
+      </xs:simpleType>
+     </xs:attribute>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:complexType name="keybase">
+  <xs:complexContent>
+   <xs:extension base="xs:annotated">
+    <xs:sequence>
+     <xs:element ref="xs:selector"/>
+     <xs:element ref="xs:field" minOccurs="1" maxOccurs="unbounded"/>
+    </xs:sequence>
+    <xs:attribute name="name" type="xs:NCName" use="required"/>
+   </xs:extension>
+  </xs:complexContent>
+ </xs:complexType>
+
+ <xs:group name="identityConstraint">
+  <xs:annotation>
+   <xs:documentation>The three kinds of identity constraints, all with
+                     type of or derived from 'keybase'.
+   </xs:documentation>
+  </xs:annotation>
+  <xs:choice>
+   <xs:element ref="xs:unique"/>
+   <xs:element ref="xs:key"/>
+   <xs:element ref="xs:keyref"/>
+  </xs:choice>
+ </xs:group>
+
+ <xs:element name="unique" type="xs:keybase" id="unique">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-unique"/>
+  </xs:annotation>
+ </xs:element>
+ <xs:element name="key" type="xs:keybase" id="key">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-key"/>
+  </xs:annotation>
+ </xs:element>
+ <xs:element name="keyref" id="keyref">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-keyref"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:keybase">
+     <xs:attribute name="refer" type="xs:QName" use="required"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:element name="notation" id="notation">
+  <xs:annotation>
+   <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-notation"/>
+  </xs:annotation>
+  <xs:complexType>
+   <xs:complexContent>
+    <xs:extension base="xs:annotated">
+     <xs:attribute name="name" type="xs:NCName" use="required"/>
+     <xs:attribute name="public" type="xs:public"/>
+     <xs:attribute name="system" type="xs:anyURI"/>
+    </xs:extension>
+   </xs:complexContent>
+  </xs:complexType>
+ </xs:element>
+
+ <xs:simpleType name="public">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+   <xs:documentation>
+   A public identifier, per ISO 8879</xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token"/>
+ </xs:simpleType>
+
+ <xs:element name="appinfo" id="appinfo">
+   <xs:annotation>
+     <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-appinfo"/>
+   </xs:annotation>
+   <xs:complexType mixed="true">
+    <xs:sequence minOccurs="0" maxOccurs="unbounded">
+     <xs:any processContents="lax"/>
+    </xs:sequence>
+    <xs:attribute name="source" type="xs:anyURI"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:complexType>
+ </xs:element>
+
+ <xs:element name="documentation" id="documentation">
+   <xs:annotation>
+     <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-documentation"/>
+   </xs:annotation>
+   <xs:complexType mixed="true">
+    <xs:sequence minOccurs="0" maxOccurs="unbounded">
+     <xs:any processContents="lax"/>
+    </xs:sequence>
+    <xs:attribute name="source" type="xs:anyURI"/>
+    <xs:attribute ref="xml:lang"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:complexType>
+ </xs:element>
+
+ <xs:element name="annotation" id="annotation">
+   <xs:annotation>
+     <xs:documentation source="http://www.w3.org/TR/xmlschema-1/#element-annotation"/>
+   </xs:annotation>
+   <xs:complexType>
+    <xs:complexContent>
+     <xs:extension base="xs:openAttrs">
+      <xs:choice minOccurs="0" maxOccurs="unbounded">
+       <xs:element ref="xs:appinfo"/>
+       <xs:element ref="xs:documentation"/>
+      </xs:choice>
+      <xs:attribute name="id" type="xs:ID"/>
+     </xs:extension>
+    </xs:complexContent>
+   </xs:complexType>
+ </xs:element>
+
+ <xs:annotation>
+  <xs:documentation>
+   notations for use within XML Schema schemas</xs:documentation>
+ </xs:annotation>
+
+ <xs:notation name="XMLSchemaStructures" public="structures" system="http://www.w3.org/2000/08/XMLSchema.xsd"/>
+ <xs:notation name="XML" public="REC-xml-19980210" system="http://www.w3.org/TR/1998/REC-xml-19980210"/>
+  
+ <xs:complexType name="anyType" mixed="true">
+  <xs:annotation>
+   <xs:documentation>
+   Not the real urType, but as close an approximation as we can
+   get in the XML representation</xs:documentation>
+  </xs:annotation>
+  <xs:sequence>
+   <xs:any minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+  </xs:sequence>
+  <xs:anyAttribute processContents="lax"/>
+ </xs:complexType>
+
+  <xs:annotation>
+    <xs:documentation>
+      First the built-in primitive datatypes.  These definitions are for
+      information only, the real built-in definitions are magic.
+    </xs:documentation>
+
+    <xs:documentation>
+      For each built-in datatype in this schema (both primitive and
+      derived) can be uniquely addressed via a URI constructed
+      as follows:
+        1) the base URI is the URI of the XML Schema namespace
+        2) the fragment identifier is the name of the datatype
+
+      For example, to address the int datatype, the URI is:
+
+        http://www.w3.org/2001/XMLSchema#int
+
+      Additionally, each facet definition element can be uniquely
+      addressed via a URI constructed as follows:
+        1) the base URI is the URI of the XML Schema namespace
+        2) the fragment identifier is the name of the facet
+
+      For example, to address the maxInclusive facet, the URI is:
+
+        http://www.w3.org/2001/XMLSchema#maxInclusive
+
+      Additionally, each facet usage in a built-in datatype definition
+      can be uniquely addressed via a URI constructed as follows:
+        1) the base URI is the URI of the XML Schema namespace
+        2) the fragment identifier is the name of the datatype, followed
+           by a period (".") followed by the name of the facet
+
+      For example, to address the usage of the maxInclusive facet in
+      the definition of int, the URI is:
+
+        http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+    </xs:documentation>
+  </xs:annotation>
+
+  <xs:simpleType name="string" id="string">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality" value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+                source="http://www.w3.org/TR/xmlschema-2/#string"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="preserve" id="string.preserve"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="boolean" id="boolean">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality" value="finite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#boolean"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse" fixed="true"
+        id="boolean.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="float" id="float">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="total"/>
+        <hfp:hasProperty name="bounded" value="true"/>
+        <hfp:hasProperty name="cardinality" value="finite"/>
+        <hfp:hasProperty name="numeric" value="true"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#float"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse" fixed="true"
+        id="float.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="double" id="double">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="total"/>
+        <hfp:hasProperty name="bounded" value="true"/>
+        <hfp:hasProperty name="cardinality" value="finite"/>
+        <hfp:hasProperty name="numeric" value="true"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#double"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="double.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="decimal" id="decimal">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="totalDigits"/>
+        <hfp:hasFacet name="fractionDigits"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="total"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="true"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#decimal"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="decimal.whiteSpace"/>
+    </xs:restriction>
+   </xs:simpleType>
+
+   <xs:simpleType name="duration" id="duration">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#duration"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="duration.whiteSpace"/>
+    </xs:restriction>
+   </xs:simpleType>
+
+ <xs:simpleType name="dateTime" id="dateTime">
+    <xs:annotation>
+    <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#dateTime"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="dateTime.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="time" id="time">
+    <xs:annotation>
+    <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#time"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="time.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="date" id="date">
+   <xs:annotation>
+    <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#date"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="date.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="gYearMonth" id="gYearMonth">
+   <xs:annotation>
+    <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#gYearMonth"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="gYearMonth.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="gYear" id="gYear">
+    <xs:annotation>
+    <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#gYear"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="gYear.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+ <xs:simpleType name="gMonthDay" id="gMonthDay">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+       <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#gMonthDay"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+         <xs:whiteSpace value="collapse" fixed="true"
+                id="gMonthDay.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="gDay" id="gDay">
+    <xs:annotation>
+  <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#gDay"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+         <xs:whiteSpace value="collapse"  fixed="true"
+                id="gDay.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+ <xs:simpleType name="gMonth" id="gMonth">
+    <xs:annotation>
+  <xs:appinfo>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="maxInclusive"/>
+        <hfp:hasFacet name="maxExclusive"/>
+        <hfp:hasFacet name="minInclusive"/>
+        <hfp:hasFacet name="minExclusive"/>
+        <hfp:hasProperty name="ordered" value="partial"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#gMonth"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+         <xs:whiteSpace value="collapse"  fixed="true"
+                id="gMonth.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+   <xs:simpleType name="hexBinary" id="hexBinary">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#binary"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse" fixed="true"
+        id="hexBinary.whiteSpace"/>
+    </xs:restriction>
+   </xs:simpleType>
+
+ <xs:simpleType name="base64Binary" id="base64Binary">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+                source="http://www.w3.org/TR/xmlschema-2/#base64Binary"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse" fixed="true"
+        id="base64Binary.whiteSpace"/>
+    </xs:restriction>
+   </xs:simpleType>
+
+   <xs:simpleType name="anyURI" id="anyURI">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#anyURI"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="anyURI.whiteSpace"/>
+    </xs:restriction>
+   </xs:simpleType>
+
+  <xs:simpleType name="QName" id="QName">
+    <xs:annotation>
+        <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#QName"/>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="QName.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+   <xs:simpleType name="NOTATION" id="NOTATION">
+    <xs:annotation>
+        <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#NOTATION"/>
+      <xs:documentation>
+        NOTATION cannot be used directly in a schema; rather a type
+        must be derived from it by specifying at least one enumeration
+        facet whose value is the name of a NOTATION declared in the
+        schema.
+      </xs:documentation>
+    </xs:annotation>
+    <xs:restriction base="xs:anySimpleType">
+      <xs:whiteSpace value="collapse"  fixed="true"
+        id="NOTATION.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:annotation>
+    <xs:documentation>
+      Now the derived primitive types
+    </xs:documentation>
+  </xs:annotation>
+
+  <xs:simpleType name="normalizedString" id="normalizedString">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#normalizedString"/>
+    </xs:annotation>
+    <xs:restriction base="xs:string">
+      <xs:whiteSpace value="replace"
+        id="normalizedString.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="token" id="token">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#token"/>
+    </xs:annotation>
+    <xs:restriction base="xs:normalizedString">
+      <xs:whiteSpace value="collapse" id="token.whiteSpace"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="language" id="language">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#language"/>
+    </xs:annotation>
+    <xs:restriction base="xs:token">
+      <xs:pattern
+        value="[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"
+                id="language.pattern">
+        <xs:annotation>
+          <xs:documentation
+                source="http://www.ietf.org/rfc/rfc3066.txt">
+            pattern specifies the content of section 2.12 of XML 1.0e2
+            and RFC 3066 (Revised version of RFC 1766).
+          </xs:documentation>
+        </xs:annotation>
+      </xs:pattern>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="IDREFS" id="IDREFS">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#IDREFS"/>
+    </xs:annotation>
+    <xs:restriction>
+      <xs:simpleType>
+        <xs:list itemType="xs:IDREF"/>
+      </xs:simpleType>
+        <xs:minLength value="1" id="IDREFS.minLength"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="ENTITIES" id="ENTITIES">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#ENTITIES"/>
+    </xs:annotation>
+    <xs:restriction>
+      <xs:simpleType>
+        <xs:list itemType="xs:ENTITY"/>
+      </xs:simpleType>
+        <xs:minLength value="1" id="ENTITIES.minLength"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="NMTOKEN" id="NMTOKEN">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#NMTOKEN"/>
+    </xs:annotation>
+    <xs:restriction base="xs:token">
+      <xs:pattern value="\c+" id="NMTOKEN.pattern">
+        <xs:annotation>
+          <xs:documentation
+                source="http://www.w3.org/TR/REC-xml#NT-Nmtoken">
+            pattern matches production 7 from the XML spec
+          </xs:documentation>
+        </xs:annotation>
+      </xs:pattern>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="NMTOKENS" id="NMTOKENS">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasFacet name="length"/>
+        <hfp:hasFacet name="minLength"/>
+        <hfp:hasFacet name="maxLength"/>
+        <hfp:hasFacet name="enumeration"/>
+        <hfp:hasFacet name="whiteSpace"/>
+        <hfp:hasFacet name="pattern"/>
+        <hfp:hasProperty name="ordered" value="false"/>
+        <hfp:hasProperty name="bounded" value="false"/>
+        <hfp:hasProperty name="cardinality"
+                value="countably infinite"/>
+        <hfp:hasProperty name="numeric" value="false"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#NMTOKENS"/>
+    </xs:annotation>
+    <xs:restriction>
+      <xs:simpleType>
+        <xs:list itemType="xs:NMTOKEN"/>
+      </xs:simpleType>
+        <xs:minLength value="1" id="NMTOKENS.minLength"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="Name" id="Name">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#Name"/>
+    </xs:annotation>
+    <xs:restriction base="xs:token">
+      <xs:pattern value="\i\c*" id="Name.pattern">
+        <xs:annotation>
+          <xs:documentation
+                        source="http://www.w3.org/TR/REC-xml#NT-Name">
+            pattern matches production 5 from the XML spec
+          </xs:documentation>
+        </xs:annotation>
+      </xs:pattern>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="NCName" id="NCName">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#NCName"/>
+    </xs:annotation>
+    <xs:restriction base="xs:Name">
+      <xs:pattern value="[\i-[:]][\c-[:]]*" id="NCName.pattern">
+        <xs:annotation>
+          <xs:documentation
+                source="http://www.w3.org/TR/REC-xml-names/#NT-NCName">
+            pattern matches production 4 from the Namespaces in XML spec
+          </xs:documentation>
+        </xs:annotation>
+      </xs:pattern>
+    </xs:restriction>
+  </xs:simpleType>
+
+   <xs:simpleType name="ID" id="ID">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#ID"/>
+    </xs:annotation>
+    <xs:restriction base="xs:NCName"/>
+   </xs:simpleType>
+
+   <xs:simpleType name="IDREF" id="IDREF">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#IDREF"/>
+    </xs:annotation>
+    <xs:restriction base="xs:NCName"/>
+   </xs:simpleType>
+
+   <xs:simpleType name="ENTITY" id="ENTITY">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#ENTITY"/>
+    </xs:annotation>
+    <xs:restriction base="xs:NCName"/>
+   </xs:simpleType>
+
+  <xs:simpleType name="integer" id="integer">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#integer"/>
+    </xs:annotation>
+    <xs:restriction base="xs:decimal">
+      <xs:fractionDigits value="0" fixed="true" id="integer.fractionDigits"/>
+      <xs:pattern value="[\-+]?[0-9]+"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="nonPositiveInteger" id="nonPositiveInteger">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#nonPositiveInteger"/>
+    </xs:annotation>
+    <xs:restriction base="xs:integer">
+      <xs:maxInclusive value="0" id="nonPositiveInteger.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="negativeInteger" id="negativeInteger">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#negativeInteger"/>
+    </xs:annotation>
+    <xs:restriction base="xs:nonPositiveInteger">
+      <xs:maxInclusive value="-1" id="negativeInteger.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="long" id="long">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasProperty name="bounded" value="true"/>
+        <hfp:hasProperty name="cardinality" value="finite"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#long"/>
+    </xs:annotation>
+    <xs:restriction base="xs:integer">
+      <xs:minInclusive value="-9223372036854775808" id="long.minInclusive"/>
+      <xs:maxInclusive value="9223372036854775807" id="long.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="int" id="int">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#int"/>
+    </xs:annotation>
+    <xs:restriction base="xs:long">
+      <xs:minInclusive value="-2147483648" id="int.minInclusive"/>
+      <xs:maxInclusive value="2147483647" id="int.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="short" id="short">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#short"/>
+    </xs:annotation>
+    <xs:restriction base="xs:int">
+      <xs:minInclusive value="-32768" id="short.minInclusive"/>
+      <xs:maxInclusive value="32767" id="short.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="byte" id="byte">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#byte"/>
+    </xs:annotation>
+    <xs:restriction base="xs:short">
+      <xs:minInclusive value="-128" id="byte.minInclusive"/>
+      <xs:maxInclusive value="127" id="byte.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="nonNegativeInteger" id="nonNegativeInteger">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#nonNegativeInteger"/>
+    </xs:annotation>
+    <xs:restriction base="xs:integer">
+      <xs:minInclusive value="0" id="nonNegativeInteger.minInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="unsignedLong" id="unsignedLong">
+    <xs:annotation>
+      <xs:appinfo>
+        <hfp:hasProperty name="bounded" value="true"/>
+        <hfp:hasProperty name="cardinality" value="finite"/>
+      </xs:appinfo>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#unsignedLong"/>
+    </xs:annotation>
+    <xs:restriction base="xs:nonNegativeInteger">
+      <xs:maxInclusive value="18446744073709551615"
+        id="unsignedLong.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="unsignedInt" id="unsignedInt">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#unsignedInt"/>
+    </xs:annotation>
+    <xs:restriction base="xs:unsignedLong">
+      <xs:maxInclusive value="4294967295"
+        id="unsignedInt.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="unsignedShort" id="unsignedShort">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#unsignedShort"/>
+    </xs:annotation>
+    <xs:restriction base="xs:unsignedInt">
+      <xs:maxInclusive value="65535"
+        id="unsignedShort.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="unsignedByte" id="unsignedByte">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#unsignedByte"/>
+    </xs:annotation>
+    <xs:restriction base="xs:unsignedShort">
+      <xs:maxInclusive value="255" id="unsignedByte.maxInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+  <xs:simpleType name="positiveInteger" id="positiveInteger">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#positiveInteger"/>
+    </xs:annotation>
+    <xs:restriction base="xs:nonNegativeInteger">
+      <xs:minInclusive value="1" id="positiveInteger.minInclusive"/>
+    </xs:restriction>
+  </xs:simpleType>
+
+ <xs:simpleType name="derivationControl">
+  <xs:annotation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:NMTOKEN">
+   <xs:enumeration value="substitution"/>
+   <xs:enumeration value="extension"/>
+   <xs:enumeration value="restriction"/>
+   <xs:enumeration value="list"/>
+   <xs:enumeration value="union"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:group name="simpleDerivation">
+  <xs:choice>
+    <xs:element ref="xs:restriction"/>
+    <xs:element ref="xs:list"/>
+    <xs:element ref="xs:union"/>
+  </xs:choice>
+ </xs:group>
+
+ <xs:simpleType name="simpleDerivationSet">
+  <xs:annotation>
+   <xs:documentation>
+   #all or (possibly empty) subset of {restriction, union, list}
+   </xs:documentation>
+   <xs:documentation>
+   A utility type, not for public use</xs:documentation>
+  </xs:annotation>
+  <xs:union>
+   <xs:simpleType>
+    <xs:restriction base="xs:token">
+     <xs:enumeration value="#all"/>
+    </xs:restriction>
+   </xs:simpleType>
+   <xs:simpleType>
+    <xs:list>
+     <xs:simpleType>
+      <xs:restriction base="xs:derivationControl">
+       <xs:enumeration value="list"/>
+       <xs:enumeration value="union"/>
+       <xs:enumeration value="restriction"/>
+      </xs:restriction>
+     </xs:simpleType>
+    </xs:list>
+   </xs:simpleType>
+  </xs:union>
+ </xs:simpleType>
+
+  <xs:complexType name="simpleType" abstract="true">
+    <xs:complexContent>
+      <xs:extension base="xs:annotated">
+        <xs:group ref="xs:simpleDerivation"/>
+        <xs:attribute name="final" type="xs:simpleDerivationSet"/>
+        <xs:attribute name="name" type="xs:NCName">
+          <xs:annotation>
+            <xs:documentation>
+              Can be restricted to required or forbidden
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:complexType name="topLevelSimpleType">
+    <xs:complexContent>
+      <xs:restriction base="xs:simpleType">
+        <xs:sequence>
+          <xs:element ref="xs:annotation" minOccurs="0"/>
+          <xs:group ref="xs:simpleDerivation"/>
+        </xs:sequence>
+        <xs:attribute name="name" use="required"
+             type="xs:NCName">
+          <xs:annotation>
+            <xs:documentation>
+              Required at the top level
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+       <xs:anyAttribute namespace="##other" processContents="lax"/>
+      </xs:restriction>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:complexType name="localSimpleType">
+    <xs:complexContent>
+      <xs:restriction base="xs:simpleType">
+        <xs:sequence>
+          <xs:element ref="xs:annotation" minOccurs="0"/>
+          <xs:group ref="xs:simpleDerivation"/>
+        </xs:sequence>
+        <xs:attribute name="name" use="prohibited">
+          <xs:annotation>
+            <xs:documentation>
+              Forbidden when nested
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="final" use="prohibited"/>
+       <xs:anyAttribute namespace="##other" processContents="lax"/>
+      </xs:restriction>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:element name="simpleType" type="xs:topLevelSimpleType" id="simpleType">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-simpleType"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:group name="facets">
+   <xs:annotation>
+    <xs:documentation>
+       We should use a substitution group for facets, but
+       that's ruled out because it would allow users to
+       add their own, which we're not ready for yet.
+    </xs:documentation>
+   </xs:annotation>
+   <xs:choice>
+    <xs:element ref="xs:minExclusive"/>
+    <xs:element ref="xs:minInclusive"/>
+    <xs:element ref="xs:maxExclusive"/>
+    <xs:element ref="xs:maxInclusive"/>
+    <xs:element ref="xs:totalDigits"/>
+    <xs:element ref="xs:fractionDigits"/>
+    <xs:element ref="xs:length"/>
+    <xs:element ref="xs:minLength"/>
+    <xs:element ref="xs:maxLength"/>
+    <xs:element ref="xs:enumeration"/>
+    <xs:element ref="xs:whiteSpace"/>
+    <xs:element ref="xs:pattern"/>
+   </xs:choice>
+  </xs:group>
+
+  <xs:group name="simpleRestrictionModel">
+   <xs:sequence>
+    <xs:element name="simpleType" type="xs:localSimpleType" minOccurs="0"/>
+    <xs:group ref="xs:facets" minOccurs="0" maxOccurs="unbounded"/>
+   </xs:sequence>
+  </xs:group>
+
+  <xs:element name="restriction" id="restriction">
+   <xs:complexType>
+    <xs:annotation>
+      <xs:documentation
+                source="http://www.w3.org/TR/xmlschema-2/#element-restriction">
+          base attribute and simpleType child are mutually
+          exclusive, but one or other is required
+        </xs:documentation>
+      </xs:annotation>
+      <xs:complexContent>
+        <xs:extension base="xs:annotated">
+         <xs:group ref="xs:simpleRestrictionModel"/>
+         <xs:attribute name="base" type="xs:QName" use="optional"/>
+        </xs:extension>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="list" id="list">
+   <xs:complexType>
+    <xs:annotation>
+      <xs:documentation
+                source="http://www.w3.org/TR/xmlschema-2/#element-list">
+          itemType attribute and simpleType child are mutually
+          exclusive, but one or other is required
+        </xs:documentation>
+      </xs:annotation>
+      <xs:complexContent>
+        <xs:extension base="xs:annotated">
+          <xs:sequence>
+            <xs:element name="simpleType" type="xs:localSimpleType"
+                minOccurs="0"/>
+          </xs:sequence>
+          <xs:attribute name="itemType" type="xs:QName" use="optional"/>
+        </xs:extension>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="union" id="union">
+   <xs:complexType>
+    <xs:annotation>
+      <xs:documentation
+                source="http://www.w3.org/TR/xmlschema-2/#element-union">
+          memberTypes attribute must be non-empty or there must be
+          at least one simpleType child
+        </xs:documentation>
+      </xs:annotation>
+      <xs:complexContent>
+        <xs:extension base="xs:annotated">
+          <xs:sequence>
+            <xs:element name="simpleType" type="xs:localSimpleType"
+                minOccurs="0" maxOccurs="unbounded"/>
+          </xs:sequence>
+          <xs:attribute name="memberTypes" use="optional">
+            <xs:simpleType>
+              <xs:list itemType="xs:QName"/>
+            </xs:simpleType>
+          </xs:attribute>
+        </xs:extension>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:complexType name="facet">
+    <xs:complexContent>
+      <xs:extension base="xs:annotated">
+        <xs:attribute name="value" use="required"/>
+        <xs:attribute name="fixed" type="xs:boolean" use="optional"
+                      default="false"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+
+ <xs:complexType name="noFixedFacet">
+  <xs:complexContent>
+   <xs:restriction base="xs:facet">
+    <xs:sequence>
+     <xs:element ref="xs:annotation" minOccurs="0"/>
+    </xs:sequence>
+    <xs:attribute name="fixed" use="prohibited"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+   </xs:restriction>
+  </xs:complexContent>
+ </xs:complexType>
+
+  <xs:element name="minExclusive" id="minExclusive" type="xs:facet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-minExclusive"/>
+    </xs:annotation>
+  </xs:element>
+  <xs:element name="minInclusive" id="minInclusive" type="xs:facet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-minInclusive"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:element name="maxExclusive" id="maxExclusive" type="xs:facet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-maxExclusive"/>
+    </xs:annotation>
+  </xs:element>
+  <xs:element name="maxInclusive" id="maxInclusive" type="xs:facet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-maxInclusive"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:complexType name="numFacet">
+    <xs:complexContent>
+      <xs:restriction base="xs:facet">
+       <xs:sequence>
+         <xs:element ref="xs:annotation" minOccurs="0"/>
+       </xs:sequence>
+       <xs:attribute name="value" type="xs:nonNegativeInteger" use="required"/>
+       <xs:anyAttribute namespace="##other" processContents="lax"/>
+      </xs:restriction>
+    </xs:complexContent>
+  </xs:complexType>
+
+  <xs:element name="totalDigits" id="totalDigits">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-totalDigits"/>
+    </xs:annotation>
+    <xs:complexType>
+      <xs:complexContent>
+        <xs:restriction base="xs:numFacet">
+          <xs:sequence>
+            <xs:element ref="xs:annotation" minOccurs="0"/>
+          </xs:sequence>
+          <xs:attribute name="value" type="xs:positiveInteger" use="required"/>
+         <xs:anyAttribute namespace="##other" processContents="lax"/>
+        </xs:restriction>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+  <xs:element name="fractionDigits" id="fractionDigits" type="xs:numFacet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-fractionDigits"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:element name="length" id="length" type="xs:numFacet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-length"/>
+    </xs:annotation>
+  </xs:element>
+  <xs:element name="minLength" id="minLength" type="xs:numFacet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-minLength"/>
+    </xs:annotation>
+  </xs:element>
+  <xs:element name="maxLength" id="maxLength" type="xs:numFacet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-maxLength"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:element name="enumeration" id="enumeration" type="xs:noFixedFacet">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-enumeration"/>
+    </xs:annotation>
+  </xs:element>
+
+  <xs:element name="whiteSpace" id="whiteSpace">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-whiteSpace"/>
+    </xs:annotation>
+    <xs:complexType>
+      <xs:complexContent>
+        <xs:restriction base="xs:facet">
+          <xs:sequence>
+            <xs:element ref="xs:annotation" minOccurs="0"/>
+          </xs:sequence>
+          <xs:attribute name="value" use="required">
+            <xs:simpleType>
+              <xs:restriction base="xs:NMTOKEN">
+                <xs:enumeration value="preserve"/>
+                <xs:enumeration value="replace"/>
+                <xs:enumeration value="collapse"/>
+              </xs:restriction>
+            </xs:simpleType>
+          </xs:attribute>
+         <xs:anyAttribute namespace="##other" processContents="lax"/>
+        </xs:restriction>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:element name="pattern" id="pattern">
+    <xs:annotation>
+      <xs:documentation
+        source="http://www.w3.org/TR/xmlschema-2/#element-pattern"/>
+    </xs:annotation>
+    <xs:complexType>
+      <xs:complexContent>
+        <xs:restriction base="xs:noFixedFacet">
+          <xs:sequence>
+            <xs:element ref="xs:annotation" minOccurs="0"/>
+          </xs:sequence>
+          <xs:attribute name="value" type="xs:string" use="required"/>
+         <xs:anyAttribute namespace="##other" processContents="lax"/>
+        </xs:restriction>
+      </xs:complexContent>
+    </xs:complexType>
+  </xs:element>
+
+</xs:schema>
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/cr.xsd
new file mode 100644 (file)
index 0000000..11ab59a
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+       xmlns:cr="http://argeo.org/ns/cr"
+       targetNamespace="http://argeo.org/ns/cr" elementFormDefault="qualified"
+       xml:lang="en-GB">
+
+       <xs:element name="root">
+               <xs:complexType>
+                       <xs:sequence>
+                               <xs:any minOccurs="0" />
+                       </xs:sequence>
+               </xs:complexType>
+       </xs:element>
+
+       <xs:attribute name="uuid" type="cr:uuid" />
+
+       <!-- UUID -->
+       <xs:simpleType name="uuid">
+               <xs:annotation>
+                       <xs:documentation>An UUID as defined in RFC4122.
+                       </xs:documentation>
+               </xs:annotation>
+               <xs:restriction base="xs:string">
+                       <xs:pattern
+                               value="([0-9]|[a-f]|[A-F]){8}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){12}" />
+               </xs:restriction>
+       </xs:simpleType>
+
+       <!-- UUID URN -->
+       <xs:simpleType name="uuidUrn">
+               <xs:annotation>
+                       <xs:documentation>The URN of an UUID as defined in RFC4122.
+                       </xs:documentation>
+               </xs:annotation>
+               <xs:restriction base="xs:anyURI">
+                       <xs:pattern
+                               value="urn:uuid:([0-9]|[a-f]|[A-F]){8}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){4}-([0-9]|[a-f]|[A-F]){12}" />
+               </xs:restriction>
+       </xs:simpleType>
+</xs:schema> 
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/datatypes.dtd
new file mode 100644 (file)
index 0000000..8e48553
--- /dev/null
@@ -0,0 +1,203 @@
+<!--
+        DTD for XML Schemas: Part 2: Datatypes
+        $Id: datatypes.dtd,v 1.23 2001/03/16 17:36:30 ht Exp $
+        Note this DTD is NOT normative, or even definitive. - - the
+        prose copy in the datatypes REC is the definitive version
+        (which shouldn't differ from this one except for this comment
+        and entity expansions, but just in case)
+  -->
+
+<!--
+        This DTD cannot be used on its own, it is intended
+        only for incorporation in XMLSchema.dtd, q.v.
+  -->
+
+<!-- Define all the element names, with optional prefix -->
+<!ENTITY % simpleType "%p;simpleType">
+<!ENTITY % restriction "%p;restriction">
+<!ENTITY % list "%p;list">
+<!ENTITY % union "%p;union">
+<!ENTITY % maxExclusive "%p;maxExclusive">
+<!ENTITY % minExclusive "%p;minExclusive">
+<!ENTITY % maxInclusive "%p;maxInclusive">
+<!ENTITY % minInclusive "%p;minInclusive">
+<!ENTITY % totalDigits "%p;totalDigits">
+<!ENTITY % fractionDigits "%p;fractionDigits">
+<!ENTITY % length "%p;length">
+<!ENTITY % minLength "%p;minLength">
+<!ENTITY % maxLength "%p;maxLength">
+<!ENTITY % enumeration "%p;enumeration">
+<!ENTITY % whiteSpace "%p;whiteSpace">
+<!ENTITY % pattern "%p;pattern">
+
+<!--
+        Customisation entities for the ATTLIST of each element
+        type. Define one of these if your schema takes advantage
+        of the anyAttribute='##other' in the schema for schemas
+  -->
+
+<!ENTITY % simpleTypeAttrs "">
+<!ENTITY % restrictionAttrs "">
+<!ENTITY % listAttrs "">
+<!ENTITY % unionAttrs "">
+<!ENTITY % maxExclusiveAttrs "">
+<!ENTITY % minExclusiveAttrs "">
+<!ENTITY % maxInclusiveAttrs "">
+<!ENTITY % minInclusiveAttrs "">
+<!ENTITY % totalDigitsAttrs "">
+<!ENTITY % fractionDigitsAttrs "">
+<!ENTITY % lengthAttrs "">
+<!ENTITY % minLengthAttrs "">
+<!ENTITY % maxLengthAttrs "">
+<!ENTITY % enumerationAttrs "">
+<!ENTITY % whiteSpaceAttrs "">
+<!ENTITY % patternAttrs "">
+
+<!-- Define some entities for informative use as attribute
+        types -->
+<!ENTITY % URIref "CDATA">
+<!ENTITY % XPathExpr "CDATA">
+<!ENTITY % QName "NMTOKEN">
+<!ENTITY % QNames "NMTOKENS">
+<!ENTITY % NCName "NMTOKEN">
+<!ENTITY % nonNegativeInteger "NMTOKEN">
+<!ENTITY % boolean "(true|false)">
+<!ENTITY % simpleDerivationSet "CDATA">
+<!--
+        #all or space-separated list drawn from derivationChoice
+  -->
+
+<!--
+        Note that the use of 'facet' below is less restrictive
+        than is really intended:  There should in fact be no
+        more than one of each of minInclusive, minExclusive,
+        maxInclusive, maxExclusive, totalDigits, fractionDigits,
+        length, maxLength, minLength within datatype,
+        and the min- and max- variants of Inclusive and Exclusive
+        are mutually exclusive. On the other hand,  pattern and
+        enumeration may repeat.
+  -->
+<!ENTITY % minBound "(%minInclusive; | %minExclusive;)">
+<!ENTITY % maxBound "(%maxInclusive; | %maxExclusive;)">
+<!ENTITY % bounds "%minBound; | %maxBound;">
+<!ENTITY % numeric "%totalDigits; | %fractionDigits;">
+<!ENTITY % ordered "%bounds; | %numeric;">
+<!ENTITY % unordered
+   "%pattern; | %enumeration; | %whiteSpace; | %length; |
+   %maxLength; | %minLength;">
+<!ENTITY % facet "%ordered; | %unordered;">
+<!ENTITY % facetAttr 
+        "value CDATA #REQUIRED
+        id ID #IMPLIED">
+<!ENTITY % fixedAttr "fixed %boolean; #IMPLIED">
+<!ENTITY % facetModel "(%annotation;)?">
+<!ELEMENT %simpleType;
+        ((%annotation;)?, (%restriction; | %list; | %union;))>
+<!ATTLIST %simpleType;
+    name      %NCName; #IMPLIED
+    final     %simpleDerivationSet; #IMPLIED
+    id        ID       #IMPLIED
+    %simpleTypeAttrs;>
+<!-- name is required at top level -->
+<!ELEMENT %restriction; ((%annotation;)?,
+                         (%restriction1; |
+                          ((%simpleType;)?,(%facet;)*)),
+                         (%attrDecls;))>
+<!ATTLIST %restriction;
+    base      %QName;                  #IMPLIED
+    id        ID       #IMPLIED
+    %restrictionAttrs;>
+<!--
+        base and simpleType child are mutually exclusive,
+        one is required.
+
+        restriction is shared between simpleType and
+        simpleContent and complexContent (in XMLSchema.xsd).
+        restriction1 is for the latter cases, when this
+        is restricting a complex type, as is attrDecls.
+  -->
+<!ELEMENT %list; ((%annotation;)?,(%simpleType;)?)>
+<!ATTLIST %list;
+    itemType      %QName;             #IMPLIED
+    id        ID       #IMPLIED
+    %listAttrs;>
+<!--
+        itemType and simpleType child are mutually exclusive,
+        one is required
+  -->
+<!ELEMENT %union; ((%annotation;)?,(%simpleType;)*)>
+<!ATTLIST %union;
+    id            ID       #IMPLIED
+    memberTypes   %QNames;            #IMPLIED
+    %unionAttrs;>
+<!--
+        At least one item in memberTypes or one simpleType
+        child is required
+  -->
+
+<!ELEMENT %maxExclusive; %facetModel;>
+<!ATTLIST %maxExclusive;
+        %facetAttr;
+        %fixedAttr;
+        %maxExclusiveAttrs;>
+<!ELEMENT %minExclusive; %facetModel;>
+<!ATTLIST %minExclusive;
+        %facetAttr;
+        %fixedAttr;
+        %minExclusiveAttrs;>
+
+<!ELEMENT %maxInclusive; %facetModel;>
+<!ATTLIST %maxInclusive;
+        %facetAttr;
+        %fixedAttr;
+        %maxInclusiveAttrs;>
+<!ELEMENT %minInclusive; %facetModel;>
+<!ATTLIST %minInclusive;
+        %facetAttr;
+        %fixedAttr;
+        %minInclusiveAttrs;>
+
+<!ELEMENT %totalDigits; %facetModel;>
+<!ATTLIST %totalDigits;
+        %facetAttr;
+        %fixedAttr;
+        %totalDigitsAttrs;>
+<!ELEMENT %fractionDigits; %facetModel;>
+<!ATTLIST %fractionDigits;
+        %facetAttr;
+        %fixedAttr;
+        %fractionDigitsAttrs;>
+
+<!ELEMENT %length; %facetModel;>
+<!ATTLIST %length;
+        %facetAttr;
+        %fixedAttr;
+        %lengthAttrs;>
+<!ELEMENT %minLength; %facetModel;>
+<!ATTLIST %minLength;
+        %facetAttr;
+        %fixedAttr;
+        %minLengthAttrs;>
+<!ELEMENT %maxLength; %facetModel;>
+<!ATTLIST %maxLength;
+        %facetAttr;
+        %fixedAttr;
+        %maxLengthAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %enumeration; %facetModel;>
+<!ATTLIST %enumeration;
+        %facetAttr;
+        %enumerationAttrs;>
+
+<!ELEMENT %whiteSpace; %facetModel;>
+<!ATTLIST %whiteSpace;
+        %facetAttr;
+        %fixedAttr;
+        %whiteSpaceAttrs;>
+
+<!-- This one can be repeated -->
+<!ELEMENT %pattern; %facetModel;>
+<!ATTLIST %pattern;
+        %facetAttr;
+        %patternAttrs;>
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/schema-for-xslt20.xsd
new file mode 100644 (file)
index 0000000..30b1c5a
--- /dev/null
@@ -0,0 +1,1134 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" targetNamespace="http://www.w3.org/1999/XSL/Transform" elementFormDefault="qualified">
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+  <xs:documentation>
+  
+    This is a schema for XSLT 2.0 stylesheets.
+    
+    It defines all the elements that appear in the XSLT namespace; it also
+    provides hooks that allow the inclusion of user-defined literal result elements,
+    extension instructions, and top-level data elements.
+    
+    The schema is derived (with kind permission) from a schema for XSLT 1.0 stylesheets
+    produced by Asir S Vedamuthu of WebMethods Inc.
+    
+    This schema is available for use under the conditions of the W3C Software License
+    published at http://www.w3.org/Consortium/Legal/copyright-software-19980720
+    
+    The schema is organized as follows:
+    
+    PART A: definitions of complex types and model groups used as the basis 
+            for element definitions
+    PART B: definitions of individual XSLT elements
+    PART C: definitions for literal result elements
+    PART D: definitions of simple types used in attribute definitions
+    
+    This schema does not attempt to define all the constraints that apply to a valid
+    XSLT 2.0 stylesheet module. It is the intention that all valid stylesheet modules 
+    should conform to this schema; however, the schema is non-normative and in the event 
+    of any conflict, the text of the Recommendation takes precedence.
+
+    This schema does not implement the special rules that apply when a stylesheet
+    has sections that use forwards-compatible-mode. In this mode, setting version="3.0"
+    allows elements from the XSLT namespace to be used that are not defined in XSLT 2.0.
+
+    Simplified stylesheets (those with a literal result element as the outermost element)
+    will validate against this schema only if validation starts in lax mode.
+    
+    This version is dated 2007-03-16
+    Authors: Michael H Kay, Saxonica Limited
+             Jeni Tennison, Jeni Tennison Consulting Ltd.
+             
+    2007-03-15: added xsl:document element
+                revised xsl:sequence element
+                see http://www.w3.org/Bugs/Public/show_bug.cgi?id=4237         
+    
+  </xs:documentation>
+</xs:annotation>   
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<!--
+The declaration of xml:space and xml:lang may need to be commented out because
+of problems processing the schema using various tools
+-->
+      
+<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
+
+<!-- 
+    An XSLT stylesheet may contain an in-line schema within an xsl:import-schema element,
+    so the Schema for schemas needs to be imported
+-->
+  
+<xs:import namespace="http://www.w3.org/2001/XMLSchema" schemaLocation="XMLSchema.xsd"/>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+  <xs:documentation>
+    PART A: definitions of complex types and model groups used as the basis 
+            for element definitions
+  </xs:documentation>
+</xs:annotation>   
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:complexType name="generic-element-type" mixed="true">
+  <xs:attribute name="default-collation" type="xsl:uri-list"/>
+  <xs:attribute name="exclude-result-prefixes" type="xsl:prefix-list-or-all"/>
+  <xs:attribute name="extension-element-prefixes" type="xsl:prefix-list"/>
+  <xs:attribute name="use-when" type="xsl:expression"/>
+  <xs:attribute name="xpath-default-namespace" type="xs:anyURI"/>
+  <xs:anyAttribute namespace="##other" processContents="lax"/>
+</xs:complexType>
+
+<xs:complexType name="versioned-element-type" mixed="true">
+  <xs:complexContent>
+    <xs:extension base="xsl:generic-element-type">    
+      <xs:attribute name="version" type="xs:decimal" use="optional"/>
+    </xs:extension>
+  </xs:complexContent>
+</xs:complexType>
+
+<xs:complexType name="element-only-versioned-element-type" mixed="false">
+  <xs:complexContent>
+    <xs:restriction base="xsl:versioned-element-type">
+      <xs:anyAttribute namespace="##other" processContents="lax"/>
+    </xs:restriction>
+  </xs:complexContent>
+</xs:complexType>
+
+<xs:complexType name="sequence-constructor">
+  <xs:complexContent mixed="true">
+    <xs:extension base="xsl:versioned-element-type">    
+      <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:extension>
+  </xs:complexContent>
+</xs:complexType>
+
+<xs:group name="sequence-constructor-group">
+  <xs:choice>
+    <xs:element ref="xsl:variable"/>
+    <xs:element ref="xsl:instruction"/>
+    <xs:group ref="xsl:result-elements"/>
+  </xs:choice>
+</xs:group>
+
+<xs:element name="declaration" type="xsl:generic-element-type" abstract="true"/>
+
+<xs:element name="instruction" type="xsl:versioned-element-type" abstract="true"/>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+  <xs:documentation>
+    PART B: definitions of individual XSLT elements    
+    Elements are listed in alphabetical order.    
+  </xs:documentation>
+</xs:annotation>   
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:element name="analyze-string" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:matching-substring" minOccurs="0"/>
+          <xs:element ref="xsl:non-matching-substring" minOccurs="0"/>
+          <xs:element ref="xsl:fallback" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="select" type="xsl:expression" use="required"/>
+        <xs:attribute name="regex" type="xsl:avt" use="required"/>
+        <xs:attribute name="flags" type="xsl:avt" default=""/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="apply-imports" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:with-param" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="apply-templates" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+          <xs:element ref="xsl:sort"/>
+          <xs:element ref="xsl:with-param"/>
+        </xs:choice>
+        <xs:attribute name="select" type="xsl:expression" default="child::node()"/>
+        <xs:attribute name="mode" type="xsl:mode"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="attribute" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:avt" use="required"/>
+        <xs:attribute name="namespace" type="xsl:avt"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="separator" type="xsl:avt"/>   
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>        
+
+<xs:element name="attribute-set" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+          <xs:element ref="xsl:attribute"/>
+        </xs:sequence>
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="call-template" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:with-param" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="character-map" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:output-character" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="use-character-maps" type="xsl:QNames" default=""/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="choose" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:when" maxOccurs="unbounded"/>
+          <xs:element ref="xsl:otherwise" minOccurs="0"/>
+        </xs:sequence>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="comment" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="select" type="xsl:expression"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="copy" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="copy-namespaces" type="xsl:yes-or-no" default="yes"/>
+        <xs:attribute name="inherit-namespaces" type="xsl:yes-or-no" default="yes"/>
+        <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="copy-of" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:attribute name="select" type="xsl:expression" use="required"/>
+        <xs:attribute name="copy-namespaces" type="xsl:yes-or-no" default="yes"/>
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="document" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="decimal-format" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="name" type="xsl:QName"/>
+        <xs:attribute name="decimal-separator" type="xsl:char" default="."/>
+        <xs:attribute name="grouping-separator" type="xsl:char" default=","/>
+        <xs:attribute name="infinity" type="xs:string" default="Infinity"/>
+        <xs:attribute name="minus-sign" type="xsl:char" default="-"/>
+        <xs:attribute name="NaN" type="xs:string" default="NaN"/>
+        <xs:attribute name="percent" type="xsl:char" default="%"/>
+        <xs:attribute name="per-mille" type="xsl:char" default="‰"/>
+        <xs:attribute name="zero-digit" type="xsl:char" default="0"/>
+        <xs:attribute name="digit" type="xsl:char" default="#"/>
+        <xs:attribute name="pattern-separator" type="xsl:char" default=";"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="element" substitutionGroup="xsl:instruction">
+  <xs:complexType mixed="true">
+    <xs:complexContent>
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:avt" use="required"/>
+        <xs:attribute name="namespace" type="xsl:avt"/>
+        <xs:attribute name="inherit-namespaces" type="xsl:yes-or-no" default="yes"/>
+        <xs:attribute name="use-attribute-sets" type="xsl:QNames" default=""/>
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="fallback" substitutionGroup="xsl:instruction" type="xsl:sequence-constructor"/>
+
+<xs:element name="for-each" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:sort" minOccurs="0" maxOccurs="unbounded"/>
+          <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="select" type="xsl:expression" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="for-each-group" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:sort" minOccurs="0" maxOccurs="unbounded"/>
+          <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="select" type="xsl:expression" use="required"/>
+        <xs:attribute name="group-by" type="xsl:expression"/>
+        <xs:attribute name="group-adjacent" type="xsl:expression"/>            
+        <xs:attribute name="group-starting-with" type="xsl:pattern"/>            
+        <xs:attribute name="group-ending-with" type="xsl:pattern"/>            
+        <xs:attribute name="collation" type="xs:anyURI"/>            
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="function" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:param" minOccurs="0" maxOccurs="unbounded"/>
+          <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="override" type="xsl:yes-or-no" default="yes"/>
+        <xs:attribute name="as" type="xsl:sequence-type" default="item()*"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="if" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="test" type="xsl:expression" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="import">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="href" type="xs:anyURI" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="import-schema" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xs:schema" minOccurs="0" maxOccurs="1"/>
+        </xs:sequence>
+        <xs:attribute name="namespace" type="xs:anyURI"/>
+        <xs:attribute name="schema-location" type="xs:anyURI"/>                  
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="include" substitutionGroup="xsl:declaration">
+    <xs:complexType>
+      <xs:complexContent>
+        <xs:extension base="xsl:element-only-versioned-element-type">
+          <xs:attribute name="href" type="xs:anyURI" use="required"/>
+        </xs:extension>
+      </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="key" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="match" type="xsl:pattern" use="required"/>
+        <xs:attribute name="use" type="xsl:expression"/>
+        <xs:attribute name="collation" type="xs:anyURI"/>               
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="matching-substring" type="xsl:sequence-constructor"/>
+
+<xs:element name="message" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="terminate" type="xsl:avt" default="no"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="namespace" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:avt" use="required"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="namespace-alias" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="stylesheet-prefix" type="xsl:prefix-or-default" use="required"/>
+        <xs:attribute name="result-prefix" type="xsl:prefix-or-default" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="next-match" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+          <xs:element ref="xsl:with-param"/>
+          <xs:element ref="xsl:fallback"/>
+        </xs:choice>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="non-matching-substring" type="xsl:sequence-constructor"/>
+
+<xs:element name="number" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:attribute name="value" type="xsl:expression"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="level" type="xsl:level" default="single"/>
+        <xs:attribute name="count" type="xsl:pattern"/>
+        <xs:attribute name="from" type="xsl:pattern"/>
+        <xs:attribute name="format" type="xsl:avt" default="1"/>
+        <xs:attribute name="lang" type="xsl:avt"/>
+        <xs:attribute name="letter-value" type="xsl:avt"/>
+        <xs:attribute name="ordinal" type="xsl:avt"/>        
+        <xs:attribute name="grouping-separator" type="xsl:avt"/>
+        <xs:attribute name="grouping-size" type="xsl:avt"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="otherwise" type="xsl:sequence-constructor"/>
+
+<xs:element name="output" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:generic-element-type">
+        <xs:attribute name="name" type="xsl:QName"/>
+        <xs:attribute name="method" type="xsl:method"/>
+        <xs:attribute name="byte-order-mark" type="xsl:yes-or-no"/>
+        <xs:attribute name="cdata-section-elements" type="xsl:QNames"/>
+        <xs:attribute name="doctype-public" type="xs:string"/>
+        <xs:attribute name="doctype-system" type="xs:string"/>
+        <xs:attribute name="encoding" type="xs:string"/>
+        <xs:attribute name="escape-uri-attributes" type="xsl:yes-or-no"/>
+        <xs:attribute name="include-content-type" type="xsl:yes-or-no"/>
+        <xs:attribute name="indent" type="xsl:yes-or-no"/>
+        <xs:attribute name="media-type" type="xs:string"/>
+        <xs:attribute name="normalization-form" type="xs:NMTOKEN"/>
+        <xs:attribute name="omit-xml-declaration" type="xsl:yes-or-no"/>
+        <xs:attribute name="standalone" type="xsl:yes-or-no-or-omit"/>
+        <xs:attribute name="undeclare-prefixes" type="xsl:yes-or-no"/>
+        <xs:attribute name="use-character-maps" type="xsl:QNames"/>
+        <xs:attribute name="version" type="xs:NMTOKEN"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="output-character">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="character" type="xsl:char" use="required"/>
+        <xs:attribute name="string" type="xs:string" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="param">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="as" type="xsl:sequence-type"/>
+        <xs:attribute name="required" type="xsl:yes-or-no"/>
+        <xs:attribute name="tunnel" type="xsl:yes-or-no"/>        
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="perform-sort" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:sort" minOccurs="1" maxOccurs="unbounded"/>
+          <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="select" type="xsl:expression"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="preserve-space" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="elements" type="xsl:nametests" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="processing-instruction" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:avt" use="required"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="result-document" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="format" type="xsl:avt"/>
+        <xs:attribute name="href" type="xsl:avt"/>
+        <xs:attribute name="type" type="xsl:QName"/>
+        <xs:attribute name="validation" type="xsl:validation-type"/>
+        <xs:attribute name="method" type="xsl:avt"/>
+        <xs:attribute name="byte-order-mark" type="xsl:avt"/>
+        <xs:attribute name="cdata-section-elements" type="xsl:avt"/>
+        <xs:attribute name="doctype-public" type="xsl:avt"/>
+        <xs:attribute name="doctype-system" type="xsl:avt"/>
+        <xs:attribute name="encoding" type="xsl:avt"/>
+        <xs:attribute name="escape-uri-attributes" type="xsl:avt"/>
+        <xs:attribute name="include-content-type" type="xsl:avt"/>
+        <xs:attribute name="indent" type="xsl:avt"/>
+        <xs:attribute name="media-type" type="xsl:avt"/>
+        <xs:attribute name="normalization-form" type="xsl:avt"/>
+        <xs:attribute name="omit-xml-declaration" type="xsl:avt"/>
+        <xs:attribute name="standalone" type="xsl:avt"/>
+        <xs:attribute name="undeclare-prefixes" type="xsl:avt"/>
+        <xs:attribute name="use-character-maps" type="xsl:QNames"/>
+        <xs:attribute name="output-version" type="xsl:avt"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="sequence" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:sequence minOccurs="0" maxOccurs="unbounded">
+          <xs:element ref="xsl:fallback"/>
+        </xs:sequence>
+        <xs:attribute name="select" type="xsl:expression"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="sort">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="select" type="xsl:expression"/>  
+        <xs:attribute name="lang" type="xsl:avt"/>        
+        <xs:attribute name="data-type" type="xsl:avt" default="text"/>        
+        <xs:attribute name="order" type="xsl:avt" default="ascending"/>        
+        <xs:attribute name="case-order" type="xsl:avt"/>
+        <xs:attribute name="collation" type="xsl:avt"/>
+        <xs:attribute name="stable" type="xsl:yes-or-no"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="strip-space" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:element-only-versioned-element-type">
+        <xs:attribute name="elements" type="xsl:nametests" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="stylesheet" substitutionGroup="xsl:transform"/>
+
+<xs:element name="template" substitutionGroup="xsl:declaration">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:versioned-element-type">
+        <xs:sequence>
+          <xs:element ref="xsl:param" minOccurs="0" maxOccurs="unbounded"/>
+          <xs:group ref="xsl:sequence-constructor-group" minOccurs="0" maxOccurs="unbounded"/>
+        </xs:sequence>
+        <xs:attribute name="match" type="xsl:pattern"/>
+        <xs:attribute name="priority" type="xs:decimal"/>
+        <xs:attribute name="mode" type="xsl:modes"/>
+        <xs:attribute name="name" type="xsl:QName"/>
+        <xs:attribute name="as" type="xsl:sequence-type" default="item()*"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:complexType name="text-element-base-type">
+  <xs:simpleContent>
+    <xs:restriction base="xsl:versioned-element-type">
+      <xs:simpleType>
+        <xs:restriction base="xs:string"/>
+      </xs:simpleType>
+      <xs:anyAttribute namespace="##other" processContents="lax"/>
+    </xs:restriction>
+  </xs:simpleContent>
+</xs:complexType>
+
+<xs:element name="text" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:simpleContent>
+      <xs:extension base="xsl:text-element-base-type">
+        <xs:attribute name="disable-output-escaping" type="xsl:yes-or-no" default="no"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:complexType name="transform-element-base-type">
+  <xs:complexContent>
+    <xs:restriction base="xsl:element-only-versioned-element-type">
+      <xs:attribute name="version" type="xs:decimal" use="required"/>
+      <xs:anyAttribute namespace="##other" processContents="lax"/>
+    </xs:restriction>
+  </xs:complexContent>
+</xs:complexType>
+
+<xs:element name="transform">
+  <xs:complexType>
+    <xs:complexContent>
+      <xs:extension base="xsl:transform-element-base-type">
+        <xs:sequence>
+          <xs:element ref="xsl:import" minOccurs="0" maxOccurs="unbounded"/>
+          <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element ref="xsl:declaration"/>
+            <xs:element ref="xsl:variable"/>
+            <xs:element ref="xsl:param"/>              
+            <xs:any namespace="##other" processContents="lax"/> <!-- weaker than XSLT 1.0 -->
+          </xs:choice>
+        </xs:sequence>
+        <xs:attribute name="id" type="xs:ID"/>
+        <xs:attribute name="default-validation" type="xsl:validation-strip-or-preserve" default="strip"/>
+        <xs:attribute name="input-type-annotations" type="xsl:input-type-annotations-type" default="unspecified"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="value-of" substitutionGroup="xsl:instruction">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="separator" type="xsl:avt"/>            
+        <xs:attribute name="disable-output-escaping" type="xsl:yes-or-no" default="no"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="variable">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="select" type="xsl:expression" use="optional"/>
+        <xs:attribute name="as" type="xsl:sequence-type" use="optional"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="when">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="test" type="xsl:expression" use="required"/>
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<xs:element name="with-param">
+  <xs:complexType>
+    <xs:complexContent mixed="true">
+      <xs:extension base="xsl:sequence-constructor">
+        <xs:attribute name="name" type="xsl:QName" use="required"/>
+        <xs:attribute name="select" type="xsl:expression"/>
+        <xs:attribute name="as" type="xsl:sequence-type"/>
+        <xs:attribute name="tunnel" type="xsl:yes-or-no"/>   
+      </xs:extension>
+    </xs:complexContent>
+  </xs:complexType>
+</xs:element>
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+  <xs:documentation>
+    PART C: definition of literal result elements
+    
+    There are three ways to define the literal result elements
+    permissible in a stylesheet.
+    
+    (a) do nothing. This allows any element to be used as a literal
+        result element, provided it is not in the XSLT namespace
+    
+    (b) declare all permitted literal result elements as members
+        of the xsl:literal-result-element substitution group
+        
+    (c) redefine the model group xsl:result-elements to accommodate
+        all permitted literal result elements.
+        
+    Literal result elements are allowed to take certain attributes
+    in the XSLT namespace. These are defined in the attribute group
+    literal-result-element-attributes, which can be included in the
+    definition of any literal result element.
+    
+  </xs:documentation>
+</xs:annotation>   
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:element name="literal-result-element" abstract="true" type="xs:anyType"/>
+
+<xs:attributeGroup name="literal-result-element-attributes">
+  <xs:attribute name="default-collation" form="qualified" type="xsl:uri-list"/>
+  <xs:attribute name="extension-element-prefixes" form="qualified" type="xsl:prefixes"/>
+  <xs:attribute name="exclude-result-prefixes" form="qualified" type="xsl:prefixes"/>
+  <xs:attribute name="xpath-default-namespace" form="qualified" type="xs:anyURI"/>    
+  <xs:attribute name="inherit-namespaces" form="qualified" type="xsl:yes-or-no" default="yes"/>
+  <xs:attribute name="use-attribute-sets" form="qualified" type="xsl:QNames" default=""/>
+  <xs:attribute name="use-when" form="qualified" type="xsl:expression"/>
+  <xs:attribute name="version" form="qualified" type="xs:decimal"/>
+  <xs:attribute name="type" form="qualified" type="xsl:QName"/>
+  <xs:attribute name="validation" form="qualified" type="xsl:validation-type"/>
+</xs:attributeGroup>
+
+<xs:group name="result-elements">
+  <xs:choice>
+    <xs:element ref="xsl:literal-result-element"/>
+    <xs:any namespace="##other" processContents="lax"/>
+    <xs:any namespace="##local" processContents="lax"/>
+  </xs:choice>
+</xs:group>
+
+
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+<xs:annotation>
+  <xs:documentation>
+    PART D: definitions of simple types used in stylesheet attributes 
+  </xs:documentation>
+</xs:annotation>   
+<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+<xs:simpleType name="avt">
+  <xs:annotation>
+    <xs:documentation>
+      This type is used for all attributes that allow an attribute value template.
+      The general rules for the syntax of attribute value templates, and the specific
+      rules for each such attribute, are described in the XSLT 2.0 Recommendation.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:string"/>
+</xs:simpleType>
+
+<xs:simpleType name="char">
+  <xs:annotation>
+    <xs:documentation>
+      A string containing exactly one character.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:string">
+    <xs:length value="1"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="expression">
+  <xs:annotation>
+    <xs:documentation>
+      An XPath 2.0 expression.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:pattern value=".+"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="input-type-annotations-type">
+  <xs:annotation>
+    <xs:documentation>
+      Describes how type annotations in source documents are handled.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:enumeration value="preserve"/>
+    <xs:enumeration value="strip"/>   
+    <xs:enumeration value="unspecified"/>        
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="level">
+  <xs:annotation>
+    <xs:documentation>
+      The level attribute of xsl:number: 
+      one of single, multiple, or any.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:NCName">
+    <xs:enumeration value="single"/>
+    <xs:enumeration value="multiple"/>
+    <xs:enumeration value="any"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="mode">
+  <xs:annotation>
+    <xs:documentation>
+      The mode attribute of xsl:apply-templates: 
+      either a QName, or #current, or #default.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:union memberTypes="xsl:QName">
+    <xs:simpleType>
+      <xs:restriction base="xs:token">
+        <xs:enumeration value="#default"/>
+        <xs:enumeration value="#current"/>
+      </xs:restriction>
+    </xs:simpleType>
+  </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="modes">
+  <xs:annotation>
+    <xs:documentation>
+      The mode attribute of xsl:template: 
+      either a list, each member being either a QName or #default;
+      or the value #all
+    </xs:documentation>
+  </xs:annotation>
+  <xs:union>
+    <xs:simpleType>
+      <xs:list>
+        <xs:simpleType>
+          <xs:union memberTypes="xsl:QName">
+            <xs:simpleType>
+              <xs:restriction base="xs:token">
+                <xs:enumeration value="#default"/>
+              </xs:restriction>
+            </xs:simpleType>
+          </xs:union>
+        </xs:simpleType>
+      </xs:list>
+    </xs:simpleType>
+    <xs:simpleType>
+      <xs:restriction base="xs:token">
+        <xs:enumeration value="#all"/>
+      </xs:restriction>
+    </xs:simpleType>
+  </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="nametests">
+  <xs:annotation>
+    <xs:documentation>
+      A list of NameTests, as defined in the XPath 2.0 Recommendation.
+      Each NameTest is either a QName, or "*", or "prefix:*", or "*:localname"
+    </xs:documentation>
+  </xs:annotation>
+  <xs:list>
+    <xs:simpleType>
+      <xs:union memberTypes="xsl:QName">
+        <xs:simpleType>
+          <xs:restriction base="xs:token">
+            <xs:enumeration value="*"/>
+          </xs:restriction>
+        </xs:simpleType>
+        <xs:simpleType>
+          <xs:restriction base="xs:token">
+            <xs:pattern value="\i\c*:\*"/>
+            <xs:pattern value="\*:\i\c*"/>            
+          </xs:restriction>
+        </xs:simpleType>
+      </xs:union>
+    </xs:simpleType>
+  </xs:list>
+</xs:simpleType>
+
+<xs:simpleType name="prefixes">
+  <xs:list itemType="xs:NCName"/>
+</xs:simpleType>
+
+<xs:simpleType name="prefix-list-or-all">
+  <xs:union memberTypes="xsl:prefix-list">
+    <xs:simpleType>
+      <xs:restriction base="xs:token">
+        <xs:enumeration value="#all"/>
+      </xs:restriction>
+    </xs:simpleType>
+  </xs:union>
+</xs:simpleType>
+      
+<xs:simpleType name="prefix-list">
+  <xs:list itemType="xsl:prefix-or-default"/>
+</xs:simpleType>
+
+<xs:simpleType name="method">
+  <xs:annotation>
+    <xs:documentation>
+      The method attribute of xsl:output:
+      Either one of the recognized names "xml", "xhtml", "html", "text",
+      or a QName that must include a prefix.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:union>
+    <xs:simpleType>
+      <xs:restriction base="xs:NCName">
+        <xs:enumeration value="xml"/>
+        <xs:enumeration value="xhtml"/>
+        <xs:enumeration value="html"/>
+        <xs:enumeration value="text"/>
+      </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType>
+      <xs:restriction base="xsl:QName">
+        <xs:pattern value="\c*:\c*"/>
+      </xs:restriction>
+    </xs:simpleType>
+  </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="pattern">
+  <xs:annotation>
+    <xs:documentation>
+      A match pattern as defined in the XSLT 2.0 Recommendation.
+      The syntax for patterns is a restricted form of the syntax for
+      XPath 2.0 expressions.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xsl:expression"/>
+</xs:simpleType>
+
+<xs:simpleType name="prefix-or-default">
+  <xs:annotation>
+    <xs:documentation>
+      Either a namespace prefix, or #default.
+      Used in the xsl:namespace-alias element.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:union memberTypes="xs:NCName">
+    <xs:simpleType>
+      <xs:restriction base="xs:token">
+        <xs:enumeration value="#default"/>
+      </xs:restriction>
+    </xs:simpleType>
+  </xs:union>
+</xs:simpleType>
+
+<xs:simpleType name="QNames">
+  <xs:annotation>
+    <xs:documentation>
+      A list of QNames.
+      Used in the [xsl:]use-attribute-sets attribute of various elements,
+      and in the cdata-section-elements attribute of xsl:output
+    </xs:documentation>
+  </xs:annotation>
+  <xs:list itemType="xsl:QName"/>          
+</xs:simpleType>
+
+<xs:simpleType name="QName">
+  <xs:annotation>
+    <xs:documentation>
+      A QName.
+      This schema does not use the built-in type xs:QName, but rather defines its own
+      QName type. Although xs:QName would define the correct validation on these attributes,
+      a schema processor would expand unprefixed QNames incorrectly when constructing the PSVI,
+      because (as defined in XML Schema errata) an unprefixed xs:QName is assumed to be in
+      the default namespace, which is not the correct assumption for XSLT.
+      The data type is defined as a restriction of the built-in type Name, restricted
+      so that it can only contain one colon which must not be the first or last character.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:Name">
+    <xs:pattern value="([^:]+:)?[^:]+"/>      
+  </xs:restriction>        
+</xs:simpleType>
+
+<xs:simpleType name="sequence-type">
+  <xs:annotation>
+    <xs:documentation>
+      The description of a data type, conforming to the
+      SequenceType production defined in the XPath 2.0 Recommendation
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:pattern value=".+"/>      
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="uri-list">
+  <xs:list itemType="xs:anyURI"/>
+</xs:simpleType>
+
+<xs:simpleType name="validation-strip-or-preserve">
+  <xs:annotation>
+    <xs:documentation>
+      Describes different ways of type-annotating an element or attribute.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xsl:validation-type">
+    <xs:enumeration value="preserve"/>
+    <xs:enumeration value="strip"/>    
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="validation-type">
+  <xs:annotation>
+    <xs:documentation>
+      Describes different ways of type-annotating an element or attribute.
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:enumeration value="strict"/>
+    <xs:enumeration value="lax"/>
+    <xs:enumeration value="preserve"/>
+    <xs:enumeration value="strip"/>    
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="yes-or-no">
+  <xs:annotation>
+    <xs:documentation>
+      One of the values "yes" or "no".
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:enumeration value="yes"/>
+    <xs:enumeration value="no"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="yes-or-no-or-omit">
+  <xs:annotation>
+    <xs:documentation>
+      One of the values "yes" or "no" or "omit".
+    </xs:documentation>
+  </xs:annotation>
+  <xs:restriction base="xs:token">
+    <xs:enumeration value="yes"/>
+    <xs:enumeration value="no"/>
+    <xs:enumeration value="omit"/>
+  </xs:restriction>
+</xs:simpleType>
+
+</xs:schema>
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd
new file mode 100644 (file)
index 0000000..e7443f7
--- /dev/null
@@ -0,0 +1,449 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+        targetNamespace="DAV:"
+        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+        xmlns:dav="DAV:"
+        elementFormDefault="qualified">
+
+    <element name="activelock">
+        <complexType>
+            <sequence>
+                <element ref="dav:lockscope"/>
+                <element ref="dav:locktype"/>
+                <element ref="dav:depth"/>
+                <element ref="dav:owner" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:timeout" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:locktoken" minOccurs="0" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="lockentry">
+        <complexType>
+            <sequence>
+                <element ref="dav:lockscope"/>
+                <element ref="dav:locktype"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="lockinfo">
+        <complexType>
+            <sequence>
+                <element ref="dav:lockscope"/>
+                <element ref="dav:locktype"/>
+                <element ref="dav:owner" minOccurs="0" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="locktype">
+        <complexType>
+            <sequence>
+                <element ref="dav:write"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="write">
+        <complexType/>
+    </element>
+
+    <element name="lockscope">
+        <complexType>
+            <choice>
+                <element ref="dav:exclusive"/>
+                <element ref="dav:shared"/>
+            </choice>
+        </complexType>
+    </element>
+
+    <element name="exclusive">
+        <complexType/>
+    </element>
+
+    <element name="shared">
+        <complexType/>
+    </element>
+
+    <element name="depth" type="xsd:string"/>
+
+    <element name="owner">
+        <complexType mixed="true">
+            <sequence>
+                <any namespace="http://www.w3.org/namespace/"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="timeout" type="xsd:string"/>
+
+    <element name="locktoken">
+        <complexType>
+            <sequence>
+                <element ref="dav:href" maxOccurs="unbounded"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="href" type="xsd:string"/>
+
+    <element name="link">
+        <complexType>
+            <sequence>
+                <element ref="dav:src" maxOccurs="unbounded"/>
+                <element ref="dav:dst" maxOccurs="unbounded"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="dst" type="xsd:string"/>
+
+    <element name="src" type="xsd:string"/>
+
+    <element name="multistatus">
+        <complexType>
+            <sequence>
+                <element ref="dav:response" maxOccurs="unbounded"/>
+                <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:sync-token" minOccurs="0" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="response">
+        <complexType>
+            <sequence>
+                <element ref="dav:href" minOccurs="1" maxOccurs="unbounded"/>
+                <choice>
+                    <sequence>
+                        <element ref="dav:status"/>
+                    </sequence>
+                    <element ref="dav:propstat" maxOccurs="unbounded"/>
+                </choice>
+                <element ref="dav:error" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:location" minOccurs="0" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="status" type="xsd:string"/>
+
+    <element name="error">
+        <complexType>
+            <sequence>
+                <any namespace="##any"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="propstat">
+        <complexType>
+            <sequence>
+                <element ref="dav:prop"/>
+                <element ref="dav:status"/>
+                <element ref="dav:error" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:responsedescription" minOccurs="0" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="responsedescription" type="xsd:string"/>
+
+    <element name="location">
+        <complexType>
+            <sequence>
+                <element ref="dav:href" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="prop">
+        <complexType>
+            <all>
+                <element ref="dav:creationdate" minOccurs="0"/>
+                <element ref="dav:displayname" minOccurs="0"/>
+                <element ref="dav:getcontentlanguage" minOccurs="0"/>
+                <element ref="dav:getcontentlength" minOccurs="0"/>
+                <element ref="dav:getcontenttype" minOccurs="0"/>
+                <element ref="dav:getetag" minOccurs="0"/>
+                <element ref="dav:getlastmodified" minOccurs="0"/>
+                <element ref="dav:lockdiscovery" minOccurs="0"/>
+                <element ref="dav:resourcetype" minOccurs="0"/>
+                <element ref="dav:supportedlock" minOccurs="0"/>
+                <element ref="dav:supported-report-set" minOccurs="0"/>
+                <element ref="dav:quota-available-bytes" minOccurs="0"/>
+                <element ref="dav:quota-used-bytes" minOccurs="0"/>
+                <!-- Microsoft has some own elements in DAV namespace - don't use it for now -->
+                <!--
+                <element ref="dav:contentclass" minOccurs="0"/>
+                <element ref="dav:defaultdocument" minOccurs="0"/>
+                <element ref="dav:href" minOccurs="0"/>
+                <element ref="dav:iscollection" minOccurs="0"/>
+                <element ref="dav:ishidden" minOccurs="0"/>
+                <element ref="dav:isreadonly" minOccurs="0"/>
+                <element ref="dav:isroot" minOccurs="0"/>
+                <element ref="dav:isstructureddocument" minOccurs="0"/>
+                <element ref="dav:lastaccessed" minOccurs="0"/>
+                <element ref="dav:name" minOccurs="0"/>
+                <element ref="dav:parentname" minOccurs="0"/>
+                -->
+                <any processContents="skip" namespace="##other" maxOccurs="unbounded" />
+            </all>
+        </complexType>
+    </element>
+
+    <element name="propertybehavior">
+        <complexType>
+            <choice>
+                <element ref="dav:omit"/>
+                <element ref="dav:keepalive"/>
+            </choice>
+        </complexType>
+    </element>
+
+    <element name="omit">
+        <complexType/>
+    </element>
+
+    <element name="keepalive">
+        <complexType mixed="true">
+            <sequence>
+                <element ref="dav:href" maxOccurs="unbounded"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="propertyupdate">
+        <complexType>
+            <choice maxOccurs="unbounded">
+                <element ref="dav:remove"/>
+                <element ref="dav:set"/>
+            </choice>
+        </complexType>
+    </element>
+
+    <element name="remove">
+        <complexType>
+            <sequence>
+                <element ref="dav:prop"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="set">
+        <complexType>
+            <sequence>
+                <element ref="dav:prop"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="propfind">
+        <complexType>
+            <choice>
+                <element ref="dav:allprop"/>
+                <element ref="dav:propname"/>
+                <element ref="dav:prop"/>
+            </choice>
+        </complexType>
+    </element>
+
+    <element name="allprop">
+        <complexType/>
+    </element>
+
+    <element name="propname">
+        <complexType/>
+    </element>
+
+    <element name="collection">
+        <complexType/>
+    </element>
+
+    <element name="creationdate">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="displayname">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="getcontentlanguage">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="getcontentlength">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="getcontenttype">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="getetag">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="getlastmodified">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="lockdiscovery">
+        <complexType>
+            <sequence minOccurs="0" maxOccurs="unbounded">
+                <element ref="dav:activelock"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="resourcetype">
+        <complexType>
+            <sequence>
+                <element ref="dav:collection" minOccurs="0"/>
+                <any processContents="skip" namespace="##other" minOccurs="0" maxOccurs="unbounded" />
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="supportedlock">
+        <complexType>
+            <sequence minOccurs="0" maxOccurs="unbounded">
+                <element ref="dav:lockentry"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="source">
+        <complexType>
+            <sequence minOccurs="0" maxOccurs="unbounded">
+                <element ref="dav:link"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="quota-available-bytes">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="quota-used-bytes">
+        <complexType mixed="true">
+            <sequence/>
+        </complexType>
+    </element>
+
+    <element name="searchrequest">
+        <complexType>
+            <sequence>
+                <any processContents="skip" namespace="##other" minOccurs="1" maxOccurs="1" />
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="supported-report-set">
+        <complexType>
+            <sequence>
+                <element maxOccurs="unbounded" ref="dav:supported-report"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="supported-report">
+        <complexType>
+            <sequence>
+                <element ref="dav:report"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="report">
+        <complexType>
+            <sequence>
+                <any processContents="skip" namespace="##other" minOccurs="1" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="sync-collection">
+        <complexType>
+            <sequence>
+                <element ref="dav:sync-token" minOccurs="1" maxOccurs="1"/>
+                <element ref="dav:sync-level" minOccurs="1" maxOccurs="1"/>
+                <element ref="dav:limit" minOccurs="0" maxOccurs="1"/>
+                <element ref="dav:prop" minOccurs="1" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="sync-token" type="anyURI"/>
+
+    <element name="sync-level" type="string">
+        <simpleType>
+            <restriction base="string">
+                <enumeration value="1"/>
+                <enumeration value="infinite"/>
+            </restriction>
+        </simpleType>
+    </element>
+
+    <element name="limit">
+        <complexType>
+            <sequence>
+                <element ref="dav:nresults" minOccurs="1" maxOccurs="1"/>
+            </sequence>
+        </complexType>
+    </element>
+
+    <element name="nresults" type="integer"/>
+
+    <!-- Microsoft has some own elements in DAV namespace - don't use it for now -->
+    <!--
+    <element name="contentclass" type="xsd:string"/>
+    <element name="defaultdocument" type="xsd:string"/>
+    <element name="iscollection" type="xsd:string"/>
+    <element name="ishidden" type="xsd:string"/>
+    <element name="isreadonly" type="xsd:string"/>
+    <element name="isroot" type="xsd:string"/>
+    <element name="isstructureddocument" type="xsd:string"/>
+    <element name="lastaccessed" type="xsd:string"/>
+    <element name="name" type="xsd:string"/>
+    <element name="parentname" type="xsd:string"/>
+    -->
+</schema>
+
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xlink.xsd
new file mode 100644 (file)
index 0000000..199a469
--- /dev/null
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/1999/xlink" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <xs:annotation>
+  <xs:documentation>This schema document provides attribute declarations and
+attribute group, complex type and simple type definitions which can be used in
+the construction of user schemas to define the structure of particular linking
+constructs, e.g.
+<![CDATA[
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns:xl="http://www.w3.org/1999/xlink">
+
+ <xs:import namespace="http://www.w3.org/1999/xlink"
+            location="http://www.w3.org/1999/xlink.xsd">
+
+ <xs:element name="mySimple">
+  <xs:complexType>
+   ...
+   <xs:attributeGroup ref="xl:simpleAttrs"/>
+   ...
+  </xs:complexType>
+ </xs:element>
+ ...
+</xs:schema>]]></xs:documentation>
+ </xs:annotation>
+
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+
+ <xs:attribute name="type" type="xlink:typeType"/>
+
+ <xs:simpleType name="typeType">
+  <xs:restriction base="xs:token">
+   <xs:enumeration value="simple"/>
+   <xs:enumeration value="extended"/>
+   <xs:enumeration value="title"/>
+   <xs:enumeration value="resource"/>
+   <xs:enumeration value="locator"/>
+   <xs:enumeration value="arc"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="href" type="xlink:hrefType"/>
+
+ <xs:simpleType name="hrefType">
+  <xs:restriction base="xs:anyURI"/>
+ </xs:simpleType>
+
+ <xs:attribute name="role" type="xlink:roleType"/>
+
+ <xs:simpleType name="roleType">
+  <xs:restriction base="xs:anyURI">
+   <xs:minLength value="1"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="arcrole" type="xlink:arcroleType"/>
+
+ <xs:simpleType name="arcroleType">
+  <xs:restriction base="xs:anyURI">
+   <xs:minLength value="1"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="title" type="xlink:titleAttrType"/>
+
+ <xs:simpleType name="titleAttrType">
+  <xs:restriction base="xs:string"/>
+ </xs:simpleType>
+
+ <xs:attribute name="show" type="xlink:showType"/>
+
+ <xs:simpleType name="showType">
+  <xs:restriction base="xs:token">
+   <xs:enumeration value="new"/>
+   <xs:enumeration value="replace"/>
+   <xs:enumeration value="embed"/>
+   <xs:enumeration value="other"/>
+   <xs:enumeration value="none"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="actuate" type="xlink:actuateType"/>
+
+ <xs:simpleType name="actuateType">
+  <xs:restriction base="xs:token">
+   <xs:enumeration value="onLoad"/>
+   <xs:enumeration value="onRequest"/>
+   <xs:enumeration value="other"/>
+   <xs:enumeration value="none"/>
+  </xs:restriction>
+ </xs:simpleType>
+
+ <xs:attribute name="label" type="xlink:labelType"/>
+
+ <xs:simpleType name="labelType">
+  <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attribute name="from" type="xlink:fromType"/>
+
+ <xs:simpleType name="fromType">
+  <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attribute name="to" type="xlink:toType"/>
+
+ <xs:simpleType name="toType">
+  <xs:restriction base="xs:NCName"/>
+ </xs:simpleType>
+
+ <xs:attributeGroup name="simpleAttrs">
+  <xs:attribute ref="xlink:type" fixed="simple"/>
+  <xs:attribute ref="xlink:href"/>
+  <xs:attribute ref="xlink:role"/>
+  <xs:attribute ref="xlink:arcrole"/>
+  <xs:attribute ref="xlink:title"/>
+  <xs:attribute ref="xlink:show"/>
+  <xs:attribute ref="xlink:actuate"/>
+ </xs:attributeGroup>
+
+ <xs:group name="simpleModel">
+  <xs:sequence>
+   <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="simple">
+  <xs:annotation>
+   <xs:documentation>
+    Intended for use as the type of user-declared elements to make them
+    simple links.
+   </xs:documentation>
+  </xs:annotation>
+  <xs:group ref="xlink:simpleModel"/>
+  <xs:attributeGroup ref="xlink:simpleAttrs"/>
+ </xs:complexType>
+
+ <xs:attributeGroup name="extendedAttrs">
+  <xs:attribute ref="xlink:type" fixed="extended" use="required"/>
+  <xs:attribute ref="xlink:role"/>
+  <xs:attribute ref="xlink:title"/>
+ </xs:attributeGroup>
+
+ <xs:group name="extendedModel">
+   <xs:choice>
+    <xs:element ref="xlink:title"/>
+    <xs:element ref="xlink:resource"/>
+    <xs:element ref="xlink:locator"/>
+    <xs:element ref="xlink:arc"/>
+  </xs:choice>
+ </xs:group>
+
+ <xs:complexType name="extended">
+  <xs:annotation>
+   <xs:documentation>
+    Intended for use as the type of user-declared elements to make them
+    extended links.
+    Note that the elements referenced in the content model are all abstract.
+    The intention is that by simply declaring elements with these as their
+    substitutionGroup, all the right things will happen.
+   </xs:documentation>
+  </xs:annotation>
+  <xs:group ref="xlink:extendedModel" minOccurs="0" maxOccurs="unbounded"/>
+  <xs:attributeGroup ref="xlink:extendedAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="title" type="xlink:titleEltType" abstract="true"/>
+
+ <xs:attributeGroup name="titleAttrs">
+  <xs:attribute ref="xlink:type" fixed="title" use="required"/>
+  <xs:attribute ref="xml:lang">
+   <xs:annotation>
+    <xs:documentation>
+     xml:lang is not required, but provides much of the
+     motivation for title elements in addition to attributes, and so
+     is provided here for convenience.
+    </xs:documentation>
+   </xs:annotation>
+  </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="titleModel">
+  <xs:sequence>
+   <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="titleEltType">
+  <xs:group ref="xlink:titleModel"/>
+  <xs:attributeGroup ref="xlink:titleAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="resource" type="xlink:resourceType" abstract="true"/>
+
+ <xs:attributeGroup name="resourceAttrs">
+  <xs:attribute ref="xlink:type" fixed="resource" use="required"/>
+  <xs:attribute ref="xlink:role"/>
+  <xs:attribute ref="xlink:title"/>
+  <xs:attribute ref="xlink:label"/>
+ </xs:attributeGroup>
+
+ <xs:group name="resourceModel">
+  <xs:sequence>
+   <xs:any processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:complexType mixed="true" name="resourceType">
+  <xs:group ref="xlink:resourceModel"/>
+  <xs:attributeGroup ref="xlink:resourceAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="locator" type="xlink:locatorType" abstract="true"/>
+
+ <xs:attributeGroup name="locatorAttrs">
+  <xs:attribute ref="xlink:type" fixed="locator" use="required"/>
+  <xs:attribute ref="xlink:href" use="required"/>
+  <xs:attribute ref="xlink:role"/>
+  <xs:attribute ref="xlink:title"/>
+  <xs:attribute ref="xlink:label">
+   <xs:annotation>
+    <xs:documentation>
+     label is not required, but locators have no particular
+     XLink function if they are not labeled.
+    </xs:documentation>
+   </xs:annotation>
+  </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="locatorModel">
+  <xs:sequence>
+   <xs:element ref="xlink:title" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:complexType name="locatorType">
+  <xs:group ref="xlink:locatorModel"/>
+  <xs:attributeGroup ref="xlink:locatorAttrs"/>
+ </xs:complexType>
+
+ <xs:element name="arc" type="xlink:arcType" abstract="true"/>
+
+ <xs:attributeGroup name="arcAttrs">
+  <xs:attribute ref="xlink:type" fixed="arc" use="required"/>
+  <xs:attribute ref="xlink:arcrole"/>
+  <xs:attribute ref="xlink:title"/>
+  <xs:attribute ref="xlink:show"/>
+  <xs:attribute ref="xlink:actuate"/>
+  <xs:attribute ref="xlink:from"/>
+  <xs:attribute ref="xlink:to">
+   <xs:annotation>
+    <xs:documentation>
+     from and to have default behavior when values are missing
+    </xs:documentation>
+   </xs:annotation>
+  </xs:attribute>
+ </xs:attributeGroup>
+
+ <xs:group name="arcModel">
+  <xs:sequence>
+   <xs:element ref="xlink:title" minOccurs="0" maxOccurs="unbounded"/>
+  </xs:sequence>
+ </xs:group>
+
+ <xs:complexType name="arcType">
+  <xs:group ref="xlink:arcModel"/>
+  <xs:attributeGroup ref="xlink:arcAttrs"/>
+ </xs:complexType>
+
+ <!-- Hack required for GML support -->
+ <xs:attributeGroup name="simpleLink">
+  <xs:attribute name="type" type="xs:string" use="optional" fixed="simple" form="qualified"/>
+  <xs:attribute ref="xlink:href" use="optional"/>
+  <xs:attribute ref="xlink:role" use="optional"/>
+  <xs:attribute ref="xlink:arcrole" use="optional"/>
+  <xs:attribute ref="xlink:title" use="optional"/>
+  <xs:attribute ref="xlink:show" use="optional"/>
+  <xs:attribute ref="xlink:actuate" use="optional"/>
+ </xs:attributeGroup>
+</xs:schema>
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/xml.xsd
new file mode 100644 (file)
index 0000000..aea7d0d
--- /dev/null
@@ -0,0 +1,287 @@
+<?xml version='1.0'?>
+<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
+<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" 
+  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
+  xmlns   ="http://www.w3.org/1999/xhtml"
+  xml:lang="en">
+
+ <xs:annotation>
+  <xs:documentation>
+   <div>
+    <h1>About the XML namespace</h1>
+
+    <div class="bodytext">
+     <p>
+      This schema document describes the XML namespace, in a form
+      suitable for import by other schema documents.
+     </p>
+     <p>
+      See <a href="http://www.w3.org/XML/1998/namespace.html">
+      http://www.w3.org/XML/1998/namespace.html</a> and
+      <a href="http://www.w3.org/TR/REC-xml">
+      http://www.w3.org/TR/REC-xml</a> for information 
+      about this namespace.
+     </p>
+     <p>
+      Note that local names in this namespace are intended to be
+      defined only by the World Wide Web Consortium or its subgroups.
+      The names currently defined in this namespace are listed below.
+      They should not be used with conflicting semantics by any Working
+      Group, specification, or document instance.
+     </p>
+     <p>   
+      See further below in this document for more information about <a
+      href="#usage">how to refer to this schema document from your own
+      XSD schema documents</a> and about <a href="#nsversioning">the
+      namespace-versioning policy governing this schema document</a>.
+     </p>
+    </div>
+   </div>
+  </xs:documentation>
+ </xs:annotation>
+
+ <xs:attribute name="lang">
+  <xs:annotation>
+   <xs:documentation>
+    <div>
+     
+      <h3>lang (as an attribute name)</h3>
+      <p>
+       denotes an attribute whose value
+       is a language code for the natural language of the content of
+       any element; its value is inherited.  This name is reserved
+       by virtue of its definition in the XML specification.</p>
+     
+    </div>
+    <div>
+     <h4>Notes</h4>
+     <p>
+      Attempting to install the relevant ISO 2- and 3-letter
+      codes as the enumerated possible values is probably never
+      going to be a realistic possibility.  
+     </p>
+     <p>
+      See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
+       http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
+      and the IANA language subtag registry at
+      <a href="http://www.iana.org/assignments/language-subtag-registry">
+       http://www.iana.org/assignments/language-subtag-registry</a>
+      for further information.
+     </p>
+     <p>
+      The union allows for the 'un-declaration' of xml:lang with
+      the empty string.
+     </p>
+    </div>
+   </xs:documentation>
+  </xs:annotation>
+  <xs:simpleType>
+   <xs:union memberTypes="xs:language">
+    <xs:simpleType>    
+     <xs:restriction base="xs:string">
+      <xs:enumeration value=""/>
+     </xs:restriction>
+    </xs:simpleType>
+   </xs:union>
+  </xs:simpleType>
+ </xs:attribute>
+
+ <xs:attribute name="space">
+  <xs:annotation>
+   <xs:documentation>
+    <div>
+     
+      <h3>space (as an attribute name)</h3>
+      <p>
+       denotes an attribute whose
+       value is a keyword indicating what whitespace processing
+       discipline is intended for the content of the element; its
+       value is inherited.  This name is reserved by virtue of its
+       definition in the XML specification.</p>
+     
+    </div>
+   </xs:documentation>
+  </xs:annotation>
+  <xs:simpleType>
+   <xs:restriction base="xs:NCName">
+    <xs:enumeration value="default"/>
+    <xs:enumeration value="preserve"/>
+   </xs:restriction>
+  </xs:simpleType>
+ </xs:attribute>
+ <xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
+   <xs:documentation>
+    <div>
+     
+      <h3>base (as an attribute name)</h3>
+      <p>
+       denotes an attribute whose value
+       provides a URI to be used as the base for interpreting any
+       relative URIs in the scope of the element on which it
+       appears; its value is inherited.  This name is reserved
+       by virtue of its definition in the XML Base specification.</p>
+     
+     <p>
+      See <a
+      href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
+      for information about this attribute.
+     </p>
+    </div>
+   </xs:documentation>
+  </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="id" type="xs:ID">
+  <xs:annotation>
+   <xs:documentation>
+    <div>
+     
+      <h3>id (as an attribute name)</h3> 
+      <p>
+       denotes an attribute whose value
+       should be interpreted as if declared to be of type ID.
+       This name is reserved by virtue of its definition in the
+       xml:id specification.</p>
+     
+     <p>
+      See <a
+      href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
+      for information about this attribute.
+     </p>
+    </div>
+   </xs:documentation>
+  </xs:annotation>
+ </xs:attribute>
+
+ <xs:attributeGroup name="specialAttrs">
+  <xs:attribute ref="xml:base"/>
+  <xs:attribute ref="xml:lang"/>
+  <xs:attribute ref="xml:space"/>
+  <xs:attribute ref="xml:id"/>
+ </xs:attributeGroup>
+
+ <xs:annotation>
+  <xs:documentation>
+   <div>
+   
+    <h3>Father (in any context at all)</h3> 
+
+    <div class="bodytext">
+     <p>
+      denotes Jon Bosak, the chair of 
+      the original XML Working Group.  This name is reserved by 
+      the following decision of the W3C XML Plenary and 
+      XML Coordination groups:
+     </p>
+     <blockquote>
+       <p>
+       In appreciation for his vision, leadership and
+       dedication the W3C XML Plenary on this 10th day of
+       February, 2000, reserves for Jon Bosak in perpetuity
+       the XML name "xml:Father".
+       </p>
+     </blockquote>
+    </div>
+   </div>
+  </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+  <xs:documentation>
+   <div xml:id="usage" id="usage">
+    <h2><a name="usage">About this schema document</a></h2>
+
+    <div class="bodytext">
+     <p>
+      This schema defines attributes and an attribute group suitable
+      for use by schemas wishing to allow <code>xml:base</code>,
+      <code>xml:lang</code>, <code>xml:space</code> or
+      <code>xml:id</code> attributes on elements they define.
+     </p>
+     <p>
+      To enable this, such a schema must import this schema for
+      the XML namespace, e.g. as follows:
+     </p>
+     <pre>
+          &lt;schema . . .>
+           . . .
+           &lt;import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     </pre>
+     <p>
+      or
+     </p>
+     <pre>
+           &lt;import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     </pre>
+     <p>
+      Subsequently, qualified reference to any of the attributes or the
+      group defined below will have the desired effect, e.g.
+     </p>
+     <pre>
+          &lt;type . . .>
+           . . .
+           &lt;attributeGroup ref="xml:specialAttrs"/>
+     </pre>
+     <p>
+      will define a type which will schema-validate an instance element
+      with any of those attributes.
+     </p>
+    </div>
+   </div>
+  </xs:documentation>
+ </xs:annotation>
+
+ <xs:annotation>
+  <xs:documentation>
+   <div id="nsversioning" xml:id="nsversioning">
+    <h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
+    <div class="bodytext">
+     <p>
+      In keeping with the XML Schema WG's standard versioning
+      policy, this schema document will persist at
+      <a href="http://www.w3.org/2009/01/xml.xsd">
+       http://www.w3.org/2009/01/xml.xsd</a>.
+     </p>
+     <p>
+      At the date of issue it can also be found at
+      <a href="http://www.w3.org/2001/xml.xsd">
+       http://www.w3.org/2001/xml.xsd</a>.
+     </p>
+     <p>
+      The schema document at that URI may however change in the future,
+      in order to remain compatible with the latest version of XML
+      Schema itself, or with the XML namespace itself.  In other words,
+      if the XML Schema or XML namespaces change, the version of this
+      document at <a href="http://www.w3.org/2001/xml.xsd">
+       http://www.w3.org/2001/xml.xsd 
+      </a> 
+      will change accordingly; the version at 
+      <a href="http://www.w3.org/2009/01/xml.xsd">
+       http://www.w3.org/2009/01/xml.xsd 
+      </a> 
+      will not change.
+     </p>
+     <p>
+      Previous dated (and unchanging) versions of this schema 
+      document are at:
+     </p>
+     <ul>
+      <li><a href="http://www.w3.org/2009/01/xml.xsd">
+       http://www.w3.org/2009/01/xml.xsd</a></li>
+      <li><a href="http://www.w3.org/2007/08/xml.xsd">
+       http://www.w3.org/2007/08/xml.xsd</a></li>
+      <li><a href="http://www.w3.org/2004/10/xml.xsd">
+       http://www.w3.org/2004/10/xml.xsd</a></li>
+      <li><a href="http://www.w3.org/2001/03/xml.xsd">
+       http://www.w3.org/2001/03/xml.xsd</a></li>
+     </ul>
+    </div>
+   </div>
+  </xs:documentation>
+ </xs:annotation>
+
+</xs:schema>
+
index 626f582e515cc40d7e3d9d1f897fbd8812584063..a4c14186ac17d299b0673e1b67ee0857db166c13 100644 (file)
@@ -1,30 +1,55 @@
 package org.argeo.cms.acr.xml;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
 
 import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.namespace.QName;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
-import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
 import org.w3c.dom.Attr;
+import org.w3c.dom.DOMException;
 import org.w3c.dom.Document;
+import org.w3c.dom.DocumentFragment;
 import org.w3c.dom.Element;
 import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 
+/** Content persisted as a DOM element. */
 public class DomContent extends AbstractContent implements ProvidedContent {
 
-       private final ProvidedSession session;
        private final DomContentProvider provider;
        private final Element element;
 
@@ -32,7 +57,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
        private Boolean hasText = null;
 
        public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
-               this.session = session;
+               super(session);
                this.provider = contentProvider;
                this.element = element;
        }
@@ -43,9 +68,24 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public QName getName() {
+               if (isLocalRoot()) {// root
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null) {
+                               if (ContentUtils.ROOT_SLASH.equals(mountPath)) {
+                                       return CrName.root.qName();
+                               }
+                               Content mountPoint = getSession().getMountPoint(mountPath);
+                               QName mountPointName = mountPoint.getName();
+                               return mountPointName;
+                       }
+               }
                return toQName(this.element);
        }
 
+       protected boolean isLocalRoot() {
+               return element.getParentNode() == null || element.getParentNode() instanceof Document;
+       }
+
        protected QName toQName(Node node) {
                String prefix = node.getPrefix();
                if (prefix == null) {
@@ -55,27 +95,27 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        if (namespaceURI == null) {
                                return toQName(node, node.getLocalName());
                        } else {
-                               String contextPrefix = session.getPrefix(namespaceURI);
+                               String contextPrefix = provider.getPrefix(namespaceURI);
                                if (contextPrefix == null)
                                        throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
-                               return toQName(node, namespaceURI, node.getLocalName(), session);
+                               return toQName(node, namespaceURI, node.getLocalName(), provider);
                        }
                } else {
                        String namespaceURI = node.getNamespaceURI();
                        if (namespaceURI == null)
                                namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
                        if (namespaceURI == null) {
-                               namespaceURI = session.getNamespaceURI(prefix);
+                               namespaceURI = provider.getNamespaceURI(prefix);
                                if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
                                        throw new IllegalStateException("Prefix " + prefix + " is unbound");
                                // TODO bind the prefix in the document?
                        }
-                       return toQName(node, namespaceURI, node.getLocalName(), session);
+                       return toQName(node, namespaceURI, node.getLocalName(), provider);
                }
        }
 
        protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
-               return new ContentName(namespaceURI, localName, session);
+               return new ContentName(namespaceURI, localName, namespaceContext);
        }
 
        protected QName toQName(Node source, String localName) {
@@ -93,11 +133,14 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                for (int i = 0; i < attributes.getLength(); i++) {
                        Attr attr = (Attr) attributes.item(i);
                        QName key = toQName(attr);
+                       if (key.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+                               continue;// skip prefix mapping
                        result.add(key);
                }
                return result;
        }
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<A> get(QName key, Class<A> clss) {
                String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
@@ -109,7 +152,7 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                        else
                                return Optional.empty();
                } else
-                       return null;
+                       return Optional.empty();
        }
 
        @Override
@@ -117,13 +160,30 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                Object previous = get(key);
                String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
                                : key.getNamespaceURI();
+               String prefixToUse = registerPrefixIfNeeded(key);
                element.setAttributeNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+                               namespaceUriOrNull == null ? key.getLocalPart() : prefixToUse + ":" + key.getLocalPart(),
                                value.toString());
                return previous;
        }
-       
-       
+
+       protected String registerPrefixIfNeeded(QName name) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+                               : name.getNamespaceURI();
+               String prefixToUse;
+               if (namespaceUriOrNull != null) {
+                       String registeredPrefix = provider.getPrefix(namespaceUriOrNull);
+                       if (registeredPrefix != null) {
+                               prefixToUse = registeredPrefix;
+                       } else {
+                               provider.registerPrefix(name.getPrefix(), namespaceUriOrNull);
+                               prefixToUse = name.getPrefix();
+                       }
+               } else {
+                       prefixToUse = null;
+               }
+               return prefixToUse;
+       }
 
        @Override
        public boolean hasText() {
@@ -169,17 +229,27 @@ public class DomContent extends AbstractContent implements ProvidedContent {
        @Override
        public Iterator<Content> iterator() {
                NodeList nodeList = element.getChildNodes();
-               return new ElementIterator(session, provider, nodeList);
+               return new ElementIterator(this, getSession(), provider, nodeList);
        }
 
        @Override
        public Content getParent() {
-               Node parent = element.getParentNode();
-               if (parent == null)
-                       return null;
-               if (!(parent instanceof Element))
+               Node parentNode = element.getParentNode();
+               if (isLocalRoot()) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath == null)
+                               return null;
+                       if (ContentUtils.ROOT_SLASH.equals(mountPath)) {
+                               return null;
+                       }
+                       String[] parent = ContentUtils.getParentPath(mountPath);
+                       if (ContentUtils.EMPTY.equals(parent[0]))
+                               return null;
+                       return getSession().get(parent[0]);
+               }
+               if (!(parentNode instanceof Element))
                        throw new IllegalStateException("Parent is not an element");
-               return new DomContent(this, (Element) parent);
+               return new DomContent(this, (Element) parentNode);
        }
 
        @Override
@@ -188,8 +258,9 @@ public class DomContent extends AbstractContent implements ProvidedContent {
                Document document = this.element.getOwnerDocument();
                String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
                                : name.getNamespaceURI();
+               String prefixToUse = registerPrefixIfNeeded(name);
                Element child = document.createElementNS(namespaceUriOrNull,
-                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+                               namespaceUriOrNull == null ? name.getLocalPart() : prefixToUse + ":" + name.getLocalPart());
                element.appendChild(child);
                return new DomContent(this, child);
        }
@@ -210,10 +281,149 @@ public class DomContent extends AbstractContent implements ProvidedContent {
 
        }
 
-       public ProvidedSession getSession() {
-               return session;
+       @SuppressWarnings("unchecked")
+       @Override
+       public <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+               if (CharBuffer.class.isAssignableFrom(clss)) {
+                       String textContent = element.getTextContent();
+                       CharBuffer buf = CharBuffer.wrap(textContent);
+                       return (A) buf;
+               } else if (Source.class.isAssignableFrom(clss)) {
+                       DOMSource source = new DOMSource(element);
+                       return (A) source;
+               }
+               return super.adapt(clss);
+       }
+
+       @SuppressWarnings("unchecked")
+       public <A> CompletableFuture<A> write(Class<A> clss) {
+               if (String.class.isAssignableFrom(clss)) {
+                       CompletableFuture<String> res = new CompletableFuture<>();
+                       res.thenAccept((s) -> {
+                               getSession().notifyModification(this);
+                               element.setTextContent(s);
+                       });
+                       return (CompletableFuture<A>) res;
+               } else if (Source.class.isAssignableFrom(clss)) {
+                       CompletableFuture<Source> res = new CompletableFuture<>();
+                       res.thenAccept((source) -> {
+                               try {
+                                       Transformer transformer = provider.getTransformerFactory().newTransformer();
+                                       DocumentFragment documentFragment = element.getOwnerDocument().createDocumentFragment();
+                                       DOMResult result = new DOMResult(documentFragment);
+                                       transformer.transform(source, result);
+                                       // Node parentNode = element.getParentNode();
+                                       Element resultElement = (Element) documentFragment.getFirstChild();
+                                       QName resultName = toQName(resultElement);
+                                       if (!resultName.equals(getName()))
+                                               throw new IllegalArgumentException(resultName + "+ is not compatible with " + getName());
+
+                                       // attributes
+                                       NamedNodeMap attrs = resultElement.getAttributes();
+                                       for (int i = 0; i < attrs.getLength(); i++) {
+                                               Attr attr2 = (Attr) element.getOwnerDocument().importNode(attrs.item(i), true);
+                                               element.getAttributes().setNamedItem(attr2);
+                                       }
+
+                                       // Move all the children
+                                       while (element.hasChildNodes()) {
+                                               element.removeChild(element.getFirstChild());
+                                       }
+                                       while (resultElement.hasChildNodes()) {
+                                               element.appendChild(resultElement.getFirstChild());
+                                       }
+//                                     parentNode.replaceChild(resultNode, element);
+//                                     element = (Element)resultNode;
+
+                               } catch (DOMException | TransformerException e) {
+                                       throw new RuntimeException("Cannot write to element", e);
+                               }
+                       });
+                       return (CompletableFuture<A>) res;
+               }
+               return super.write(clss);
        }
 
+       @SuppressWarnings("unchecked")
+       @Override
+       public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
+               if (InputStream.class.isAssignableFrom(clss)) {
+                       PipedOutputStream out = new PipedOutputStream();
+                       ForkJoinPool.commonPool().execute(() -> {
+                               try {
+                                       Source source = new DOMSource(element);
+                                       Result result = new StreamResult(out);
+                                       provider.getTransformerFactory().newTransformer().transform(source, result);
+                                       out.flush();
+                                       out.close();
+                               } catch (TransformerException | IOException e) {
+                                       throw new RuntimeException("Cannot read " + getPath(), e);
+                               }
+                       });
+                       return (C) new PipedInputStream(out);
+               }
+               return super.open(clss);
+       }
+
+       @Override
+       public int getSiblingIndex() {
+               Node curr = element.getPreviousSibling();
+               int count = 1;
+               while (curr != null) {
+                       if (curr instanceof Element) {
+                               if (Objects.equals(curr.getNamespaceURI(), element.getNamespaceURI())
+                                               && Objects.equals(curr.getLocalName(), element.getLocalName())) {
+                                       count++;
+                               }
+                       }
+                       curr = curr.getPreviousSibling();
+               }
+               return count;
+       }
+
+       /*
+        * TYPING
+        */
+       @Override
+       public List<QName> getContentClasses() {
+               List<QName> res = new ArrayList<>();
+               if (isLocalRoot()) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null) {
+                               Content mountPoint = getSession().getMountPoint(mountPath);
+                               res.addAll(mountPoint.getContentClasses());
+                       }
+               } else {
+                       res.add(getName());
+               }
+               return res;
+       }
+
+       @Override
+       public void addContentClasses(QName... contentClass) {
+               if (isLocalRoot()) {
+                       String mountPath = provider.getMountPath();
+                       if (mountPath != null) {
+                               Content mountPoint = getSession().getMountPoint(mountPath);
+                               mountPoint.addContentClasses(contentClass);
+                       }
+               } else {
+                       super.addContentClasses(contentClass);
+               }
+       }
+
+       /*
+        * MOUNT MANAGEMENT
+        */
+       @Override
+       public ProvidedContent getMountPoint(String relativePath) {
+               // FIXME use qualified names
+               Element childElement = (Element) element.getElementsByTagName(relativePath).item(0);
+               // TODO check that it is a mount
+               return new DomContent(this, childElement);
+       }
+
+       @Override
        public DomContentProvider getProvider() {
                return provider;
        }
index c5fde8d7c521482c26b5a81932d223744026e9c4..66ff878d5c6a9a644445a97bafa64869e19e2cfd 100644 (file)
@@ -1,11 +1,14 @@
 package org.argeo.cms.acr.xml;
 
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
 import javax.xml.namespace.NamespaceContext;
+import javax.xml.transform.TransformerFactory;
 import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
@@ -13,29 +16,41 @@ import javax.xml.xpath.XPathFactory;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.CmsContentRepository;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
 public class DomContentProvider implements ContentProvider, NamespaceContext {
-       private Document document;
+       private final Document document;
 
        // XPath
        // TODO centralise in some executor?
        private final ThreadLocal<XPath> xPath;
 
-       public DomContentProvider(Document document) {
+       private TransformerFactory transformerFactory;
+
+       private String mountPath;
+
+       public DomContentProvider(String mountPath, Document document) {
+               this.mountPath = mountPath;
                this.document = document;
                this.document.normalizeDocument();
+
+               transformerFactory = TransformerFactory.newInstance();
+
                XPathFactory xPathFactory = XPathFactory.newInstance();
                xPath = new ThreadLocal<>() {
 
                        @Override
                        protected XPath initialValue() {
                                // TODO set the document as namespace context?
-                               XPath res= xPathFactory.newXPath();
+                               XPath res = xPathFactory.newXPath();
                                res.setNamespaceContext(DomContentProvider.this);
                                return res;
                        }
@@ -53,27 +68,62 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
 //     }
 
        @Override
-       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+       public ProvidedContent get(ProvidedSession session, String relativePath) {
                if ("".equals(relativePath))
                        return new DomContent(session, this, document.getDocumentElement());
+
+               NodeList nodes = findContent(relativePath);
+               if (nodes.getLength() > 1)
+                       throw new IllegalArgumentException("Multiple content found for " + relativePath + " under " + mountPath);
+               if (nodes.getLength() == 0)
+                       throw new ContentNotFoundException(session, mountPath + "/" + relativePath,
+                                       "Path " + relativePath + " under " + mountPath + " was not found");
+               Element element = (Element) nodes.item(0);
+               return new DomContent(session, this, element);
+       }
+
+       protected NodeList findContent(String relativePath) {
                if (relativePath.startsWith("/"))
                        throw new IllegalArgumentException("Relative path cannot start with /");
-
                String xPathExpression = '/' + relativePath;
                if ("/".equals(mountPath))
-                       xPathExpression = "/cr:root" + xPathExpression;
+                       xPathExpression = "/" + CrName.root.qName() + xPathExpression;
                try {
                        NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
-                       if (nodes.getLength() > 1)
-                               throw new IllegalArgumentException(
-                                               "Multiple content found for " + relativePath + " under " + mountPath);
-                       if (nodes.getLength() == 0)
-                               throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
-                       Element element = (Element) nodes.item(0);
-                       return new DomContent(session, this, element);
+                       return nodes;
                } catch (XPathExpressionException e) {
                        throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
                }
+
+       }
+
+       @Override
+       public boolean exists(ProvidedSession session, String relativePath) {
+               if ("".equals(relativePath))
+                       return true;
+               NodeList nodes = findContent(relativePath);
+               return nodes.getLength() != 0;
+       }
+
+       public void persist(ProvidedSession session) {
+               if (mountPath != null) {
+                       Content mountPoint = session.getMountPoint(mountPath);
+                       try (OutputStream out = mountPoint.open(OutputStream.class)) {
+                               CmsContentRepository contentRepository = (CmsContentRepository) session.getRepository();
+                               contentRepository.writeDom(document, out);
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot persist " + mountPath, e);
+                       }
+               }
+       }
+
+       @Override
+       public String getMountPath() {
+               return mountPath;
+       }
+
+       public void registerPrefix(String prefix, String namespace) {
+               DomUtils.addNamespace(document.getDocumentElement(), prefix, namespace);
        }
 
        /*
@@ -81,11 +131,17 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
         */
        @Override
        public String getNamespaceURI(String prefix) {
+               String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
                return document.lookupNamespaceURI(prefix);
        }
 
        @Override
        public String getPrefix(String namespaceURI) {
+               String prefix = NamespaceUtils.getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
                return document.lookupPrefix(namespaceURI);
        }
 
@@ -96,4 +152,8 @@ public class DomContentProvider implements ContentProvider, NamespaceContext {
                return Collections.unmodifiableList(res).iterator();
        }
 
+       TransformerFactory getTransformerFactory() {
+               return transformerFactory;
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomUtils.java
new file mode 100644 (file)
index 0000000..3b5ff0d
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.cms.acr.xml;
+
+import javax.xml.XMLConstants;
+
+import org.w3c.dom.Element;
+
+public class DomUtils {
+       public static void addNamespace(Element element, String prefix, String namespace) {
+               element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix,
+                               namespace);
+       }
+
+//     public static void writeDom(TransformerFactory transformerFactory, Document document, OutputStream out)
+//                     throws IOException {
+//             try {
+//                     Transformer transformer = transformerFactory.newTransformer();
+//                     transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+//                     transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+//                     DOMSource source = new DOMSource(document);
+//                     StreamResult result = new StreamResult(out);
+//                     transformer.transform(source, result);
+//             } catch (TransformerException e) {
+//                     throw new IOException("Cannot write dom", e);
+//             }
+//     }
+
+       /** singleton */
+       private DomUtils() {
+
+       }
+}
index 3b07081e4a7f3477e92a6137709eb95ad8f90bb4..3f747622195943cb617d1391b344c7b69275eedb 100644 (file)
@@ -3,13 +3,16 @@ package org.argeo.cms.acr.xml;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import org.argeo.api.acr.ArgeoNamespace;
 import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.spi.ProvidedSession;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
 class ElementIterator implements Iterator<Content> {
+       private DomContent parent;
        private final ProvidedSession session;
        private final DomContentProvider provider;
        private final NodeList nodeList;
@@ -18,7 +21,8 @@ class ElementIterator implements Iterator<Content> {
        private final int length;
        private Element nextElement = null;
 
-       public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+       public ElementIterator(DomContent parent, ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+               this.parent = parent;
                this.session = session;
                this.provider = provider;
                this.nodeList = nodeList;
@@ -48,7 +52,15 @@ class ElementIterator implements Iterator<Content> {
        public Content next() {
                if (nextElement == null)
                        throw new NoSuchElementException();
-               DomContent result = new DomContent(session, provider, nextElement);
+               Content result;
+               String isMount = nextElement.getAttributeNS(ArgeoNamespace.CR_NAMESPACE_URI, CrName.mount.qName().getLocalPart());
+               if (isMount.equals("true")) {
+                       result = session.get(parent.getPath() + '/' + nextElement.getTagName());
+               }
+
+               else {
+                       result = new DomContent(session, provider, nextElement);
+               }
                currentIndex++;
                nextElement = findNext();
                return result;
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/XmlNormalizer.java
new file mode 100644 (file)
index 0000000..4292399
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.cms.acr.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * Consistently normalises an XML in order to ease diff (typically in a
+ * versioning system).
+ */
+public class XmlNormalizer {
+//     private final static Logger logger = System.getLogger(XmlNormalizer.class.getName());
+
+       private DocumentBuilder documentBuilder;
+       private TransformerFactory transformerFactory;
+
+       private DOMSource stripSpaceXsl;
+
+       public XmlNormalizer() {
+               this(2);
+       }
+
+       public XmlNormalizer(int indent) {
+               try {
+                       documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
+                       transformerFactory = TransformerFactory.newInstance();
+                       transformerFactory.setAttribute("indent-number", indent);
+                       try (InputStream in = XmlNormalizer.class.getResourceAsStream("stripSpaces.xsl")) {
+                               DOMResult result = new DOMResult();
+                               transformerFactory.newTransformer().transform(new StreamSource(in), result);
+                               stripSpaceXsl = new DOMSource(result.getNode());
+                       }
+               } catch (ParserConfigurationException | TransformerFactoryConfigurationError | TransformerException
+                               | IOException e) {
+                       throw new IllegalStateException("Cannot initialise document builder and transformer", e);
+               }
+       }
+
+       public void normalizeXmlFiles(DirectoryStream<Path> ds) throws IOException {
+               for (Path path : ds) {
+                       normalizeXmlFile(path);
+               }
+       }
+
+       public void normalizeXmlFile(Path path) throws IOException {
+               byte[] bytes = Files.readAllBytes(path);
+               try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+                               OutputStream out = Files.newOutputStream(path)) {
+                       normalizeAndIndent(in, out);
+//                     logger.log(Level.DEBUG, () -> "Normalized XML " + path);
+               }
+       }
+
+       public void normalizeAndIndent(Source source, Result result) {
+               try {
+                       Transformer transformer = transformerFactory.newTransformer(stripSpaceXsl);
+                       transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                       // transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+                       transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+
+                       transformer.transform(source, result);
+               } catch (TransformerException e) {
+                       throw new RuntimeException("Cannot strip space from " + source, e);
+               }
+       }
+
+       public void normalizeAndIndent(InputStream in, OutputStream out) throws IOException {
+               try {
+                       Document document = documentBuilder.parse(in);
+
+                       // clear whitespaces outside tags
+                       document.normalize();
+//                     XPath xPath = XPathFactory.newInstance().newXPath();
+//                     NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document,
+//                                     XPathConstants.NODESET);
+//
+//                     for (int i = 0; i < nodeList.getLength(); ++i) {
+//                             Node node = nodeList.item(i);
+//                             node.getParentNode().removeChild(node);
+//                     }
+
+                       normalizeAndIndent(new DOMSource(document), new StreamResult(out));
+               } catch (DOMException | IllegalArgumentException | SAXException | TransformerFactoryConfigurationError e) {
+                       throw new RuntimeException("Cannot normalise and indent XML", e);
+               }
+       }
+
+       public static void print(Source source, int indent) {
+               XmlNormalizer xmlNormalizer = new XmlNormalizer(indent);
+               xmlNormalizer.normalizeAndIndent(source, new StreamResult(System.out));
+       }
+
+       public static void print(Source source) {
+               print(source, 2);
+       }
+
+       public static void main(String[] args) throws IOException {
+               Path dir = Paths.get(args[0]);
+               XmlNormalizer xmlNormalizer = new XmlNormalizer();
+               DirectoryStream<Path> ds = Files.newDirectoryStream(dir, "*.svg");
+               xmlNormalizer.normalizeXmlFiles(ds);
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl b/org.argeo.cms/src/org/argeo/cms/acr/xml/stripSpaces.xsl
new file mode 100644 (file)
index 0000000..c1d265f
--- /dev/null
@@ -0,0 +1,12 @@
+<xsl:stylesheet version="1.0"
+       xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+       <xsl:strip-space elements="*" />
+       <xsl:output method="xml" encoding="UTF-8" />
+
+       <xsl:template match="@*|node()">
+               <xsl:copy>
+                       <xsl:apply-templates select="@*|node()" />
+               </xsl:copy>
+       </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
index de3a3027012b973b0ffbfff3b57d3e187b30035b..82873ad019db954f57059f60b8048d8c30a1d9ae 100644 (file)
@@ -9,8 +9,7 @@ import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
 import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.UserAdmin;
 
@@ -22,7 +21,7 @@ public class AnonymousLoginModule implements LoginModule {
        private Map<String, Object> sharedState = null;
 
        // private state
-       private BundleContext bc;
+//     private BundleContext bc;
 
        @SuppressWarnings("unchecked")
        @Override
@@ -30,12 +29,12 @@ public class AnonymousLoginModule implements LoginModule {
                        Map<String, ?> options) {
                this.subject = subject;
                this.sharedState = (Map<String, Object>) sharedState;
-               try {
-                       bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext();
-                       assert bc != null;
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot initialize login module", e);
-               }
+//             try {
+//                     bc = FrameworkUtil.getBundle(AnonymousLoginModule.class).getBundleContext();
+//                     assert bc != null;
+//             } catch (Exception e) {
+//                     throw new IllegalStateException("Cannot initialize login module", e);
+//             }
        }
 
        @Override
@@ -45,7 +44,7 @@ public class AnonymousLoginModule implements LoginModule {
 
        @Override
        public boolean commit() throws LoginException {
-               UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class));
+               UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
                Authorization authorization = userAdmin.getAuthorization(null);
                RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
                Locale locale = Locale.getDefault();
index 72676611e4e43f631429c52e8b31a6a0893e4529..289f8dcc65eabf2e101c0af6208bbb2ead05ff90 100644 (file)
@@ -1,30 +1,33 @@
 package org.argeo.cms.auth;
 
+import static org.argeo.api.cms.CmsConstants.ROLE_ADMIN;
+import static org.argeo.api.cms.CmsConstants.ROLE_ANONYMOUS;
+import static org.argeo.api.cms.CmsConstants.ROLE_USER;
+import static org.argeo.api.cms.CmsConstants.ROLE_USER_ADMIN;
+
 import java.security.Principal;
-import java.util.Collection;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
 import java.util.UUID;
 
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
+//import javax.naming.InvalidNameException;
+//import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
 
+import org.argeo.api.cms.AnonymousPrincipal;
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.DataAdminPrincipal;
-import org.argeo.api.cms.AnonymousPrincipal;
-import org.argeo.api.cms.CmsConstants;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.http.WebCmsSessionImpl;
-import org.argeo.cms.security.NodeSecurityUtils;
-import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpContext;
+import org.argeo.cms.internal.auth.RemoteCmsSessionImpl;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
 import org.osgi.service.useradmin.Authorization;
 
 /** Centralises security related registrations. */
@@ -32,8 +35,8 @@ class CmsAuthUtils {
        // Standard
        final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
        final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD;
-       final static String HEADER_AUTHORIZATION = "Authorization";
-       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+//     final static String HEADER_AUTHORIZATION = "Authorization";
+//     final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
 
        // Argeo specific
        final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
@@ -43,6 +46,11 @@ class CmsAuthUtils {
        final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr";
        final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port";
 
+       final static String SINGLE_USER_LOCAL_ID = "single-user";
+
+       private final static List<String> RESERVED_ROLES = Collections
+                       .unmodifiableList(Arrays.asList(new String[] { ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_USER, ROLE_USER_ADMIN }));
+
        static void addAuthorization(Subject subject, Authorization authorization) {
                assert subject != null;
                checkSubjectEmpty(subject);
@@ -54,46 +62,46 @@ class CmsAuthUtils {
                boolean singleUser = authorization instanceof SingleUserAuthorization;
 
                Set<Principal> principals = subject.getPrincipals();
-               try {
-                       String authName = authorization.getName();
-
-                       // determine user's principal
-                       final LdapName name;
-                       final Principal userPrincipal;
-                       if (authName == null) {
-                               name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
-                               userPrincipal = new AnonymousPrincipal();
-                               principals.add(userPrincipal);
-                       } else {
-                               name = new LdapName(authName);
-                               NodeSecurityUtils.checkUserName(name);
-                               userPrincipal = new X500Principal(name.toString());
-                               principals.add(userPrincipal);
-
-                               if (singleUser) {
-                                       principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal));
-                                       principals.add(new DataAdminPrincipal());
-                               }
+//             try {
+               String authName = authorization.getName();
+
+               // determine user's principal
+//                     final LdapName name;
+               final Principal userPrincipal;
+               if (authName == null) {
+//                             name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
+                       userPrincipal = new AnonymousPrincipal();
+                       principals.add(userPrincipal);
+               } else {
+//                             name = new LdapName(authName);
+                       checkUserName(authName);
+                       userPrincipal = new X500Principal(authName.toString());
+                       principals.add(userPrincipal);
+
+                       if (singleUser) {
+                               principals.add(new ImpliedByPrincipal(CmsConstants.ROLE_ADMIN, userPrincipal));
+                               principals.add(new DataAdminPrincipal());
                        }
+               }
 
-                       // Add roles provided by authorization
-                       for (String role : authorization.getRoles()) {
-                               LdapName roleName = new LdapName(role);
-                               if (roleName.equals(name)) {
-                                       // skip
-                               } else if (roleName.equals(NodeSecurityUtils.ROLE_ANONYMOUS_NAME)) {
-                                       // skip
-                               } else {
-                                       NodeSecurityUtils.checkImpliedPrincipalName(roleName);
-                                       principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal));
-                                       if (roleName.equals(NodeSecurityUtils.ROLE_ADMIN_NAME))
-                                               principals.add(new DataAdminPrincipal());
-                               }
+               // Add roles provided by authorization
+               for (String role : authorization.getRoles()) {
+//                             LdapName roleName = new LdapName(role);
+                       if (role.equals(authName)) {
+                               // skip
+                       } else if (role.equals(CmsConstants.ROLE_ANONYMOUS)) {
+                               // skip
+                       } else {
+//                                     NodeSecurityUtils.checkImpliedPrincipalName(role);
+                               principals.add(new ImpliedByPrincipal(role, userPrincipal));
+                               if (role.equals(CmsConstants.ROLE_ADMIN))
+                                       principals.add(new DataAdminPrincipal());
                        }
-
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot commit", e);
                }
+
+//             } catch (InvalidNameException e) {
+//                     throw new IllegalArgumentException("Cannot commit", e);
+//             }
        }
 
        private static void checkSubjectEmpty(Subject subject) {
@@ -128,30 +136,33 @@ class CmsAuthUtils {
                // TODO move it to a service in order to avoid static synchronization
                if (request != null) {
                        RemoteAuthSession httpSession = request.getSession();
-                       assert httpSession != null;
-                       String httpSessId = httpSession.getId();
+                       String httpSessId = httpSession != null ? httpSession.getId() : null;
                        boolean anonymous = authorization.getName() == null;
                        String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS;
-                       request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
-                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+                       request.setAttribute(RemoteAuthRequest.REMOTE_USER, remoteUser);
+                       request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
 
                        CmsSessionImpl cmsSession;
-                       CmsSessionImpl currentLocalSession = CmsSessionImpl.getByLocalId(httpSessId);
+                       CmsSessionImpl currentLocalSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessId);
                        if (currentLocalSession != null) {
-                               boolean currentLocalSessionAnonymous = currentLocalSession.getAuthorization().getName() == null;
+                               boolean currentLocalSessionAnonymous = currentLocalSession.isAnonymous();
                                if (!anonymous) {
                                        if (currentLocalSessionAnonymous) {
                                                currentLocalSession.close();
                                                // new CMS session
-                                               cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+                                               UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+                                               cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+                                               CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
                                        } else if (!authorization.getName().equals(currentLocalSession.getAuthorization().getName())) {
                                                throw new IllegalStateException("Inconsistent user " + authorization.getName()
                                                                + " for existing CMS session " + currentLocalSession);
                                        } else {
                                                // keep current session
                                                cmsSession = currentLocalSession;
-                                               // keyring
-                                               subject.getPrivateCredentials().addAll(cmsSession.getSecretKeys());
+                                               // credentials
+                                               // TODO control it more??
+                                               subject.getPrivateCredentials().addAll(cmsSession.getSubject().getPrivateCredentials());
+                                               subject.getPublicCredentials().addAll(cmsSession.getSubject().getPublicCredentials());
                                        }
                                } else {// anonymous
                                        if (!currentLocalSessionAnonymous) {
@@ -164,7 +175,9 @@ class CmsAuthUtils {
                                }
                        } else {
                                // new CMS session
-                               cmsSession = new WebCmsSessionImpl(subject, authorization, locale, request);
+                               UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+                               cmsSession = new RemoteCmsSessionImpl(cmsSessionUuid, subject, authorization, locale, request);
+                               CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
                        }
 
                        if (cmsSession == null)// should be dead code (cf. SuppressWarning of the method)
@@ -175,39 +188,45 @@ class CmsAuthUtils {
                                subject.getPrivateCredentials().add(nodeSessionId);
                        } else {
                                UUID storedSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
-                               // if (storedSessionId.equals(httpSessionId.getValue()))
-                               throw new IllegalStateException(
-                                               "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
+                               if (!storedSessionId.equals(nodeSessionId.getUuid()))
+                                       throw new IllegalStateException(
+                                                       "Subject already logged with session " + storedSessionId + " (not " + nodeSessionId + ")");
                        }
+                       request.setAttribute(CmsSession.class.getName(), cmsSession);
                } else {
-                       CmsSessionImpl cmsSession = new CmsSessionImpl(subject, authorization, locale, "desktop");
+                       CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(SINGLE_USER_LOCAL_ID);
+                       if (cmsSession == null) {
+                               UUID cmsSessionUuid = CmsContextImpl.getCmsContext().getUuidFactory().timeUUID();
+                               cmsSession = new CmsSessionImpl(cmsSessionUuid, subject, authorization, locale, SINGLE_USER_LOCAL_ID);
+                               CmsContextImpl.getCmsContext().registerCmsSession(cmsSession);
+                       }
                        CmsSessionId nodeSessionId = new CmsSessionId(cmsSession.getUuid());
                        subject.getPrivateCredentials().add(nodeSessionId);
                }
        }
 
-       public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
-               Authorization authorization = null;
-               Collection<ServiceReference<CmsSession>> sr;
-               try {
-                       sr = bc.getServiceReferences(CmsSession.class,
-                                       "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e);
-               }
-               CmsSessionImpl cmsSession;
-               if (sr.size() == 1) {
-                       cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next());
-//                     locale = cmsSession.getLocale();
-                       authorization = cmsSession.getAuthorization();
-                       if (authorization.getName() == null)
-                               return null;// anonymous is not sufficient
-               } else if (sr.size() == 0)
-                       return null;
-               else
-                       throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
-               return cmsSession;
-       }
+//     public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
+//             Authorization authorization = null;
+//             Collection<ServiceReference<CmsSession>> sr;
+//             try {
+//                     sr = bc.getServiceReferences(CmsSession.class,
+//                                     "(" + CmsSession.SESSION_LOCAL_ID + "=" + httpSessionId + ")");
+//             } catch (InvalidSyntaxException e) {
+//                     throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e);
+//             }
+//             CmsSessionImpl cmsSession;
+//             if (sr.size() == 1) {
+//                     cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next());
+////                   locale = cmsSession.getLocale();
+//                     authorization = cmsSession.getAuthorization();
+//                     if (authorization.getName() == null)
+//                             return null;// anonymous is not sufficient
+//             } else if (sr.size() == 0)
+//                     return null;
+//             else
+//                     throw new IllegalStateException(sr.size() + ">1 web sessions detected for http session " + httpSessionId);
+//             return cmsSession;
+//     }
 
        public static <T extends Principal> T getSinglePrincipal(Subject subject, Class<T> clss) {
                Set<T> principals = subject.getPrincipals(clss);
@@ -218,6 +237,11 @@ class CmsAuthUtils {
                return principals.iterator().next();
        }
 
+       private static void checkUserName(String name) throws IllegalArgumentException {
+               if (RESERVED_ROLES.contains(name))
+                       throw new IllegalArgumentException(name + " is a reserved name");
+       }
+
        private CmsAuthUtils() {
 
        }
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java
new file mode 100644 (file)
index 0000000..8834f35
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.auth;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.ContentName;
+import org.argeo.cms.SystemRole;
+
+/** Standard CMS system roles. */
+public enum CmsRole implements SystemRole {
+       userAdmin, //
+       groupAdmin, //
+       //
+       ;
+
+       private final static String QUALIFIER = "cms.";
+
+       private final ContentName name;
+
+       CmsRole() {
+               name = new ContentName(ArgeoNamespace.ROLE_NAMESPACE_URI, QUALIFIER + name());
+       }
+
+       @Override
+       public QName qName() {
+               return name;
+       }
+
+       @Override
+       public String toString() {
+               return name.toPrefixedString();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java
new file mode 100644 (file)
index 0000000..874381e
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.auth;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Scanner;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+/** Callback handler to be used with a command line UI. */
+public class ConsoleCallbackHandler implements CallbackHandler {
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               Console console = System.console();
+//             if (console == null)
+//                     throw new IllegalStateException("No console available");
+
+               Scanner scanner = null;
+               PrintWriter writer;
+               if (console == null) {// IDE
+                       scanner = new Scanner(System.in);
+                       writer = new PrintWriter(System.out, true);
+               } else {
+                       writer = console.writer();
+
+               }
+               for (int i = 0; i < callbacks.length; i++) {
+                       if (callbacks[i] instanceof TextOutputCallback) {
+                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
+                               writer.printf(callback.getMessage());
+                       } else if (callbacks[i] instanceof NameCallback) {
+                               NameCallback callback = (NameCallback) callbacks[i];
+                               writer.printf(callback.getPrompt());
+                               if (callback.getDefaultName() != null)
+                                       writer.printf(" (" + callback.getDefaultName() + ")");
+                               String answer = console != null ? console.readLine() : scanner.next();
+                               if (callback.getDefaultName() != null && answer.trim().equals(""))
+                                       callback.setName(callback.getDefaultName());
+                               else
+                                       callback.setName(answer);
+                       } else if (callbacks[i] instanceof PasswordCallback) {
+                               PasswordCallback callback = (PasswordCallback) callbacks[i];
+                               writer.printf(callback.getPrompt());
+                               char[] answer = console != null ? console.readPassword() : scanner.next().toCharArray();
+                               callback.setPassword(answer);
+                               Arrays.fill(answer, ' ');
+                       }
+//                     else if (callbacks[i] instanceof LocaleChoice) {
+//                             LocaleChoice callback = (LocaleChoice) callbacks[i];
+//                             writer.write("Language");
+//                             writer.write("\n");
+//                             for (int j = 0; j < callback.getLocales().size(); j++) {
+//                                     Locale locale = callback.getLocales().get(j);
+//                                     writer.print(j + " : " + locale.getDisplayName() + "\n");
+//                             }
+//                             writer.write("(" + callback.getDefaultIndex() + ") : ");
+//                             String answer = console.readLine();
+//                             if (answer.trim().equals(""))
+//                                     callback.setSelectedIndex(callback.getDefaultIndex());
+//                             else
+//                                     callback.setSelectedIndex(new Integer(answer.trim()));
+//                     }
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java
deleted file mode 100644 (file)
index 85a4824..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.security.AccessController;
-import java.security.Principal;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsSessionId;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.runtime.CmsContextImpl;
-import org.osgi.service.useradmin.Authorization;
-
-/**
- * Programmatic access to the currently authenticated user, within a CMS
- * context.
- */
-public final class CurrentUser {
-       /*
-        * CURRENT USER API
-        */
-
-       /**
-        * Technical username of the currently authenticated user.
-        * 
-        * @return the authenticated username or null if not authenticated / anonymous
-        */
-       public static String getUsername() {
-               return getUsername(currentSubject());
-       }
-
-       /**
-        * Human readable name of the currently authenticated user (typically first name
-        * and last name).
-        */
-       public static String getDisplayName() {
-               return getDisplayName(currentSubject());
-       }
-
-       /** Whether a user is currently authenticated. */
-       public static boolean isAnonymous() {
-               return isAnonymous(currentSubject());
-       }
-
-       /** Locale of the current user */
-       public final static Locale locale() {
-               return locale(currentSubject());
-       }
-
-       /** Roles of the currently logged-in user */
-       public final static Set<String> roles() {
-               return roles(currentSubject());
-       }
-
-       /** Returns true if the current user is in the specified role */
-       public static boolean isInRole(String role) {
-               Set<String> roles = roles();
-               return roles.contains(role);
-       }
-
-       /** Executes as the current user */
-       public final static <T> T doAs(PrivilegedAction<T> action) {
-               return Subject.doAs(currentSubject(), action);
-       }
-
-       /** Executes as the current user */
-       public final static <T> T tryAs(PrivilegedExceptionAction<T> action) throws PrivilegedActionException {
-               return Subject.doAs(currentSubject(), action);
-       }
-
-       /*
-        * WRAPPERS
-        */
-
-       public final static String getUsername(Subject subject) {
-               if (subject == null)
-                       throw new IllegalArgumentException("Subject cannot be null");
-               if (subject.getPrincipals(X500Principal.class).size() != 1)
-                       return CmsConstants.ROLE_ANONYMOUS;
-               Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
-               return principal.getName();
-       }
-
-       public final static String getDisplayName(Subject subject) {
-               return getAuthorization(subject).toString();
-       }
-
-       public final static Set<String> roles(Subject subject) {
-               Set<String> roles = new HashSet<String>();
-               roles.add(getUsername(subject));
-               for (Principal group : subject.getPrincipals(ImpliedByPrincipal.class)) {
-                       roles.add(group.getName());
-               }
-               return roles;
-       }
-
-       public final static Locale locale(Subject subject) {
-               Set<Locale> locales = subject.getPublicCredentials(Locale.class);
-               if (locales.isEmpty()) {
-                       Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
-                       return defaultLocale;
-               } else
-                       return locales.iterator().next();
-       }
-
-       /** Whether this user is currently authenticated. */
-       public static boolean isAnonymous(Subject subject) {
-               if (subject == null)
-                       return true;
-               String username = getUsername(subject);
-               return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS);
-       }
-
-       public CmsSession getCmsSession() {
-               Subject subject = currentSubject();
-               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
-               return CmsSessionImpl.getByUuid(cmsSessionId.getUuid());
-       }
-
-       /*
-        * HELPERS
-        */
-       private static Subject currentSubject() {
-               Subject subject = getAccessControllerSubject();
-               if (subject != null)
-                       return subject;
-               throw new IllegalStateException("Cannot find related subject");
-       }
-
-       private static Subject getAccessControllerSubject() {
-               return Subject.getSubject(AccessController.getContext());
-       }
-
-       private static Authorization getAuthorization(Subject subject) {
-               return subject.getPrivateCredentials(Authorization.class).iterator().next();
-       }
-
-       public static boolean logoutCmsSession(Subject subject) {
-               UUID nodeSessionId;
-               if (subject.getPrivateCredentials(CmsSessionId.class).size() == 1)
-                       nodeSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next().getUuid();
-               else
-                       return false;
-               CmsSessionImpl cmsSession = CmsSessionImpl.getByUuid(nodeSessionId.toString());
-
-               // FIXME logout all views
-               // TODO check why it is sometimes null
-               if (cmsSession != null)
-                       cmsSession.close();
-               // if (log.isDebugEnabled())
-               // log.debug("Logged out CMS session " + cmsSession.getUuid());
-               return true;
-       }
-
-       private CurrentUser() {
-       }
-}
index ea1046be9e00c2ed164f03a6fbcbed7d0da8dcf2..d4f402853b2174bdb1e568f906f70e5dd9b57e5f 100644 (file)
@@ -45,4 +45,5 @@ public class DataAdminLoginModule implements LoginModule {
                subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
                return true;
        }
+
 }
index c49a59ef1dcc5ea69033618702e0caff7d8c2271..ebab12f2cc4cd24149787663aa52dad44c651961 100644 (file)
@@ -1,6 +1,5 @@
 package org.argeo.cms.auth;
 
-import java.security.AccessController;
 import java.util.Map;
 import java.util.Set;
 
@@ -15,8 +14,9 @@ import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.argeo.cms.security.PBEKeySpecCallback;
-import org.argeo.util.PasswordEncryption;
+import org.argeo.api.cms.keyring.PBEKeySpecCallback;
+import org.argeo.cms.util.CurrentSubject;
+import org.argeo.cms.util.PasswordEncryption;
 
 /** Adds a secret key to the private credentials */
 public class KeyringLoginModule implements LoginModule {
@@ -28,7 +28,7 @@ public class KeyringLoginModule implements LoginModule {
                        Map<String, ?> options) {
                this.subject = subject;
                if (subject == null) {
-                       subject = Subject.getSubject(AccessController.getContext());
+                       this.subject = CurrentSubject.current();
                }
                this.callbackHandler = callbackHandler;
        }
index 2d1d14b4ee62afd9b64c037d3e2faca8a9be9ad7..be5d0e15e966355c55cb6e8ee70a4d980d729ae8 100644 (file)
@@ -4,6 +4,9 @@ import java.util.Locale;
 
 /** Transitional interface to decouple from the Servlet API. */
 public interface RemoteAuthRequest {
+       final static String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+       final static String AUTHORIZATION = "org.osgi.service.useradmin.authorization";
+
        RemoteAuthSession getSession();
 
        RemoteAuthSession createSession();
index f91b6c5decb6955d5790b218caa9bd6f9f88f594..b815b49d192674df0d1628428d7650005307f55c 100644 (file)
@@ -2,6 +2,9 @@ package org.argeo.cms.auth;
 
 /** Transitional interface to decouple from the Servlet API. */
 public interface RemoteAuthResponse {
-       void setHeader(String keys, String value);
+       /** Set this header to a single value, possibly removing previous values. */
+       void setHeader(String headerName, String value);
 
+       /** Add a value to this header. */
+       void addHeader(String headerName, String value);
 }
index d51997d74fca518340fc5f601e649c7e30dd6480..3c436ba1fc40edd772e161d65ea1c70bc5f39cea 100644 (file)
 package org.argeo.cms.auth;
 
-import java.security.AccessControlContext;
-import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.Base64;
 import java.util.function.Supplier;
 
 import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.util.CurrentSubject;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
 
 /** Remote authentication utilities. */
 public class RemoteAuthUtils {
+       private final static CmsLog log = CmsLog.getLog(RemoteAuthUtils.class);
+
        static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
-       private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext();
+       private final static Oid KERBEROS_OID;
+//     private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID;
+       static {
+               try {
+                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+//                     KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
+//                     KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");
+               } catch (GSSException e) {
+                       throw new IllegalStateException("Cannot create Kerberos OID", e);
+               }
+       }
 
        /**
         * Execute this supplier, using the CMS class loader as context classloader.
         * Useful to log in to JCR.
         */
        public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
-               ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
-               try {
-                       return Subject.doAs(
-                                       Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
-                                       new PrivilegedAction<T>() {
+               CmsSession cmsSession = getCmsSession(req);
+               return CurrentSubject.callAs(cmsSession.getSubject(), () -> supplier.get());
+//             ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+//             Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+//             try {
+//                     return Subject.doAs(
+//                                     Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
+//                                     new PrivilegedAction<T>() {
+//
+//                                             @Override
+//                                             public T run() {
+//                                                     return supplier.get();
+//                                             }
+//
+//                                     });
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentContextCl);
+//             }
+       }
+
+//     public final static void configureRequestSecurity(RemoteAuthRequest req) {
+//             if (req.getAttribute(AccessControlContext.class.getName()) != null)
+//                     throw new IllegalStateException("Request already authenticated.");
+//             AccessControlContext acc = AccessController.getContext();
+//             req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
+//             req.setAttribute(AccessControlContext.class.getName(), acc);
+//     }
+//
+//     public final static void clearRequestSecurity(RemoteAuthRequest req) {
+//             if (req.getAttribute(AccessControlContext.class.getName()) == null)
+//                     throw new IllegalStateException("Cannot clear non-authenticated request.");
+//             req.setAttribute(REMOTE_USER, null);
+//             req.setAttribute(AccessControlContext.class.getName(), null);
+//     }
+
+       public static CmsSession getCmsSession(RemoteAuthRequest req) {
+               CmsSession cmsSession = (CmsSession) req.getAttribute(CmsSession.class.getName());
+               if (cmsSession == null)
+                       throw new IllegalStateException("Request must have a CMS session attribute");
+               return cmsSession;
+       }
+
+       public static String createGssToken(Subject subject, String service, String server) {
+               if (subject.getPrivateCredentials(KerberosTicket.class).isEmpty())
+                       throw new IllegalArgumentException("Subject " + subject + " is not GSS authenticated.");
+               return Subject.doAs(subject, (PrivilegedAction<String>) () -> {
+                       // !! different format than Kerberos
+                       String serverPrinc = service + "@" + server;
+                       GSSContext context = null;
+                       String tokenStr = null;
+
+                       try {
+                               // Get service's principal name
+                               GSSManager manager = GSSManager.getInstance();
+                               // GSSName serverName = manager.createName(serverPrinc,
+                               // GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
+                               GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE);
+
+                               // Get the context for authentication
+                               context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
+                               // context.requestMutualAuth(true); // Request mutual authentication
+                               // context.requestConf(true); // Request confidentiality
+                               context.requestCredDeleg(true);
 
-                                               @Override
-                                               public T run() {
-                                                       return supplier.get();
-                                               }
+                               byte[] token = new byte[0];
 
-                                       });
+                               // token is ignored on the first call
+                               token = context.initSecContext(token, 0, token.length);
+
+                               // Send a token to the server if one was generated by
+                               // initSecContext
+                               if (token != null) {
+                                       tokenStr = Base64.getEncoder().encodeToString(token);
+                                       // complete=true;
+                               }
+                               return tokenStr;
+
+                       } catch (GSSException e) {
+                               throw new IllegalStateException("Cannot authenticate to " + serverPrinc, e);
+                       }
+               });
+       }
+
+       public static LoginContext anonymousLogin(RemoteAuthRequest remoteAuthRequest,
+                       RemoteAuthResponse remoteAuthResponse) {
+               // anonymous
+               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+                       LoginContext lc = CmsAuth.ANONYMOUS
+                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+                       lc.login();
+                       return lc;
+               } catch (LoginException e1) {
+                       if (log.isDebugEnabled())
+                               log.error("Cannot log in as anonymous", e1);
+                       return null;
                } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextCl);
+                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
                }
        }
 
-       public final static void configureRequestSecurity(RemoteAuthRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) != null)
-                       throw new IllegalStateException("Request already authenticated.");
-               AccessControlContext acc = AccessController.getContext();
-               req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
-               req.setAttribute(AccessControlContext.class.getName(), acc);
-       }
+       public static int askForWwwAuth(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse,
+                       String realm, boolean forceBasic) {
+               boolean negotiateFailed = false;
+               if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName()) != null) {
+                       // we already tried, so we give up in order not too loop endlessly
+                       if (remoteAuthRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName())
+                                       .startsWith(HttpHeader.NEGOTIATE)) {
+                               negotiateFailed = true;
+                       } else {
+                               return HttpStatus.FORBIDDEN.getCode();
+                       }
+               }
 
-       public final static void clearRequestSecurity(RemoteAuthRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) == null)
-                       throw new IllegalStateException("Cannot clear non-authenticated request.");
-               req.setAttribute(REMOTE_USER, null);
-               req.setAttribute(AccessControlContext.class.getName(), null);
+               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+               // realm=\"" + httpAuthRealm + "\"");
+               if (hasAcceptorCredentials() && !forceBasic && !negotiateFailed) {// SPNEGO
+                       remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(), HttpHeader.NEGOTIATE);
+                       // TODO make it configurable ?
+                       remoteAuthResponse.addHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
+                                       HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
+               } else {
+                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
+                                       HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
+               }
+
+               // response.setDateHeader("Date", System.currentTimeMillis());
+               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
+               // 60 * 60 * 1000));
+               // response.setHeader("Accept-Ranges", "bytes");
+               // response.setHeader("Connection", "Keep-Alive");
+               // response.setHeader("Keep-Alive", "timeout=5, max=97");
+               // response.setContentType("text/html; charset=UTF-8");
+
+               return HttpStatus.UNAUTHORIZED.getCode();
        }
 
-       public static CmsSession getCmsSession(RemoteAuthRequest req) {
-               Subject subject = Subject
-                               .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
-               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject);
-               return cmsSession;
+       private static boolean hasAcceptorCredentials() {
+               return CmsContextImpl.getCmsContext().getAcceptorCredentials() != null;
        }
+
 }
index 962094d4ace32377f3b9e4ba5da1e2ab1ce81f1a..987c3dd19dfeff760f579d27adf6f41558812a29 100644 (file)
@@ -14,16 +14,15 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
 import org.osgi.service.useradmin.Authorization;
 
-/** Use the HTTP session as the basis for authentication. */
+/** Use a remote session as the basis for authentication. */
 public class RemoteSessionLoginModule implements LoginModule {
        private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
 
@@ -34,8 +33,6 @@ public class RemoteSessionLoginModule implements LoginModule {
        private RemoteAuthRequest request = null;
        private RemoteAuthResponse response = null;
 
-       private BundleContext bc;
-
        private Authorization authorization;
        private Locale locale;
 
@@ -43,8 +40,6 @@ public class RemoteSessionLoginModule implements LoginModule {
        @Override
        public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
                        Map<String, ?> options) {
-               bc = FrameworkUtil.getBundle(RemoteSessionLoginModule.class).getBundleContext();
-               assert bc != null;
                this.subject = subject;
                this.callbackHandler = callbackHandler;
                this.sharedState = (Map<String, Object>) sharedState;
@@ -54,49 +49,43 @@ public class RemoteSessionLoginModule implements LoginModule {
        public boolean login() throws LoginException {
                if (callbackHandler == null)
                        return false;
-               RemoteAuthCallback httpCallback = new RemoteAuthCallback();
+               RemoteAuthCallback remoteAuthCallback = new RemoteAuthCallback();
                try {
-                       callbackHandler.handle(new Callback[] { httpCallback });
+                       callbackHandler.handle(new Callback[] { remoteAuthCallback });
                } catch (IOException e) {
                        throw new LoginException("Cannot handle http callback: " + e.getMessage());
                } catch (UnsupportedCallbackException e) {
                        return false;
                }
-               request = httpCallback.getRequest();
+               request = remoteAuthCallback.getRequest();
                if (request == null) {
-                       RemoteAuthSession httpSession = httpCallback.getHttpSession();
+                       RemoteAuthSession httpSession = remoteAuthCallback.getHttpSession();
                        if (httpSession == null)
                                return false;
                        // TODO factorize with below
                        String httpSessionId = httpSession.getId();
-//                     if (log.isTraceEnabled())
-//                             log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
-                       CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
-                       if (cmsSession != null) {
+                       CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
+                       if (cmsSession != null && !cmsSession.isAnonymous()) {
                                authorization = cmsSession.getAuthorization();
                                locale = cmsSession.getLocale();
                                if (log.isTraceEnabled())
                                        log.trace("Retrieved authorization from " + cmsSession);
                        }
                } else {
-                       authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
+                       authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
                        if (authorization == null) {// search by session ID
                                RemoteAuthSession httpSession = request.getSession();
-                               if (httpSession == null) {
-                                       // TODO make sure this is always safe
-                                       if (log.isTraceEnabled())
-                                               log.trace("Create http session");
-                                       httpSession = request.createSession();
-                               }
-                               String httpSessionId = httpSession.getId();
-//                             if (log.isTraceEnabled())
-//                                     log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
-                               CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
-                               if (cmsSession != null) {
-                                       authorization = cmsSession.getAuthorization();
-                                       locale = cmsSession.getLocale();
-                                       if (log.isTraceEnabled())
-                                               log.trace("Retrieved authorization from " + cmsSession);
+                               if (httpSession != null) {
+                                       String httpSessionId = httpSession.getId();
+                                       CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
+                                       if (cmsSession != null && !cmsSession.isAnonymous()) {
+                                               authorization = cmsSession.getAuthorization();
+                                               locale = cmsSession.getLocale();
+                                               if (log.isTraceEnabled())
+                                                       log.trace("Retrieved authorization from " + cmsSession);
+                                       }
+                               }else {
+                                       request.createSession();
                                }
                        }
                        sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
@@ -110,7 +99,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                } else {
                        if (log.isTraceEnabled())
                                log.trace("HTTP login: " + true);
-                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+                       request.setAttribute(RemoteAuthRequest.AUTHORIZATION, authorization);
                        return true;
                }
        }
@@ -119,7 +108,7 @@ public class RemoteSessionLoginModule implements LoginModule {
        public boolean commit() throws LoginException {
                byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
                if (outToken != null) {
-                       response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
+                       response.setHeader(HttpHeader.WWW_AUTHENTICATE.getHeaderName(),
                                        "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
                }
 
@@ -157,7 +146,7 @@ public class RemoteSessionLoginModule implements LoginModule {
        }
 
        private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
-               String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
+               String authHeader = httpRequest.getHeader(HttpHeader.AUTHORIZATION.getHeaderName());
                extractHttpAuth(authHeader);
        }
 
@@ -166,7 +155,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                        StringTokenizer st = new StringTokenizer(authHeader);
                        if (st.hasMoreTokens()) {
                                String basic = st.nextToken();
-                               if (basic.equalsIgnoreCase("Basic")) {
+                               if (basic.equalsIgnoreCase(HttpHeader.BASIC)) {
                                        try {
                                                // TODO manipulate char[]
                                                Base64.Decoder decoder = Base64.getDecoder();
@@ -184,7 +173,7 @@ public class RemoteSessionLoginModule implements LoginModule {
                                        } catch (Exception e) {
                                                throw new IllegalStateException("Couldn't retrieve authentication", e);
                                        }
-                               } else if (basic.equalsIgnoreCase("Negotiate")) {
+                               } else if (basic.equalsIgnoreCase(HttpHeader.NEGOTIATE)) {
                                        String spnegoToken = st.nextToken();
                                        Base64.Decoder decoder = Base64.getDecoder();
                                        byte[] authToken = decoder.decode(spnegoToken);
@@ -192,15 +181,6 @@ public class RemoteSessionLoginModule implements LoginModule {
                                }
                        }
                }
-
-               // auth token
-               // String mail = request.getParameter(LdapAttrs.mail.name());
-               // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
-               // if (authPassword != null) {
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
-               // if (mail != null)
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
-               // }
        }
 
        private void extractClientCertificate(RemoteAuthRequest req) {
@@ -212,7 +192,8 @@ public class RemoteSessionLoginModule implements LoginModule {
                        if (log.isDebugEnabled())
                                log.debug("Client certificate " + certDn + " verified by servlet container");
                } // Reverse proxy verified the client certificate
-               String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
+               String clientDnHttpHeader = CmsStateImpl.getDeployProperty(CmsContextImpl.getCmsContext().getCmsState(),
+                               CmsDeployProperty.HTTP_PROXY_SSL_HEADER_DN);
                if (clientDnHttpHeader != null) {
                        String certDn = req.getHeader(clientDnHttpHeader);
                        // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
index 08380ac5a227cd165756e9b430cec4e6fd9c5e6d..4b36f28abb90f927df087b743f81c09852241319 100644 (file)
@@ -1,7 +1,5 @@
 package org.argeo.cms.auth;
 
-import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.Locale;
 import java.util.Map;
 
@@ -13,15 +11,15 @@ import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 import javax.security.auth.x500.X500Principal;
 
-import org.argeo.api.cms.CmsLog;
-import org.argeo.osgi.useradmin.IpaUtils;
-import org.argeo.osgi.useradmin.OsUserUtils;
-import org.argeo.util.naming.LdapAttrs;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.directory.ldap.IpaUtils;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.osgi.useradmin.OsUserUtils;
 import org.osgi.service.useradmin.Authorization;
 
 /** Login module for when the system is owned by a single user. */
 public class SingleUserLoginModule implements LoginModule {
-       private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class);
+//     private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class);
 
        private Subject subject;
        private Map<String, Object> sharedState = null;
@@ -54,15 +52,9 @@ public class SingleUserLoginModule implements LoginModule {
                        Object username = sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        if (username == null)
                                throw new LoginException("No username available");
-                       String hostname;
-                       try {
-                               hostname = InetAddress.getLocalHost().getHostName();
-                       } catch (UnknownHostException e) {
-                               log.warn("Using localhost as hostname", e);
-                               hostname = "localhost";
-                       }
+                       String hostname = CmsContextImpl.getCmsContext().getCmsState().getHostname();
                        String baseDn = ("." + hostname).replaceAll("\\.", ",dc=");
-                       X500Principal principal = new X500Principal(LdapAttrs.uid + "=" + username + baseDn);
+                       X500Principal principal = new X500Principal(LdapAttr.uid + "=" + username + baseDn);
                        authorizationName = principal.getName();
                }
 
@@ -74,8 +66,8 @@ public class SingleUserLoginModule implements LoginModule {
                        locale = Locale.getDefault();
                Authorization authorization = new SingleUserAuthorization(authorizationName);
                CmsAuthUtils.addAuthorization(subject, authorization);
-               
-               // Add standard Java OS login 
+
+               // Add standard Java OS login
                OsUserUtils.loginAsSystemUser(subject);
 
                // additional principals (must be after Authorization registration)
index 2dbad96d28d592bcb007d7e186ea6223c054f62c..e5f367d23f1e77cdba3ed26fe806006e8b06a00e 100644 (file)
@@ -1,6 +1,5 @@
 package org.argeo.cms.auth;
 
-import java.lang.reflect.Method;
 import java.util.Map;
 
 import javax.security.auth.Subject;
@@ -11,11 +10,12 @@ import javax.security.auth.spi.LoginModule;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
 import org.ietf.jgss.GSSName;
 
+import com.sun.security.jgss.GSSUtil;
+
 /** SPNEGO login */
 public class SpnegoLoginModule implements LoginModule {
        private final static CmsLog log = CmsLog.getLog(SpnegoLoginModule.class);
@@ -36,25 +36,33 @@ public class SpnegoLoginModule implements LoginModule {
        @Override
        public boolean login() throws LoginException {
                byte[] spnegoToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN);
-               if (spnegoToken == null)
+               if (spnegoToken == null) {
+                       if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+                               // workaround: set shared state name to empty
+                               // in order to avoid Krb5LoginModule printing to System.out
+                               // TODO ask upstream to only log in debug mode
+                               sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, "");
+                       }
                        return false;
+               }
                gssContext = checkToken(spnegoToken);
                if (gssContext == null)
                        return false;
-               else
+               else {
+                       if (!sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)) {
+                               try {
+                                       if (gssContext.getCredDelegState()) {
+                                               // commit will succeeed only if we have credential delegation
+                                               GSSName name = gssContext.getSrcName();
+                                               String username = name.toString();
+                                               sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, username);
+                                       }
+                               } catch (GSSException e) {
+                                       throw new IllegalStateException("Cannot retrieve SPNEGO name", e);
+                               }
+                       }
                        return true;
-               // try {
-               // String clientName = gssContext.getSrcName().toString();
-               // String role = clientName.substring(clientName.indexOf('@') + 1);
-               //
-               // log.debug("SpnegoUserRealm: established a security context");
-               // log.debug("Client Principal is: " + gssContext.getSrcName());
-               // log.debug("Server Principal is: " + gssContext.getTargName());
-               // log.debug("Client Default Role: " + role);
-               // } catch (GSSException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
+               }
        }
 
        @Override
@@ -63,14 +71,12 @@ public class SpnegoLoginModule implements LoginModule {
                        return false;
 
                try {
-                       Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
-                       Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
                        Subject gssSubject;
                        if (gssContext.getCredDelegState())
-                               gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(),
-                                               gssContext.getDelegCred());
+                               gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), gssContext.getDelegCred());
                        else
-                               gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(), null);
+                               gssSubject = (Subject) GSSUtil.createSubject(gssContext.getSrcName(), null);
+                       // without credential delegation we won't have access to the Kerberos key
                        subject.getPrincipals().addAll(gssSubject.getPrincipals());
                        subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials());
                        return true;
@@ -111,8 +117,7 @@ public class SpnegoLoginModule implements LoginModule {
        private GSSContext checkToken(byte[] authToken) {
                GSSManager manager = GSSManager.getInstance();
                try {
-                       GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials());
-
+                       GSSContext gContext = manager.createContext(CmsContextImpl.getCmsContext().getAcceptorCredentials());
                        if (gContext == null) {
                                log.debug("SpnegoUserRealm: failed to establish GSSContext");
                        } else {
@@ -132,8 +137,4 @@ public class SpnegoLoginModule implements LoginModule {
 
        }
 
-       @Deprecated
-       public static boolean hasAcceptorCredentials() {
-               return CmsContextImpl.getAcceptorCredentials() != null;
-       }
 }
index 738b507e79495a5898f29098f5a0f2c4169bf8a9..2b5c41ddf8333395ddccf823d14124b5000ddcbf 100644 (file)
@@ -1,8 +1,9 @@
 package org.argeo.cms.auth;
 
-import static org.argeo.util.naming.LdapAttrs.cn;
+import static org.argeo.api.acr.ldap.LdapAttr.cn;
 
 import java.io.IOException;
+import java.security.Principal;
 import java.security.PrivilegedAction;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -24,18 +25,13 @@ import javax.security.auth.login.CredentialNotFoundException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
+import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.cms.directory.ldap.IpaUtils;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
-import org.argeo.cms.security.CryptoKeyring;
-import org.argeo.osgi.useradmin.AuthenticatingUser;
-import org.argeo.osgi.useradmin.IpaUtils;
-import org.argeo.osgi.useradmin.TokenUtils;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
+import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
+import org.argeo.cms.osgi.useradmin.TokenUtils;
 import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.Group;
 import org.osgi.service.useradmin.User;
@@ -52,11 +48,11 @@ public class UserAdminLoginModule implements LoginModule {
        private CallbackHandler callbackHandler;
        private Map<String, Object> sharedState = null;
 
-       private List<String> indexedUserProperties = Arrays
-                       .asList(new String[] { LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
+       private List<String> indexedUserProperties = Arrays.asList(new String[] { LdapAttr.mail.name(), LdapAttr.uid.name(),
+                       LdapAttr.employeeNumber.name(), LdapAttr.authPassword.name() });
 
        // private state
-       private BundleContext bc;
+//     private BundleContext bc;
        private User authenticatedUser = null;
        private Locale locale;
 
@@ -70,7 +66,7 @@ public class UserAdminLoginModule implements LoginModule {
                        Map<String, ?> options) {
                this.subject = subject;
                try {
-                       bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext();
+//                     bc = FrameworkUtil.getBundle(UserAdminLoginModule.class).getBundleContext();
                        this.callbackHandler = callbackHandler;
                        this.sharedState = (Map<String, Object>) sharedState;
                } catch (Exception e) {
@@ -80,7 +76,7 @@ public class UserAdminLoginModule implements LoginModule {
 
        @Override
        public boolean login() throws LoginException {
-               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
                final String username;
                final char[] password;
                Object certificateChain = null;
@@ -91,17 +87,13 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
                        // // TODO locale?
+               } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
+                               && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN)) {
+                       // SPNEGO login has succeeded, that's enough for us at this stage
+                       return true;
                } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
                                && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN)) {
                        String certDn = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
-//                     LdapName ldapName;
-//                     try {
-//                             ldapName = new LdapName(certificateName);
-//                     } catch (InvalidNameException e) {
-//                             e.printStackTrace();
-//                             return false;
-//                     }
-//                     username = ldapName.getRdn(ldapName.size() - 1).getValue().toString();
                        username = certDn;
                        certificateChain = sharedState.get(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN);
                        password = null;
@@ -111,11 +103,6 @@ public class UserAdminLoginModule implements LoginModule {
                        username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
                        password = null;
                        preauth = true;
-//             } else if (singleUser) {
-//                     username = OsUserUtils.getOsUsername();
-//                     password = null;
-//                     // TODO retrieve from http session
-//                     locale = Locale.getDefault();
                } else {
 
                        // ask for username and password
@@ -169,20 +156,24 @@ public class UserAdminLoginModule implements LoginModule {
                        return true;// expect Kerberos
 
                if (password != null) {
+                       // TODO disabling bind for the time being,
+                       // as it requires authorisations to be set at LDAP level
+                       boolean tryBind = false;
                        // try bind first
-                       try {
-                               AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
-                               bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
-                               // TODO check tokens as well
-                               if (bindAuthorization != null) {
-                                       authenticatedUser = user;
-                                       return true;
+                       if (tryBind)
+                               try {
+                                       AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
+                                       bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
+                                       // TODO check tokens as well
+                                       if (bindAuthorization != null) {
+                                               authenticatedUser = user;
+                                               return true;
+                                       }
+                               } catch (Exception e) {
+                                       // silent
+                                       if (log.isTraceEnabled())
+                                               log.trace("Bind failed", e);
                                }
-                       } catch (Exception e) {
-                               // silent
-                               if (log.isTraceEnabled())
-                                       log.trace("Bind failed", e);
-                       }
 
                        // works only if a connection password is provided
                        if (!user.hasCredential(null, password)) {
@@ -212,7 +203,7 @@ public class UserAdminLoginModule implements LoginModule {
 //             if (singleUser) {
 //                     OsUserUtils.loginAsSystemUser(subject);
 //             }
-               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getCmsContext().getUserAdmin();
                Authorization authorization;
                if (callbackHandler == null) {// anonymous
                        authorization = userAdmin.getAuthorization(null);
@@ -237,6 +228,8 @@ public class UserAdminLoginModule implements LoginModule {
                                        throw new LoginException("Kerberos login " + authenticatingUser.getName()
                                                        + " is inconsistent with user admin login " + authenticatedUser.getName());
                        }
+                       if (log.isTraceEnabled())
+                               log.trace("Retrieve authorization for " + authenticatingUser + "... ");
                        authorization = Subject.doAs(subject, new PrivilegedAction<Authorization>() {
 
                                @Override
@@ -256,34 +249,47 @@ public class UserAdminLoginModule implements LoginModule {
                CmsAuthUtils.addAuthorization(subject, authorization);
 
                // Unlock keyring (underlying login to the JCR repository)
-               char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
-               if (password != null) {
-                       ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
-                       if (keyringSr != null) {
-                               CryptoKeyring keyring = bc.getService(keyringSr);
-                               Subject.doAs(subject, new PrivilegedAction<Void>() {
-
-                                       @Override
-                                       public Void run() {
-                                               try {
-                                                       keyring.unlock(password);
-                                               } catch (Exception e) {
-                                                       e.printStackTrace();
-                                                       log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
-                                                                       + ": " + e.getMessage());
-                                               }
-                                               return null;
-                                       }
-
-                               });
-                       }
-               }
+//             char[] password = (char[]) sharedState.get(CmsAuthUtils.SHARED_STATE_PWD);
+//             if (password != null) {
+//                     ServiceReference<CryptoKeyring> keyringSr = bc.getServiceReference(CryptoKeyring.class);
+//                     if (keyringSr != null) {
+//                             CryptoKeyring keyring = bc.getService(keyringSr);
+//                             Subject.doAs(subject, new PrivilegedAction<Void>() {
+//
+//                                     @Override
+//                                     public Void run() {
+//                                             try {
+//                                                     keyring.unlock(password);
+//                                             } catch (Exception e) {
+//                                                     e.printStackTrace();
+//                                                     log.warn("Could not unlock keyring with the password provided by " + authorization.getName()
+//                                                                     + ": " + e.getMessage());
+//                                             }
+//                                             return null;
+//                                     }
+//
+//                             });
+//                     }
+//             }
 
                // Register CmsSession with initial subject
                CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
 
-               if (log.isDebugEnabled())
-                       log.debug("Logged in to CMS: " + subject);
+               if (log.isDebugEnabled()) {
+                       StringBuilder msg = new StringBuilder();
+                       msg.append("Logged in to CMS: " + authorization.getName() + "(" + authorization + ")\n");
+                       for (Principal principal : subject.getPrincipals()) {
+                               msg.append("  Principal: " + principal.getName()).append(" (")
+                                               .append(principal.getClass().getSimpleName()).append(")\n");
+                       }
+                       for (Object credential : subject.getPublicCredentials()) {
+                               msg.append("  Public Credential: " + credential).append(" (")
+                                               .append(credential.getClass().getSimpleName()).append(")\n");
+                       }
+                       log.debug(msg);
+               }
+//             if (log.isTraceEnabled())
+//                     log.trace(" Subject: " + subject);
                return true;
        }
 
index eed38cc3285aa17de5ca6563f556c0f0e21027c3..bef6d7f0a187718c914fc6084f193a3aa7e29137 100644 (file)
@@ -6,8 +6,9 @@ import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
+import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.cms.CmsConstants;
-import org.argeo.util.naming.LdapAttrs;
+import org.argeo.cms.CurrentUser;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
@@ -18,7 +19,7 @@ public class UserAdminUtils {
        // CURRENTUSER HELPERS
        /** Checks if current user is the same as the passed one */
        public static boolean isCurrentUser(User user) {
-               String userUsername = getProperty(user, LdapAttrs.DN);
+               String userUsername = getProperty(user, LdapAttr.DN);
                LdapName userLdapName = getLdapName(userUsername);
                LdapName selfUserName = getCurrentUserLdapName();
                return userLdapName.equals(selfUserName);
@@ -43,7 +44,7 @@ public class UserAdminUtils {
 
        /** Retrieves the current logged-in user common name */
        public final static String getCommonName(User user) {
-               return getProperty(user, LdapAttrs.cn.name());
+               return getProperty(user, LdapAttr.cn.name());
        }
 
        // OTHER USERS HELPERS
@@ -54,8 +55,8 @@ public class UserAdminUtils {
        public static String getUserLocalId(String dn) {
                LdapName ldapName = getLdapName(dn);
                Rdn last = ldapName.getRdn(ldapName.size() - 1);
-               if (last.getType().toLowerCase().equals(LdapAttrs.uid.name())
-                               || last.getType().toLowerCase().equals(LdapAttrs.cn.name()))
+               if (last.getType().toLowerCase().equals(LdapAttr.uid.name())
+                               || last.getType().toLowerCase().equals(LdapAttr.cn.name()))
                        return (String) last.getValue();
                else
                        throw new IllegalArgumentException("Cannot retrieve user local id, non valid dn: " + dn);
@@ -67,16 +68,19 @@ public class UserAdminUtils {
         */
        public static String getUserDisplayName(UserAdmin userAdmin, String dn) {
                Role user = userAdmin.getRole(dn);
-               String dName;
                if (user == null)
-                       dName = getUserLocalId(dn);
-               else {
-                       dName = getProperty(user, LdapAttrs.displayName.name());
-                       if (isEmpty(dName))
-                               dName = getProperty(user, LdapAttrs.cn.name());
-                       if (isEmpty(dName))
-                               dName = getUserLocalId(dn);
-               }
+                       return getUserLocalId(dn);
+               return getUserDisplayName(user);
+       }
+
+       public static String getUserDisplayName(Role user) {
+               String dName = getProperty(user, LdapAttr.displayName.name());
+               if (isEmpty(dName))
+                       dName = getProperty(user, LdapAttr.cn.name());
+               if (isEmpty(dName))
+                       dName = getProperty(user, LdapAttr.uid.name());
+               if (isEmpty(dName))
+                       dName = getUserLocalId(user.getName());
                return dName;
        }
 
@@ -89,7 +93,7 @@ public class UserAdminUtils {
                if (user == null)
                        return null;
                else
-                       return getProperty(user, LdapAttrs.mail.name());
+                       return getProperty(user, LdapAttr.mail.name());
        }
 
        // LDAP NAMES HELPERS
@@ -122,7 +126,7 @@ public class UserAdminUtils {
        }
 
        /**
-        * Simply retrieves a LDAP name from a {@link LdapAttrs.DN} with no exception
+        * Simply retrieves a LDAP name from a {@link LdapAttr.DN} with no exception
         */
        private static LdapName getLdapName(String dn) {
                try {
@@ -135,7 +139,7 @@ public class UserAdminUtils {
        /** Simply retrieves a display name of the relevant domain */
        public final static String getDomainName(User user) {
                String dn = user.getName();
-               if (dn.endsWith(CmsConstants.ROLES_BASEDN))
+               if (dn.endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))
                        return "System roles";
                if (dn.endsWith(CmsConstants.TOKENS_BASEDN))
                        return "Tokens";
@@ -147,8 +151,8 @@ public class UserAdminUtils {
                        int i = 0;
                        loop: while (i < rdns.size()) {
                                Rdn currrRdn = rdns.get(i);
-                               if (LdapAttrs.uid.name().equals(currrRdn.getType()) || LdapAttrs.cn.name().equals(currrRdn.getType())
-                                               || LdapAttrs.ou.name().equals(currrRdn.getType()))
+                               if (LdapAttr.uid.name().equals(currrRdn.getType()) || LdapAttr.cn.name().equals(currrRdn.getType())
+                                               || LdapAttr.ou.name().equals(currrRdn.getType()))
                                        break loop;
                                else {
                                        String currVal = (String) currrRdn.getValue();
diff --git a/org.argeo.cms/src/org/argeo/cms/client/CmsClient.java b/org.argeo.cms/src/org/argeo/cms/client/CmsClient.java
new file mode 100644 (file)
index 0000000..8cfae3a
--- /dev/null
@@ -0,0 +1,172 @@
+package org.argeo.cms.client;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.net.http.WebSocket;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.cms.auth.ConsoleCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.http.HttpHeader;
+
+/** Utility to connect to a remote CMS node. */
+public class CmsClient {
+       public final static String CLIENT_LOGIN_CONTEXT = "CLIENT";
+
+       private URI uri;
+
+       private HttpClient httpClient;
+       private String gssToken;
+
+       public CmsClient(URI uri) {
+               this.uri = uri;
+       }
+
+       public void login() {
+               String server = uri.getHost();
+
+               URL jaasUrl = CmsClient.class.getResource("jaas-client-ipa.cfg");
+               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+               try {
+                       LoginContext lc = new LoginContext(CLIENT_LOGIN_CONTEXT, new ConsoleCallbackHandler());
+                       lc.login();
+                       gssToken = RemoteAuthUtils.createGssToken(lc.getSubject(), "HTTP", server);
+               } catch (LoginException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } finally {
+
+               }
+       }
+
+       public String getAsString() {
+               return getAsString(uri);
+       }
+
+       public String getAsString(URI uri) {
+               uri = normalizeUri(uri);
+               try {
+                       HttpClient httpClient = getHttpClient();
+
+                       HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+                                       .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken()) //
+                                       .build();
+                       BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+                       HttpResponse<String> response = httpClient.send(request, bodyHandler);
+                       return response.body();
+//                     int responseCode = response.statusCode();
+//                     System.exit(responseCode);
+               } catch (IOException | InterruptedException e) {
+                       throw new RuntimeException("Cannot read " + uri + " as a string", e);
+               }
+       }
+
+       protected URI normalizeUri(URI uri) {
+               if (uri.getHost() != null)
+                       return uri;
+               try {
+                       String path = uri.getPath();
+                       if (path.startsWith("/")) {// absolute
+                               return new URI(this.uri.getScheme(), this.uri.getUserInfo(), this.uri.getHost(), this.uri.getPort(),
+                                               path, uri.getQuery(), uri.getFragment());
+                       } else {
+                               String thisUriStr = this.uri.toString();
+                               if (!thisUriStr.endsWith("/"))
+                                       thisUriStr = thisUriStr + "/";
+                               return URI.create(thisUriStr + path);
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot interpret " + uri, e);
+               }
+       }
+
+       public URI getUri() {
+               return uri;
+       }
+
+       String getGssToken() {
+               return gssToken;
+       }
+
+       public HttpClient getHttpClient() {
+               if (httpClient == null) {
+                       login();
+                       HttpClient client = HttpClient.newBuilder() //
+                                       .sslContext(ipaSslContext()) //
+                                       .version(HttpClient.Version.HTTP_1_1) //
+                                       .build();
+                       httpClient = client;
+               }
+               return httpClient;
+       }
+
+       public CompletableFuture<WebSocket> newWebSocket(WebSocket.Listener listener) {
+               return newWebSocket(uri, listener);
+       }
+
+       public CompletableFuture<WebSocket> newWebSocket(URI uri, WebSocket.Listener listener) {
+               CompletableFuture<WebSocket> ws = getHttpClient().newWebSocketBuilder()
+                               .header(HttpHeader.AUTHORIZATION.getHeaderName(), HttpHeader.NEGOTIATE + " " + getGssToken())
+                               .buildAsync(uri, listener);
+               return ws;
+       }
+
+       @SuppressWarnings("unchecked")
+       protected SSLContext ipaSslContext() {
+               try {
+                       final Collection<X509Certificate> certificates;
+                       Path caCertificatePath = Paths.get("/etc/ipa/ca.crt");
+                       if (Files.exists(caCertificatePath)) {
+                               CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+                               try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(caCertificatePath))) {
+                                       certificates = (Collection<X509Certificate>) certificateFactory.generateCertificates(in);
+                               }
+                       } else {
+                               certificates = null;
+                       }
+                       TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
+                               public void checkClientTrusted(X509Certificate[] xcs, String string) {
+                               }
+
+                               public void checkServerTrusted(X509Certificate[] xcs, String string) {
+                               }
+
+                               public X509Certificate[] getAcceptedIssuers() {
+                                       if (certificates == null)
+                                               return null;
+                                       return certificates.toArray(new X509Certificate[certificates.size()]);
+                               }
+                       } };
+
+                       SSLContext sc = SSLContext.getInstance("ssl");
+                       sc.init(null, noopTrustManager, null);
+                       return sc;
+               } catch (KeyManagementException | NoSuchAlgorithmException | CertificateException | IOException e) {
+                       throw new IllegalStateException("Cannot create SSL context ", e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java
new file mode 100644 (file)
index 0000000..e8dd2fa
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.cms.client;
+
+import java.net.URI;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketEventClient implements Runnable {
+
+       private final URI uri;
+
+       private WebSocket webSocket;
+
+       private CmsClient cmsClient;
+
+       public WebSocketEventClient(URI uri) {
+               this.uri = uri;
+               cmsClient = new CmsClient(uri);
+       }
+
+       @Override
+       public void run() {
+               try {
+                       CompletableFuture<WebSocket> ws = cmsClient.newWebSocket(new WsEventListener());
+
+                       WebSocket webSocket = ws.get();
+                       webSocket.request(Long.MAX_VALUE);
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+                       while (!webSocket.isInputClosed()) {
+                               webSocket.sendPing(ByteBuffer.allocate(0));
+                               Thread.sleep(10000);
+                       }
+               } catch (InterruptedException e) {
+                       if (webSocket != null)
+                               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+               } catch (ExecutionException e) {
+                       throw new RuntimeException("Cannot listent to " + uri, e.getCause());
+               }
+       }
+
+       private class WsEventListener implements WebSocket.Listener {
+               public CompletionStage<?> onText(WebSocket webSocket, CharSequence message, boolean last) {
+                       System.out.println(message);
+                       CompletionStage<String> res = CompletableFuture.completedStage(message.toString());
+                       return res;
+               }
+
+               @Override
+               public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+                       // System.out.println("Pong received.");
+                       return null;
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketPing.java
new file mode 100644 (file)
index 0000000..808c8de
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.cms.client;
+
+import java.math.RoundingMode;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+
+/** Tests connectivity to the web socket server. */
+public class WebSocketPing implements Runnable {
+       private final static int PING_FRAME_SIZE = 125;
+       private final static DecimalFormat decimalFormat = new DecimalFormat("0.0");
+       static {
+               decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
+       }
+
+       private final URI uri;
+       private final UUID uuid;
+
+       private WebSocket webSocket;
+
+       public WebSocketPing(URI uri) {
+               this.uri = uri;
+               this.uuid = UUID.randomUUID();
+       }
+
+       @Override
+       public void run() {
+               try {
+                       WebSocket.Listener listener = new WebSocket.Listener() {
+
+                               @Override
+                               public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
+                                       long msb = message.getLong();
+                                       long lsb = message.getLong();
+                                       long end = System.nanoTime();
+                                       if (msb != uuid.getMostSignificantBits() || lsb != uuid.getLeastSignificantBits())
+                                               return null; // ignore
+                                       long begin = message.getLong();
+                                       double durationNs = end - begin;
+                                       double durationMs = durationNs / 1000000;
+                                       int size = message.remaining() + (3 * Long.BYTES);
+                                       System.out.println(
+                                                       size + " bytes from " + uri + ": time=" + decimalFormat.format(durationMs) + " ms");
+                                       return null;
+                               }
+
+                       };
+
+                       HttpClient client = HttpClient.newHttpClient();
+                       CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+                       webSocket = ws.get();
+                       webSocket.request(Long.MAX_VALUE);
+
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "")));
+
+                       while (!webSocket.isInputClosed()) {
+                               long begin = System.nanoTime();
+                               ByteBuffer buffer = ByteBuffer.allocate(PING_FRAME_SIZE);
+                               buffer.putLong(uuid.getMostSignificantBits());
+                               buffer.putLong(uuid.getLeastSignificantBits());
+                               buffer.putLong(begin);
+                               buffer.flip();
+                               webSocket.sendPing(buffer);
+                               Thread.sleep(1000);
+                       }
+               } catch (InterruptedException e) {
+                       if (webSocket != null)
+                               webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "");
+               } catch (ExecutionException e) {
+                       throw new RuntimeException("Cannot ping " + uri, e.getCause());
+               }
+       }
+
+//     public static void main(String[] args) throws Exception {
+//             if (args.length == 0) {
+//                     System.err.println("usage: java " + WsPing.class.getName() + " <url>");
+//                     System.exit(1);
+//                     return;
+//             }
+//             URI uri = URI.create(args[0]);
+//             new WsPing(uri).run();
+//     }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg
new file mode 100644 (file)
index 0000000..b776c2c
--- /dev/null
@@ -0,0 +1,4 @@
+CLIENT {
+    com.sun.security.auth.module.Krb5LoginModule required
+     useTicketCache=true;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java b/org.argeo.cms/src/org/argeo/cms/dav/DavClient.java
new file mode 100644 (file)
index 0000000..6fe2eb6
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandler;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.Iterator;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpMethod;
+import org.argeo.cms.http.HttpStatus;
+
+public class DavClient {
+
+       private HttpClient httpClient;
+
+       public DavClient() {
+               httpClient = HttpClient.newBuilder() //
+//                             .sslContext(insecureContext()) //
+                               .version(HttpClient.Version.HTTP_1_1) //
+                               .authenticator(new Authenticator() {
+
+                                       @Override
+                                       protected PasswordAuthentication getPasswordAuthentication() {
+                                               return new PasswordAuthentication("root", "demo".toCharArray());
+                                       }
+
+                               }) //
+                               .build();
+       }
+
+       public void setProperty(String url, QName key, String value) {
+               try {
+                       String body = """
+                                       <?xml version="1.0" encoding="utf-8" ?>
+                                       <D:propertyupdate xmlns:D="DAV:"
+                                       """ //
+                                       + "xmlns:" + key.getPrefix() + "=\"" + key.getNamespaceURI() + "\">" + //
+                                       """
+                                                               <D:set>
+                                                                       <D:prop>
+                                                       """ //
+                                       + "<" + key.getPrefix() + ":" + key.getLocalPart() + ">" + value + "</" + key.getPrefix() + ":"
+                                       + key.getLocalPart() + ">" + //
+                                       """
+                                                                       </D:prop>
+                                                               </D:set>
+                                                       </D:propertyupdate>
+                                                       """;
+                       System.out.println(body);
+                       HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)) //
+                                       .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) //
+                                       .method(HttpMethod.PROPPATCH.name(), BodyPublishers.ofString(body)) //
+                                       .build();
+                       BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+                       HttpResponse<String> response = httpClient.send(request, bodyHandler);
+                       System.out.println(response.body());
+               } catch (IOException | InterruptedException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       public Iterator<DavResponse> listChildren(URI uri) {
+               try {
+                       String body = """
+                                       <?xml version="1.0" encoding="utf-8" ?>
+                                       <D:propfind xmlns:D="DAV:">
+                                         <D:propname/>
+                                       </D:propfind>""";
+                       HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+                                       .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_1.getValue()) //
+                                       .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) //
+                                       .build();
+
+                       HttpResponse<String> responseStr = httpClient.send(request, BodyHandlers.ofString());
+                       System.out.println(responseStr.body());
+
+                       HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
+                       MultiStatusReader msReader = new MultiStatusReader(response.body(), uri.getPath());
+                       return msReader;
+               } catch (IOException | InterruptedException e) {
+                       throw new IllegalStateException("Cannot list children of " + uri, e);
+               }
+
+       }
+
+       public boolean exists(URI uri) {
+               try {
+                       HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+                                       .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) //
+                                       .method(HttpMethod.HEAD.name(), BodyPublishers.noBody()) //
+                                       .build();
+                       BodyHandler<String> bodyHandler = BodyHandlers.ofString();
+                       HttpResponse<String> response = httpClient.send(request, bodyHandler);
+                       System.out.println(response.body());
+                       int responseStatusCode = response.statusCode();
+                       if (responseStatusCode == HttpStatus.NOT_FOUND.getCode())
+                               return false;
+                       if (responseStatusCode >= 200 && responseStatusCode < 300)
+                               return true;
+                       throw new IllegalStateException(
+                                       "Cannot check whether " + uri + " exists: Unknown response status code " + responseStatusCode);
+               } catch (IOException | InterruptedException e) {
+                       throw new IllegalStateException("Cannot check whether " + uri + " exists", e);
+               }
+
+       }
+
+       public DavResponse get(URI uri) {
+               try {
+                       String body = """
+                                       <?xml version="1.0" encoding="utf-8" ?>
+                                       <D:propfind xmlns:D="DAV:">
+                                         <D:allprop/>
+                                       </D:propfind>""";
+                       HttpRequest request = HttpRequest.newBuilder().uri(uri) //
+                                       .header(HttpHeader.DEPTH.getHeaderName(), DavDepth.DEPTH_0.getValue()) //
+                                       .method(HttpMethod.PROPFIND.name(), BodyPublishers.ofString(body)) //
+                                       .build();
+
+//                     HttpResponse<String> responseStr = httpClient.send(request, BodyHandlers.ofString());
+//                     System.out.println(responseStr.body());
+
+                       HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
+                       MultiStatusReader msReader = new MultiStatusReader(response.body());
+                       if (!msReader.hasNext())
+                               throw new IllegalArgumentException(uri + " does not exist");
+                       return msReader.next();
+               } catch (IOException | InterruptedException e) {
+                       throw new IllegalStateException("Cannot list children of " + uri, e);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               DavClient davClient = new DavClient();
+//             Iterator<DavResponse> responses = davClient
+//                             .listChildren(URI.create("http://localhost/unstable/a2/org.argeo.tp.sdk/"));
+               Iterator<DavResponse> responses = davClient
+                               .listChildren(URI.create("http://root:demo@localhost:7070/api/acr/srv/example"));
+               while (responses.hasNext()) {
+                       DavResponse response = responses.next();
+                       System.out.println(response.getHref() + (response.isCollection() ? " (collection)" : ""));
+                       //System.out.println("  " + response.getPropertyNames(HttpStatus.OK));
+
+               }
+//             davClient.setProperty("http://localhost/unstable/a2/org.argeo.tp.sdk/org.opentest4j.1.2.jar",
+//                             CrName.uuid.qName(), UUID.randomUUID().toString());
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java b/org.argeo.cms/src/org/argeo/cms/dav/DavDepth.java
new file mode 100644 (file)
index 0000000..c7542b5
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.cms.dav;
+
+import org.argeo.cms.http.HttpHeader;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public enum DavDepth {
+       DEPTH_0("0"), DEPTH_1("1"), DEPTH_INFINITY("infinity");
+
+       private final String value;
+
+       private DavDepth(String value) {
+               this.value = value;
+       }
+
+       @Override
+       public String toString() {
+               return getValue();
+       }
+
+       public String getValue() {
+               return value;
+       }
+
+       public static DavDepth fromHttpExchange(HttpExchange httpExchange) {
+               String value = httpExchange.getRequestHeaders().getFirst(HttpHeader.DEPTH.getHeaderName());
+               if (value == null)
+                       return null;
+               DavDepth depth = switch (value) {
+               case "0" -> DEPTH_0;
+               case "1" -> DEPTH_1;
+               case "infinity" -> DEPTH_INFINITY;
+               default -> throw new IllegalArgumentException("Unexpected value: " + value);
+               };
+               return depth;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/dav/DavHttpHandler.java
new file mode 100644 (file)
index 0000000..63f4f82
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+import javax.xml.namespace.NamespaceContext;
+
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpMethod;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.http.server.HttpServerUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * Centralise patterns which are not ACR specific. Not really meant as a
+ * framework for building WebDav servers, but rather to make upper-level of
+ * ACR-specific code more readable and maintainable.
+ */
+public abstract class DavHttpHandler implements HttpHandler {
+
+       @Override
+       public void handle(HttpExchange exchange) throws IOException {
+               String subPath = HttpServerUtils.subPath(exchange);
+               String method = exchange.getRequestMethod();
+               try {
+                       if (HttpMethod.GET.name().equals(method)) {
+                               handleGET(exchange, subPath);
+                       } else if (HttpMethod.OPTIONS.name().equals(method)) {
+                               handleOPTIONS(exchange, subPath);
+                               exchange.sendResponseHeaders(HttpStatus.NO_CONTENT.getCode(), -1);
+                       } else if (HttpMethod.PROPFIND.name().equals(method)) {
+                               DavDepth depth = DavDepth.fromHttpExchange(exchange);
+                               if (depth == null) {
+                                       // default, as per http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
+                                       depth = DavDepth.DEPTH_INFINITY;
+                               }
+                               DavPropfind davPropfind;
+                               try (InputStream in = exchange.getRequestBody()) {
+                                       davPropfind = DavPropfind.load(depth, in);
+                               }
+                               MultiStatusWriter multiStatusWriter = new MultiStatusWriter(exchange.getProtocol());
+                               CompletableFuture<Void> published = handlePROPFIND(exchange, subPath, davPropfind, multiStatusWriter);
+                               exchange.sendResponseHeaders(HttpStatus.MULTI_STATUS.getCode(), 0l);
+                               NamespaceContext namespaceContext = getNamespaceContext(exchange, subPath);
+                               try (OutputStream out = exchange.getResponseBody()) {
+                                       multiStatusWriter.process(namespaceContext, out, published.minimalCompletionStage(),
+                                                       davPropfind.isPropname());
+                               }
+                       } else {
+                               throw new IllegalArgumentException("Unsupported method " + method);
+                       }
+               } catch (ContentNotFoundException e) {
+                       exchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+               }
+               // TODO return a structured error message
+               catch (UnsupportedOperationException e) {
+                       e.printStackTrace();
+                       exchange.sendResponseHeaders(HttpStatus.NOT_IMPLEMENTED.getCode(), -1);
+               } catch (Exception e) {
+                       exchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1);
+               }
+
+       }
+
+       protected abstract NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path);
+
+       protected abstract CompletableFuture<Void> handlePROPFIND(HttpExchange exchange, String path,
+                       DavPropfind davPropfind, Consumer<DavResponse> consumer) throws IOException;
+
+       protected abstract void handleGET(HttpExchange exchange, String path) throws IOException;
+
+       protected void handleOPTIONS(HttpExchange exchange, String path) throws IOException {
+               exchange.getResponseHeaders().set(HttpHeader.DAV.getHeaderName(), "1, 3");
+               StringJoiner methods = new StringJoiner(",");
+               methods.add(HttpMethod.OPTIONS.name());
+               methods.add(HttpMethod.HEAD.name());
+               methods.add(HttpMethod.GET.name());
+               methods.add(HttpMethod.POST.name());
+               methods.add(HttpMethod.PUT.name());
+               methods.add(HttpMethod.PROPFIND.name());
+               // TODO :
+               methods.add(HttpMethod.PROPPATCH.name());
+               methods.add(HttpMethod.MKCOL.name());
+               methods.add(HttpMethod.DELETE.name());
+               methods.add(HttpMethod.MOVE.name());
+               methods.add(HttpMethod.COPY.name());
+
+               exchange.getResponseHeaders().add(HttpHeader.ALLOW.getHeaderName(), methods.toString());
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java b/org.argeo.cms/src/org/argeo/cms/dav/DavPropfind.java
new file mode 100644 (file)
index 0000000..8160544
--- /dev/null
@@ -0,0 +1,92 @@
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+public class DavPropfind {
+       private DavDepth depth;
+       private boolean propname = false;
+       private boolean allprop = false;
+       private List<QName> props = new ArrayList<>();
+
+       public DavPropfind(DavDepth depth) {
+               this.depth = depth;
+       }
+
+       public boolean isPropname() {
+               return propname;
+       }
+
+       public void setPropname(boolean propname) {
+               this.propname = propname;
+       }
+
+       public boolean isAllprop() {
+               return allprop;
+       }
+
+       public void setAllprop(boolean allprop) {
+               this.allprop = allprop;
+       }
+
+       public List<QName> getProps() {
+               return props;
+       }
+
+       public DavDepth getDepth() {
+               return depth;
+       }
+
+       public static DavPropfind load(DavDepth depth, InputStream in) throws IOException {
+               try {
+                       DavPropfind res = null;
+                       XMLInputFactory inputFactory = XMLInputFactory.newFactory();
+                       XMLStreamReader reader = inputFactory.createXMLStreamReader(in);
+                       while (reader.hasNext()) {
+                               reader.next();
+                               if (reader.isStartElement()) {
+                                       QName name = reader.getName();
+//             System.out.println(name);
+                                       DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+                                       if (davXmlElement != null) {
+                                               switch (davXmlElement) {
+                                               case propfind:
+                                                       res = new DavPropfind(depth);
+                                                       break;
+                                               case allprop:
+                                                       res.setAllprop(true);
+                                                       break;
+                                               case propname:
+                                                       res.setPropname(true);
+                                               case prop:
+                                                       // ignore
+                                               case include:
+                                                       // ignore
+                                                       break;
+                                               default:
+                                                       // TODO check that the format is really respected
+                                                       res.getProps().add(reader.getName());
+                                               }
+                                       }
+                               }
+                       }
+
+                       // checks
+                       if (res.isPropname()) {
+                               if (!res.getProps().isEmpty() || res.isAllprop())
+                                       throw new IllegalArgumentException("Cannot set other values if propname is set");
+                       }
+                       return res;
+               } catch (FactoryConfigurationError | XMLStreamException e) {
+                       throw new RuntimeException("Cannot load propfind", e);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java b/org.argeo.cms/src/org/argeo/cms/dav/DavResponse.java
new file mode 100644 (file)
index 0000000..8dd6bf3
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.cms.dav;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.cms.http.HttpStatus;
+
+/** The WebDav response for a given resource. */
+public class DavResponse {
+       final static String MOD_DAV_NAMESPACE = "http://apache.org/dav/props/";
+
+       private String href;
+       private boolean collection;
+       private Map<HttpStatus, Set<QName>> propertyNames = new TreeMap<>();
+       private Map<QName, String> properties = new HashMap<>();
+       private List<QName> resourceTypes = new ArrayList<>();
+
+       public Map<QName, String> getProperties() {
+               return properties;
+       }
+
+       public void setHref(String href) {
+               this.href = href;
+       }
+
+       public String getHref() {
+               return href;
+       }
+
+       public boolean isCollection() {
+               return collection;
+       }
+
+       public void setCollection(boolean collection) {
+               this.collection = collection;
+       }
+
+       public List<QName> getResourceTypes() {
+               return resourceTypes;
+       }
+
+       public Set<QName> getPropertyNames(HttpStatus status) {
+               if (!propertyNames.containsKey(status))
+                       propertyNames.put(status, new TreeSet<>(DavXmlElement.QNAME_COMPARATOR));
+               return propertyNames.get(status);
+       }
+
+       public Set<HttpStatus> getStatuses() {
+               return propertyNames.keySet();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java b/org.argeo.cms/src/org/argeo/cms/dav/DavXmlElement.java
new file mode 100644 (file)
index 0000000..06da679
--- /dev/null
@@ -0,0 +1,102 @@
+package org.argeo.cms.dav;
+
+import java.util.Comparator;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.argeo.api.acr.QNamed;
+
+enum DavXmlElement implements QNamed {
+       response, //
+       multistatus, //
+       href, //
+       /** MUST be the same as DName.collection */
+       collection, //
+       prop, //
+       resourcetype, //
+
+       // propfind
+       propfind, //
+       allprop, //
+       propname, //
+       include, //
+       propstat, //
+       status, //
+
+       // locking
+       lockscope, //
+       locktype, //
+       supportedlock, //
+       lockentry, //
+       lockdiscovery, //
+       write, //
+       shared, //
+       exclusive, //
+       ;
+
+       final static String WEBDAV_NAMESPACE_URI = "DAV:";
+       final static String WEBDAV_DEFAULT_PREFIX = "D";
+
+       final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+
+               @Override
+               public int compare(QName qn1, QName qn2) {
+                       if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+                               return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+                       } else {
+                               return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+                       }
+               }
+
+       };
+
+//     private final QName value;
+//
+//     private DavXmlElement() {
+//             this.value = new ContentName(getNamespace(), localName(), RuntimeNamespaceContext.getNamespaceContext());
+//     }
+//
+//     @Override
+//     public QName qName() {
+//             return value;
+//     }
+
+       @Override
+       public String getNamespace() {
+               return WEBDAV_NAMESPACE_URI;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return WEBDAV_DEFAULT_PREFIX;
+       }
+
+       public static DavXmlElement toEnum(QName name) {
+               for (DavXmlElement e : values()) {
+                       if (e.qName().equals(name))
+                               return e;
+               }
+               return null;
+       }
+
+       public void setSimpleValue(XMLStreamWriter xsWriter, String value) throws XMLStreamException {
+               if (value == null) {
+                       emptyElement(xsWriter);
+                       return;
+               }
+               startElement(xsWriter);
+               xsWriter.writeCharacters(value);
+               xsWriter.writeEndElement();
+       }
+
+       public void emptyElement(XMLStreamWriter xsWriter) throws XMLStreamException {
+               xsWriter.writeEmptyElement(WEBDAV_NAMESPACE_URI, name());
+       }
+
+       public void startElement(XMLStreamWriter xsWriter) throws XMLStreamException {
+               xsWriter.writeStartElement(WEBDAV_NAMESPACE_URI, name());
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusReader.java
new file mode 100644 (file)
index 0000000..c7b54b0
--- /dev/null
@@ -0,0 +1,213 @@
+package org.argeo.cms.dav;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.argeo.cms.http.HttpStatus;
+
+/**
+ * Asynchronously iterate over the response statuses of the response to a
+ * PROPFIND request.
+ */
+class MultiStatusReader implements Iterator<DavResponse> {
+       private CompletableFuture<Boolean> empty = new CompletableFuture<Boolean>();
+       private AtomicBoolean processed = new AtomicBoolean(false);
+
+       private BlockingQueue<DavResponse> queue = new ArrayBlockingQueue<>(64);
+
+       private final String ignoredHref;
+
+       public MultiStatusReader(InputStream in) {
+               this(in, null);
+       }
+
+       /** Typically ignoring self */
+       public MultiStatusReader(InputStream in, String ignoredHref) {
+               this.ignoredHref = ignoredHref;
+               ForkJoinPool.commonPool().execute(() -> process(in));
+       }
+
+       protected void process(InputStream in) {
+               try {
+                       XMLInputFactory inputFactory = XMLInputFactory.newFactory();
+                       XMLStreamReader reader = inputFactory.createXMLStreamReader(in, StandardCharsets.UTF_8.name());
+
+                       DavResponse currentResponse = null;
+                       boolean collectiongProperties = false;
+                       Set<QName> currentPropertyNames = null;
+                       HttpStatus currentStatus = null;
+
+                       final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation
+                       elements: while (reader.hasNext()) {
+                               reader.next();
+                               if (reader.isStartElement()) {
+                                       QName name = reader.getName();
+//                             System.out.println(name);
+                                       DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+                                       if (davXmlElement != null) {
+                                               switch (davXmlElement) {
+                                               case response:
+                                                       currentResponse = new DavResponse();
+                                                       break;
+                                               case href:
+                                                       assert currentResponse != null;
+                                                       while (reader.hasNext() && !reader.hasText())
+                                                               reader.next();
+                                                       String href = reader.getText();
+                                                       currentResponse.setHref(href);
+                                                       break;
+//                                             case collection:
+//                                                     currentResponse.setCollection(true);
+//                                                     break;
+                                               case status:
+                                                       reader.next();
+                                                       String statusLine = reader.getText();
+                                                       currentStatus = HttpStatus.parseStatusLine(statusLine);
+                                                       break;
+                                               case prop:
+                                                       collectiongProperties = true;
+                                                       currentPropertyNames = new HashSet<>();
+                                                       break;
+                                               case resourcetype:
+                                                       while (reader.hasNext()) {
+                                                               int event = reader.nextTag();
+                                                               QName resourceType = reader.getName();
+                                                               if (event == XMLStreamConstants.END_ELEMENT && name.equals(resourceType))
+                                                                       break;
+                                                               assert currentResponse != null;
+                                                               if (event == XMLStreamConstants.START_ELEMENT) {
+                                                                       if (COLLECTION.equals(resourceType))
+                                                                               currentResponse.setCollection(true);
+                                                                       else
+                                                                               currentResponse.getResourceTypes().add(resourceType);
+                                                               }
+                                                       }
+                                                       break;
+                                               default:
+                                                       // ignore
+                                               }
+                                       } else {
+                                               if (collectiongProperties) {
+                                                       String value = null;
+                                                       // TODO deal with complex properties
+                                                       readProperty: while (reader.hasNext()) {
+                                                               reader.next();
+                                                               if (reader.getEventType() == XMLStreamConstants.END_ELEMENT)
+                                                                       break readProperty;
+                                                               if (reader.getEventType() == XMLStreamConstants.CHARACTERS)
+                                                                       value = reader.getText();
+                                                       }
+
+                                                       if (name.getNamespaceURI().equals(DavResponse.MOD_DAV_NAMESPACE))
+                                                               continue elements; // skip mod_dav properties
+
+                                                       assert currentResponse != null;
+                                                       currentPropertyNames.add(name);
+                                                       if (value != null)
+                                                               currentResponse.getProperties().put(name, value);
+
+                                               }
+                                       }
+                               } else if (reader.isEndElement()) {
+                                       QName name = reader.getName();
+//                                     System.out.println(name);
+                                       DavXmlElement davXmlElement = DavXmlElement.toEnum(name);
+                                       if (davXmlElement != null)
+                                               switch (davXmlElement) {
+                                               case propstat:
+                                                       currentResponse.getPropertyNames(currentStatus).addAll(currentPropertyNames);
+                                                       currentPropertyNames = null;
+                                                       break;
+                                               case response:
+                                                       assert currentResponse != null;
+                                                       if (ignoredHref == null || !ignoredHref.equals(currentResponse.getHref())) {
+                                                               if (!empty.isDone())
+                                                                       empty.complete(false);
+                                                               publish(currentResponse);
+                                                       }
+                                               case prop:
+                                                       collectiongProperties = false;
+                                                       break;
+                                               default:
+                                                       // ignore
+                                               }
+                               }
+                       }
+
+                       if (!empty.isDone())
+                               empty.complete(true);
+               } catch (FactoryConfigurationError | XMLStreamException e) {
+                       empty.completeExceptionally(e);
+                       throw new IllegalStateException("Cannot process DAV response", e);
+               } finally {
+                       processed();
+               }
+       }
+
+       protected synchronized void publish(DavResponse response) {
+               try {
+                       queue.put(response);
+               } catch (InterruptedException e) {
+                       throw new IllegalStateException("Cannot put response " + response, e);
+               } finally {
+                       notifyAll();
+               }
+       }
+
+       protected synchronized void processed() {
+               processed.set(true);
+               notifyAll();
+       }
+
+       @Override
+       public synchronized boolean hasNext() {
+               try {
+                       if (empty.get())
+                               return false;
+                       while (!processed.get() && queue.isEmpty()) {
+                               wait();
+                       }
+                       if (!queue.isEmpty())
+                               return true;
+                       if (processed.get())
+                               return false;
+                       throw new IllegalStateException("Cannot determine hasNext");
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot determine hasNext", e);
+               } finally {
+                       // notifyAll();
+               }
+       }
+
+       @Override
+       public synchronized DavResponse next() {
+               try {
+                       if (!hasNext())
+                               throw new IllegalStateException("No fursther items are available");
+
+                       DavResponse response = queue.take();
+                       return response;
+               } catch (InterruptedException e) {
+                       throw new IllegalStateException("Cannot get next", e);
+               } finally {
+                       // notifyAll();
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java b/org.argeo.cms/src/org/argeo/cms/dav/MultiStatusWriter.java
new file mode 100644 (file)
index 0000000..4689b8c
--- /dev/null
@@ -0,0 +1,167 @@
+package org.argeo.cms.dav;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.argeo.cms.http.HttpStatus;
+
+class MultiStatusWriter implements Consumer<DavResponse> {
+       private BlockingQueue<DavResponse> queue = new ArrayBlockingQueue<>(64);
+
+//     private OutputStream out;
+
+       private Thread processingThread;
+
+       private AtomicBoolean done = new AtomicBoolean(false);
+
+       private AtomicBoolean polling = new AtomicBoolean();
+
+       private String protocol;
+
+       public MultiStatusWriter(String protocol) {
+               this.protocol = protocol;
+       }
+
+       public void process(NamespaceContext namespaceContext, OutputStream out, CompletionStage<Void> published,
+                       boolean propname) throws IOException {
+               published.thenRun(() -> allPublished());
+               processingThread = Thread.currentThread();
+//             this.out = out;
+
+               try {
+                       XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
+                       XMLStreamWriter xsWriter = xmlOutputFactory.createXMLStreamWriter(out, StandardCharsets.UTF_8.name());
+                       xsWriter.setNamespaceContext(namespaceContext);
+                       xsWriter.setDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI);
+
+                       xsWriter.writeStartDocument();
+                       DavXmlElement.multistatus.startElement(xsWriter);
+                       xsWriter.writeDefaultNamespace(DavXmlElement.WEBDAV_NAMESPACE_URI);
+
+                       poll: while (!(done.get() && queue.isEmpty())) {
+                               DavResponse davResponse;
+                               try {
+                                       polling.set(true);
+                                       davResponse = queue.poll(10, TimeUnit.MILLISECONDS);
+                                       if (davResponse == null)
+                                               continue poll;
+                                       //System.err.println(davResponse.getHref());
+                               } catch (InterruptedException e) {
+                                       //System.err.println(e);
+                                       continue poll;
+                               } finally {
+                                       polling.set(false);
+                               }
+
+                               writeDavResponse(xsWriter, davResponse, propname);
+                       }
+
+                       xsWriter.writeEndElement();// multistatus
+                       xsWriter.writeEndDocument();
+                       xsWriter.close();
+                       out.close();
+               } catch (FactoryConfigurationError | XMLStreamException e) {
+                       synchronized (this) {
+                               processingThread = null;
+                       }
+               }
+       }
+
+       protected void writeDavResponse(XMLStreamWriter xsWriter, DavResponse davResponse, boolean propname)
+                       throws XMLStreamException {
+               Set<String> namespaces = new HashSet<>();
+               for (HttpStatus status : davResponse.getStatuses())
+                       for (QName key : davResponse.getPropertyNames(status)) {
+                               if (key.getNamespaceURI().equals(DavXmlElement.WEBDAV_NAMESPACE_URI))
+                                       continue; // skip
+                               if (key.getNamespaceURI().equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
+                                       continue; // skip
+                               namespaces.add(key.getNamespaceURI());
+                       }
+               DavXmlElement.response.startElement(xsWriter);
+               // namespaces
+               for (String ns : namespaces)
+                       xsWriter.writeNamespace(xsWriter.getNamespaceContext().getPrefix(ns), ns);
+
+               DavXmlElement.href.setSimpleValue(xsWriter, davResponse.getHref());
+
+               {
+                       for (HttpStatus status : davResponse.getStatuses()) {
+                               DavXmlElement.propstat.startElement(xsWriter);
+                               {
+                                       DavXmlElement.prop.startElement(xsWriter);
+
+                                       // resourcetype
+                                       if (HttpStatus.OK.equals(status))
+                                               if (propname) {
+                                                       DavXmlElement.resourcetype.emptyElement(xsWriter);
+                                               } else {
+                                                       if (!davResponse.getResourceTypes().isEmpty() || davResponse.isCollection()) {
+                                                               DavXmlElement.resourcetype.startElement(xsWriter);
+                                                               if (davResponse.isCollection())
+                                                                       DavXmlElement.collection.emptyElement(xsWriter);
+                                                               for (QName resourceType : davResponse.getResourceTypes()) {
+                                                                       xsWriter.writeEmptyElement(resourceType.getNamespaceURI(),
+                                                                                       resourceType.getLocalPart());
+                                                               }
+                                                               xsWriter.writeEndElement();// resource type
+                                                       }
+                                               }
+
+                                       properties: for (QName key : davResponse.getPropertyNames(status)) {
+                                               if (DavXmlElement.resourcetype.qName().equals(key))
+                                                       continue properties;
+
+                                               if (propname) {
+                                                       xsWriter.writeEmptyElement(key.getNamespaceURI(), key.getLocalPart());
+                                               } else {
+                                                       xsWriter.writeStartElement(key.getNamespaceURI(), key.getLocalPart());
+                                                       xsWriter.writeCData(davResponse.getProperties().get(key));
+                                                       xsWriter.writeEndElement();
+                                               }
+                                       }
+                                       xsWriter.writeEndElement();// prop
+                               }
+                               DavXmlElement.status.setSimpleValue(xsWriter, status.getStatusLine(protocol));
+                               xsWriter.writeEndElement();// propstat
+                       }
+               }
+               xsWriter.writeEndElement();// response
+       }
+
+       @Override
+       public void accept(DavResponse davResponse) {
+               try {
+                       queue.put(davResponse);
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       protected synchronized void allPublished() {
+               done.set(true);
+               if (processingThread != null && queue.isEmpty() && polling.get()) {
+                       // we only interrupt if the queue is already processed
+                       // so as not to interrupt I/O
+                       processingThread.interrupt();
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java
new file mode 100644 (file)
index 0000000..5dffcb6
--- /dev/null
@@ -0,0 +1,582 @@
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.cms.directory.ldap.LdapNameUtils.toLdapName;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.transaction.xa.XAResource;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.CmsDirectory;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkingCopyXaResource;
+import org.argeo.api.cms.transaction.XAResourceProvider;
+import org.argeo.cms.osgi.useradmin.OsUserDirectory;
+import org.argeo.cms.runtime.DirectoryConf;
+
+/** A {@link CmsDirectory} based either on LDAP or LDIF. */
+public abstract class AbstractLdapDirectory implements CmsDirectory, XAResourceProvider {
+       protected static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
+       protected static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
+
+       private final LdapName baseDn;
+       private final Hashtable<String, Object> configProperties;
+       private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn;
+       private final String userObjectClass, groupObjectClass;
+       private String memberAttributeId = "member";
+
+       private final boolean readOnly;
+       private final boolean disabled;
+       private final String uri;
+
+       private String forcedPassword;
+
+       private final boolean scoped;
+
+       private List<String> credentialAttributeIds = Arrays
+                       .asList(new String[] { LdapAttr.userPassword.name(), LdapAttr.authPassword.name() });
+
+       private WorkControl transactionControl;
+       private WorkingCopyXaResource<LdapEntryWorkingCopy> xaResource;
+
+       private LdapDirectoryDao directoryDao;
+
+       /** Whether the the directory has is authenticated via a service user. */
+       private boolean authenticated = false;
+
+       public AbstractLdapDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+               this.configProperties = new Hashtable<String, Object>();
+               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                       String key = keys.nextElement();
+                       configProperties.put(key, props.get(key));
+               }
+
+               String baseDnStr = DirectoryConf.baseDn.getValue(configProperties);
+               if (baseDnStr == null)
+                       throw new IllegalArgumentException("Base DN must be specified: " + configProperties);
+               baseDn = toLdapName(baseDnStr);
+               this.scoped = scoped;
+
+               if (uriArg != null) {
+                       uri = uriArg.toString();
+                       // uri from properties is ignored
+               } else {
+                       String uriStr = DirectoryConf.uri.getValue(configProperties);
+                       if (uriStr == null)
+                               uri = null;
+                       else
+                               uri = uriStr;
+               }
+
+               forcedPassword = DirectoryConf.forcedPassword.getValue(configProperties);
+
+               userObjectClass = DirectoryConf.userObjectClass.getValue(configProperties);
+               groupObjectClass = DirectoryConf.groupObjectClass.getValue(configProperties);
+
+               String userBase = DirectoryConf.userBase.getValue(configProperties);
+               String groupBase = DirectoryConf.groupBase.getValue(configProperties);
+               String systemRoleBase = DirectoryConf.systemRoleBase.getValue(configProperties);
+               try {
+//                     baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
+                       userBaseRdn = new Rdn(userBase);
+//                     userBaseDn = new LdapName(userBase + "," + baseDn);
+                       groupBaseRdn = new Rdn(groupBase);
+//                     groupBaseDn = new LdapName(groupBase + "," + baseDn);
+                       systemRoleBaseRdn = new Rdn(systemRoleBase);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException(
+                                       "Badly formated base DN " + DirectoryConf.baseDn.getValue(configProperties), e);
+               }
+
+               // read only
+               String readOnlyStr = DirectoryConf.readOnly.getValue(configProperties);
+               if (readOnlyStr == null) {
+                       readOnly = readOnlyDefault(uri);
+                       configProperties.put(DirectoryConf.readOnly.name(), Boolean.toString(readOnly));
+               } else
+                       readOnly = Boolean.parseBoolean(readOnlyStr);
+
+               // disabled
+               String disabledStr = DirectoryConf.disabled.getValue(configProperties);
+               if (disabledStr != null)
+                       disabled = Boolean.parseBoolean(disabledStr);
+               else
+                       disabled = false;
+               if (!getRealm().isEmpty()) {
+                       // IPA multiple LDAP causes URI parsing to fail
+                       // TODO manage generic redundant LDAP case
+                       directoryDao = new LdapDao(this);
+               } else {
+                       if (uri != null) {
+                               URI u = URI.create(uri);
+                               if (DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+                                               || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+                                       directoryDao = new LdapDao(this);
+                                       authenticated = configProperties.get(Context.SECURITY_PRINCIPAL) != null;
+                               } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+                                       directoryDao = new LdifDao(this);
+                                       authenticated = true;
+                               } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+                                       directoryDao = new OsUserDirectory(this);
+                                       authenticated = true;
+                                       // singleUser = true;
+                               } else {
+                                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+                               }
+                       } else {
+                               // in memory
+                               directoryDao = new LdifDao(this);
+                       }
+               }
+               if (directoryDao != null)
+                       xaResource = new WorkingCopyXaResource<>(directoryDao);
+       }
+
+       /*
+        * INITIALISATION
+        */
+
+       public void init() {
+               getDirectoryDao().init();
+       }
+
+       public void destroy() {
+               getDirectoryDao().destroy();
+       }
+
+       /*
+        * CREATION
+        */
+       protected abstract LdapEntry newUser(LdapName name);
+
+       protected abstract LdapEntry newGroup(LdapName name);
+
+       /*
+        * EDITION
+        */
+
+       public boolean isEditing() {
+               return xaResource.wc() != null;
+       }
+
+       public LdapEntryWorkingCopy getWorkingCopy() {
+               LdapEntryWorkingCopy wc = xaResource.wc();
+               if (wc == null)
+                       return null;
+               return wc;
+       }
+
+       public void checkEdit() {
+               if (xaResource.wc() == null) {
+                       try {
+                               transactionControl.getWorkContext().registerXAResource(xaResource, null);
+                       } catch (Exception e) {
+                               throw new IllegalStateException("Cannot enlist " + xaResource, e);
+                       }
+               } else {
+               }
+       }
+
+       public void setTransactionControl(WorkControl transactionControl) {
+               this.transactionControl = transactionControl;
+       }
+
+       public XAResource getXaResource() {
+               return xaResource;
+       }
+
+       public boolean removeEntry(LdapName dn) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               boolean actuallyDeleted;
+               if (getDirectoryDao().entryExists(dn) || wc.getNewData().containsKey(dn)) {
+                       LdapEntry user = doGetRole(dn);
+                       wc.getDeletedData().put(dn, user);
+                       actuallyDeleted = true;
+               } else {// just removing from groups (e.g. system roles)
+                       actuallyDeleted = false;
+               }
+               for (LdapName groupDn : getDirectoryDao().getDirectGroups(dn)) {
+                       LdapEntry group = doGetRole(groupDn);
+                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
+               }
+               return actuallyDeleted;
+       }
+
+       /*
+        * RETRIEVAL
+        */
+
+       protected LdapEntry doGetRole(LdapName dn) {
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapEntry user;
+               try {
+                       user = getDirectoryDao().doGetEntry(dn);
+               } catch (NameNotFoundException e) {
+                       user = null;
+               }
+               if (wc != null) {
+                       if (user == null && wc.getNewData().containsKey(dn))
+                               user = wc.getNewData().get(dn);
+                       else if (wc.getDeletedData().containsKey(dn))
+                               user = null;
+               }
+               return user;
+       }
+
+       protected void collectGroups(LdapEntry user, List<LdapEntry> allRoles) {
+               Attributes attrs = user.getAttributes();
+               // TODO centralize attribute name
+               Attribute memberOf = attrs.get(LdapAttr.memberOf.name());
+               // if user belongs to this directory, we only check memberOf
+               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
+                       try {
+                               NamingEnumeration<?> values = memberOf.getAll();
+                               while (values.hasMore()) {
+                                       Object value = values.next();
+                                       LdapName groupDn = new LdapName(value.toString());
+                                       LdapEntry group = doGetRole(groupDn);
+                                       if (group != null) {
+                                               allRoles.add(group);
+                                       } else {
+                                               // user doesn't have the right to retrieve role, but we know it exists
+                                               // otherwise memberOf would not work
+                                               group = newGroup(groupDn);
+                                               allRoles.add(group);
+                                       }
+                               }
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot get memberOf groups for " + user, e);
+                       }
+               } else {
+                       directGroups: for (LdapName groupDn : getDirectoryDao().getDirectGroups(user.getDn())) {
+                               LdapEntry group = doGetRole(groupDn);
+                               if (group != null) {
+                                       if (allRoles.contains(group)) {
+                                               // important in order to avoi loops
+                                               continue directGroups;
+                                       }
+                                       allRoles.add(group);
+                                       collectGroups(group, allRoles);
+                               }
+                       }
+               }
+       }
+
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public HierarchyUnit getHierarchyUnit(String path) {
+               LdapName dn = pathToName(path);
+               return directoryDao.doGetHierarchyUnit(dn);
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+               return directoryDao.doGetDirectHierarchyUnits(baseDn, functionalOnly);
+       }
+
+       @Override
+       public HierarchyUnit getDirectChild(Type type) {
+               // TODO factorise with hierarchy unit?
+               return switch (type) {
+               case ROLES -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getSystemRoleBaseRdn()));
+               case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getUserBaseRdn()));
+               case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getBaseDn().add(getGroupBaseRdn()));
+               case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type");
+               };
+       }
+
+       @Override
+       public String getHierarchyUnitName() {
+               return getName();
+       }
+
+       @Override
+       public String getHierarchyUnitLabel(Locale locale) {
+               String key = LdapNameUtils.getLastRdn(getBaseDn()).getType();
+               Object value = LdapEntry.getLocalized(asLdapEntry().getProperties(), key, locale);
+               if (value == null)
+                       value = getHierarchyUnitName();
+               assert value != null;
+               return value.toString();
+       }
+
+       @Override
+       public HierarchyUnit getParent() {
+               return null;
+       }
+
+       @Override
+       public boolean isType(Type type) {
+               return Type.FUNCTIONAL.equals(type);
+       }
+
+       @Override
+       public CmsDirectory getDirectory() {
+               return this;
+       }
+
+       @Override
+       public HierarchyUnit createHierarchyUnit(String path) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = pathToName(path);
+               if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
+                               || wc.getNewData().containsKey(dn))
+                       throw new IllegalArgumentException("Already a hierarchy unit " + path);
+               BasicAttributes attrs = new BasicAttributes(true);
+               attrs.put(LdapAttr.objectClass.name(), LdapObj.organizationalUnit.name());
+               Rdn nameRdn = dn.getRdn(dn.size() - 1);
+               // TODO deal with multiple attr RDN
+               attrs.put(nameRdn.getType(), nameRdn.getValue());
+               wc.getModifiedData().put(dn, attrs);
+               LdapHierarchyUnit newHierarchyUnit = new LdapHierarchyUnit(this, dn);
+               wc.getNewData().put(dn, newHierarchyUnit);
+               return newHierarchyUnit;
+       }
+
+       /*
+        * PATHS
+        */
+
+       @Override
+       public String getBase() {
+               return getBaseDn().toString();
+       }
+
+       @Override
+       public String getName() {
+               return nameToSimple(getBaseDn(), ".");
+       }
+
+       protected String nameToRelativePath(LdapName dn) {
+               LdapName name = LdapNameUtils.relativeName(getBaseDn(), dn);
+               return nameToSimple(name, "/");
+       }
+
+       protected String nameToSimple(LdapName name, String separator) {
+               StringJoiner path = new StringJoiner(separator);
+               for (int i = 0; i < name.size(); i++) {
+                       path.add(name.getRdn(i).getValue().toString());
+               }
+               return path.toString();
+
+       }
+
+       protected LdapName pathToName(String path) {
+               try {
+                       LdapName name = (LdapName) getBaseDn().clone();
+                       String[] segments = path.split("/");
+                       Rdn parentRdn = null;
+                       // segments[0] is the directory itself
+                       for (int i = 0; i < segments.length; i++) {
+                               String segment = segments[i];
+                               // TODO make attr names configurable ?
+                               String attr = getDirectory().getRealm().isPresent()/* IPA */ ? LdapAttr.cn.name() : LdapAttr.ou.name();
+                               if (parentRdn != null) {
+                                       if (getUserBaseRdn().equals(parentRdn))
+                                               attr = LdapAttr.uid.name();
+                                       else if (getGroupBaseRdn().equals(parentRdn))
+                                               attr = LdapAttr.cn.name();
+                                       else if (getSystemRoleBaseRdn().equals(parentRdn))
+                                               attr = LdapAttr.cn.name();
+                               }
+                               Rdn rdn = new Rdn(attr, segment);
+                               name.add(rdn);
+                               parentRdn = rdn;
+                       }
+                       return name;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot convert " + path + " to LDAP name", e);
+               }
+
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isExternal(LdapName name) {
+               return !name.startsWith(baseDn);
+       }
+
+       protected static boolean hasObjectClass(Attributes attrs, LdapObj objectClass) {
+               return hasObjectClass(attrs, objectClass.name());
+       }
+
+       protected static boolean hasObjectClass(Attributes attrs, String objectClass) {
+               try {
+                       Attribute attr = attrs.get(LdapAttr.objectClass.name());
+                       NamingEnumeration<?> en = attr.getAll();
+                       while (en.hasMore()) {
+                               String v = en.next().toString();
+                               if (v.equalsIgnoreCase(objectClass))
+                                       return true;
+
+                       }
+                       return false;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot search for objectClass " + objectClass, e);
+               }
+       }
+
+       private static boolean readOnlyDefault(String uriStr) {
+               if (uriStr == null)
+                       return true;
+               /// TODO make it more generic
+               URI uri;
+               try {
+                       uri = new URI(uriStr.split(" ")[0]);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException(e);
+               }
+               if (uri.getScheme() == null)
+                       return false;// assume relative file to be writable
+               if (uri.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+                       File file = new File(uri);
+                       if (file.exists())
+                               return !file.canWrite();
+                       else
+                               return !file.getParentFile().canWrite();
+               } else if (uri.getScheme().equals(DirectoryConf.SCHEME_LDAP)) {
+                       if (uri.getAuthority() != null)// assume writable if authenticated
+                               return false;
+               } else if (uri.getScheme().equals(DirectoryConf.SCHEME_OS)) {
+                       return true;
+               }
+               return true;// read only by default
+       }
+
+       /*
+        * AS AN ENTRY
+        */
+       public LdapEntry asLdapEntry() {
+               try {
+                       return directoryDao.doGetEntry(baseDn);
+               } catch (NameNotFoundException e) {
+                       throw new IllegalStateException("Cannot get " + baseDn + " entry", e);
+               }
+       }
+
+       public Dictionary<String, Object> getProperties() {
+               return asLdapEntry().getProperties();
+       }
+
+       /*
+        * ACCESSORS
+        */
+       @Override
+       public Optional<String> getRealm() {
+               Object realm = configProperties.get(DirectoryConf.realm.name());
+               if (realm == null)
+                       return Optional.empty();
+               return Optional.of(realm.toString());
+       }
+
+       public LdapName getBaseDn() {
+               return (LdapName) baseDn.clone();
+       }
+
+       public boolean isReadOnly() {
+               return readOnly;
+       }
+
+       public boolean isDisabled() {
+               return disabled;
+       }
+
+       public boolean isAuthenticated() {
+               return authenticated;
+       }
+
+       public Rdn getUserBaseRdn() {
+               return userBaseRdn;
+       }
+
+       public Rdn getGroupBaseRdn() {
+               return groupBaseRdn;
+       }
+
+       public Rdn getSystemRoleBaseRdn() {
+               return systemRoleBaseRdn;
+       }
+
+//     public Dictionary<String, Object> getConfigProperties() {
+//             return configProperties;
+//     }
+
+       public Dictionary<String, Object> cloneConfigProperties() {
+               return new Hashtable<>(configProperties);
+       }
+
+       public String getForcedPassword() {
+               return forcedPassword;
+       }
+
+       public boolean isScoped() {
+               return scoped;
+       }
+
+       public List<String> getCredentialAttributeIds() {
+               return credentialAttributeIds;
+       }
+
+       public String getUri() {
+               return uri;
+       }
+
+       public LdapDirectoryDao getDirectoryDao() {
+               return directoryDao;
+       }
+
+       /** dn can be null, in that case a default should be returned. */
+       public String getUserObjectClass() {
+               return userObjectClass;
+       }
+
+       public String getGroupObjectClass() {
+               return groupObjectClass;
+       }
+
+       public String getMemberAttributeId() {
+               return memberAttributeId;
+       }
+
+       /*
+        * OBJECT METHODS
+        */
+
+       @Override
+       public int hashCode() {
+               return baseDn.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "Directory " + baseDn.toString();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectoryDao.java
new file mode 100644 (file)
index 0000000..c4a6910
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.ldap.LdapName;
+
+/** Base class for LDAP/LDIF directory DAOs. */
+public abstract class AbstractLdapDirectoryDao implements LdapDirectoryDao {
+
+       private AbstractLdapDirectory directory;
+
+       public AbstractLdapDirectoryDao(AbstractLdapDirectory directory) {
+               this.directory = directory;
+       }
+
+       public AbstractLdapDirectory getDirectory() {
+               return directory;
+       }
+
+       @Override
+       public LdapEntryWorkingCopy newWorkingCopy() {
+               return new LdapEntryWorkingCopy();
+       }
+
+       @Override
+       public LdapEntry newUser(LdapName name) {
+               return getDirectory().newUser(name);
+       }
+
+       @Override
+       public LdapEntry newGroup(LdapName name) {
+               return getDirectory().newGroup(name);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AttributesDictionary.java
new file mode 100644 (file)
index 0000000..9deda1b
--- /dev/null
@@ -0,0 +1,171 @@
+package org.argeo.cms.directory.ldap;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+public class AttributesDictionary extends Dictionary<String, Object> {
+       private final Attributes attributes;
+
+       /** The provided attributes is wrapped, not copied. */
+       public AttributesDictionary(Attributes attributes) {
+               if (attributes == null)
+                       throw new IllegalArgumentException("Attributes cannot be null");
+               this.attributes = attributes;
+       }
+
+       @Override
+       public int size() {
+               return attributes.size();
+       }
+
+       @Override
+       public boolean isEmpty() {
+               return attributes.size() == 0;
+       }
+
+       @Override
+       public Enumeration<String> keys() {
+               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+               return new Enumeration<String>() {
+
+                       @Override
+                       public boolean hasMoreElements() {
+                               return namingEnumeration.hasMoreElements();
+                       }
+
+                       @Override
+                       public String nextElement() {
+                               return namingEnumeration.nextElement();
+                       }
+
+               };
+       }
+
+       @Override
+       public Enumeration<Object> elements() {
+               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+               return new Enumeration<Object>() {
+
+                       @Override
+                       public boolean hasMoreElements() {
+                               return namingEnumeration.hasMoreElements();
+                       }
+
+                       @Override
+                       public Object nextElement() {
+                               String key = namingEnumeration.nextElement();
+                               return get(key);
+                       }
+
+               };
+       }
+
+       @Override
+       /** @returns a <code>String</code> or <code>String[]</code> */
+       public Object get(Object key) {
+               try {
+                       if (key == null)
+                               throw new IllegalArgumentException("Key cannot be null");
+                       Attribute attr = attributes.get(key.toString());
+                       if (attr == null)
+                               return null;
+                       if (attr.size() == 0)
+                               throw new IllegalStateException("There must be at least one value");
+                       else if (attr.size() == 1) {
+                               return attr.get().toString();
+                       } else {// multiple
+                               String[] res = new String[attr.size()];
+                               for (int i = 0; i < attr.size(); i++) {
+                                       Object value = attr.get();
+                                       if (value == null)
+                                               throw new RuntimeException("Values cannot be null");
+                                       res[i] = attr.get(i).toString();
+                               }
+                               return res;
+                       }
+               } catch (NamingException e) {
+                       throw new RuntimeException("Cannot get value for " + key, e);
+               }
+       }
+
+       @Override
+       public Object put(String key, Object value) {
+               if (key == null)
+                       throw new IllegalArgumentException("Key cannot be null");
+               if (value == null)
+                       throw new IllegalArgumentException("Value cannot be null");
+
+               Object oldValue = get(key);
+               Attribute attr = attributes.get(key);
+               if (attr == null) {
+                       attr = new BasicAttribute(key);
+                       attributes.put(attr);
+               }
+
+               if (value instanceof String[]) {
+                       String[] values = (String[]) value;
+                       // clean additional values
+                       for (int i = values.length; i < attr.size(); i++)
+                               attr.remove(i);
+                       // set values
+                       for (int i = 0; i < values.length; i++) {
+                               attr.set(i, values[i]);
+                       }
+               } else {
+                       if (attr.size() > 1)
+                               throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
+                       if (attr.size() == 1) {
+                               try {
+                                       if (!attr.get(0).equals(value))
+                                               attr.set(0, value.toString());
+                               } catch (NamingException e) {
+                                       throw new RuntimeException("Cannot check existing value", e);
+                               }
+                       } else {
+                               attr.add(value.toString());
+                       }
+               }
+               return oldValue;
+       }
+
+       @Override
+       public Object remove(Object key) {
+               if (key == null)
+                       throw new IllegalArgumentException("Key cannot be null");
+               Object oldValue = get(key);
+               if (oldValue == null)
+                       return null;
+               return attributes.remove(key.toString());
+       }
+
+       /**
+        * Copy the <b>content</b> of an {@link Attributes} to the provided
+        * {@link Dictionary}.
+        */
+       public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
+               AttributesDictionary ad = new AttributesDictionary(attributes);
+               Enumeration<String> keys = ad.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       dictionary.put(key, ad.get(key));
+               }
+       }
+
+       /**
+        * Copy a {@link Dictionary} into an {@link Attributes}.
+        */
+       public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
+               AttributesDictionary ad = new AttributesDictionary(attributes);
+               Enumeration<String> keys = dictionary.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       ad.put(key, dictionary.get(key));
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/AuthPassword.java
new file mode 100644 (file)
index 0000000..a871912
--- /dev/null
@@ -0,0 +1,140 @@
+package org.argeo.cms.directory.ldap;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** LDAP authPassword field according to RFC 3112 */
+public class AuthPassword implements CallbackHandler {
+       private final String authScheme;
+       private final String authInfo;
+       private final String authValue;
+
+       public AuthPassword(String value) {
+               StringTokenizer st = new StringTokenizer(value, "$");
+               // TODO make it more robust, deal with bad formatting
+               this.authScheme = st.nextToken().trim();
+               this.authInfo = st.nextToken().trim();
+               this.authValue = st.nextToken().trim();
+
+               String expectedAuthScheme = getExpectedAuthScheme();
+               if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+                       throw new IllegalArgumentException(
+                                       "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+       }
+
+       protected AuthPassword(String authInfo, String authValue) {
+               this.authScheme = getExpectedAuthScheme();
+               if (authScheme == null)
+                       throw new IllegalArgumentException("Expected auth scheme cannot be null");
+               this.authInfo = authInfo;
+               this.authValue = authValue;
+       }
+
+       protected AuthPassword(AuthPassword authPassword) {
+               this.authScheme = authPassword.getAuthScheme();
+               this.authInfo = authPassword.getAuthInfo();
+               this.authValue = authPassword.getAuthValue();
+       }
+
+       protected String getExpectedAuthScheme() {
+               return null;
+       }
+
+       protected boolean matchAuthValue(Object object) {
+               return authValue.equals(object.toString());
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof AuthPassword))
+                       return false;
+               AuthPassword authPassword = (AuthPassword) obj;
+               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+                               && authValue.equals(authValue);
+       }
+
+       public boolean keyEquals(AuthPassword authPassword) {
+               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+       }
+
+       @Override
+       public int hashCode() {
+               return authValue.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return toAuthPassword();
+       }
+
+       public final String toAuthPassword() {
+               return getAuthScheme() + '$' + authInfo + '$' + authValue;
+       }
+
+       public String getAuthScheme() {
+               return authScheme;
+       }
+
+       public String getAuthInfo() {
+               return authInfo;
+       }
+
+       public String getAuthValue() {
+               return authValue;
+       }
+
+       public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+               try {
+                       Attribute authPassword = attributes.get(LdapAttr.authPassword.name());
+                       if (authPassword != null) {
+                               NamingEnumeration<?> values = authPassword.getAll();
+                               while (values.hasMore()) {
+                                       Object val = values.next();
+                                       AuthPassword token = new AuthPassword(val.toString());
+                                       String auth;
+                                       if (Arrays.binarySearch(value, '$') >= 0) {
+                                               auth = token.authInfo + '$' + token.authValue;
+                                       } else {
+                                               auth = token.authValue;
+                                       }
+                                       if (Arrays.equals(auth.toCharArray(), value))
+                                               return token;
+                                       // if (token.matchAuthValue(value))
+                                       // return token;
+                               }
+                       }
+                       return null;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot check attribute", e);
+               }
+       }
+
+       public static boolean remove(Attributes attributes, AuthPassword value) {
+               Attribute authPassword = attributes.get(LdapAttr.authPassword.name());
+               return authPassword.remove(value.toAuthPassword());
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks) {
+                       if (callback instanceof NameCallback)
+                               ((NameCallback) callback).setName(toAuthPassword());
+                       else if (callback instanceof PasswordCallback)
+                               ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/DefaultLdapEntry.java
new file mode 100644 (file)
index 0000000..94e0ac4
--- /dev/null
@@ -0,0 +1,482 @@
+package org.argeo.cms.directory.ldap;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+
+/** An entry in an LDAP (or LDIF) directory. */
+public class DefaultLdapEntry implements LdapEntry {
+       private final AbstractLdapDirectory directory;
+
+       private final LdapName dn;
+
+       private AttributeDictionary properties;
+       private AttributeDictionary credentials;
+
+//     private String primaryObjectClass;
+//     private List<String> objectClasses = new ArrayList<>();
+
+       protected DefaultLdapEntry(AbstractLdapDirectory directory, LdapName dn) {
+               Objects.requireNonNull(directory);
+               Objects.requireNonNull(dn);
+               this.directory = directory;
+               this.dn = dn;
+
+               // Object classes
+//             Objects.requireNonNull(initialAttributes);
+//             try {
+//                     NamingEnumeration<?> en = initialAttributes.get(LdapAttrs.objectClass.name()).getAll();
+//                     String first = null;
+//                     attrs: while (en.hasMore()) {
+//                             String v = en.next().toString();
+//                             if (v.equalsIgnoreCase(LdapObjs.top.name()))
+//                                     continue attrs;
+//                             if (first == null)
+//                                     first = v;
+//                             if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+//                                     primaryObjectClass = getDirectory().getUserObjectClass();
+//                             else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+//                                     primaryObjectClass = getDirectory().getGroupObjectClass();
+//                             objectClasses.add(v);
+//                     }
+//                     if (primaryObjectClass == null) {
+//                             if (first == null)
+//                                     throw new IllegalStateException("Could not find primary object class");
+//                             primaryObjectClass = first;
+//                     }
+//             } catch (NamingException e) {
+//                     throw new IllegalStateException("Cannot find object classes", e);
+//             }
+
+       }
+
+       @Override
+       public LdapName getDn() {
+               // always return a copy since LdapName is mutable
+               return (LdapName) dn.clone();
+       }
+
+       public synchronized Attributes getAttributes() {
+               return isEditing() ? getModifiedAttributes() : getDirectory().getDirectoryDao().doGetAttributes(dn);
+       }
+
+       @Override
+       public List<LdapName> getReferences(String attributeId) {
+               Attribute memberAttribute = getAttributes().get(attributeId);
+               if (memberAttribute == null)
+                       return new ArrayList<LdapName>();
+               try {
+                       List<LdapName> roles = new ArrayList<LdapName>();
+                       NamingEnumeration<?> values = memberAttribute.getAll();
+                       while (values.hasMore()) {
+                               LdapName dn = new LdapName(values.next().toString());
+                               roles.add(dn);
+                       }
+                       return roles;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get members", e);
+               }
+
+       }
+
+       /** Should only be called from working copy thread. */
+       protected synchronized Attributes getModifiedAttributes() {
+               assert getWc() != null;
+               return getWc().getModifiedData().get(getDn());
+       }
+
+       protected synchronized boolean isEditing() {
+               return getWc() != null && getModifiedAttributes() != null;
+       }
+
+       private synchronized LdapEntryWorkingCopy getWc() {
+               return directory.getWorkingCopy();
+       }
+
+       protected synchronized void startEditing() {
+//             if (frozen)
+//                     throw new IllegalStateException("Cannot edit frozen view");
+               if (directory.isReadOnly())
+                       throw new IllegalStateException("User directory is read-only");
+               assert getModifiedAttributes() == null;
+               getWc().startEditing(this);
+               // modifiedAttributes = (Attributes) publishedAttributes.clone();
+       }
+
+       public synchronized void publishAttributes(Attributes modifiedAttributes) {
+//             publishedAttributes = modifiedAttributes;
+       }
+
+       /*
+        * PROPERTIES
+        */
+       @Override
+       public Dictionary<String, Object> getProperties() {
+               if (properties == null)
+                       properties = new AttributeDictionary(false);
+               return properties;
+       }
+
+       public Dictionary<String, Object> getCredentials() {
+               if (credentials == null)
+                       credentials = new AttributeDictionary(true);
+               return credentials;
+       }
+
+       /*
+        * CREDENTIALS
+        */
+       @Override
+       public boolean hasCredential(String key, Object value) {
+               if (key == null) {
+                       // TODO check other sources (like PKCS12)
+                       // String pwd = new String((char[]) value);
+                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+                       char[] password = DirectoryDigestUtils.bytesToChars(value);
+
+                       if (getDirectory().getForcedPassword() != null
+                                       && getDirectory().getForcedPassword().equals(new String(password)))
+                               return true;
+
+                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
+                       if (authPassword != null) {
+                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
+                                       SharedSecret onceToken = new SharedSecret(authPassword);
+                                       if (onceToken.isExpired()) {
+                                               // AuthPassword.remove(getAttributes(), onceToken);
+                                               return false;
+                                       } else {
+                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
+                                               return true;
+                                       }
+                                       // TODO delete expired tokens?
+                               } else {
+                                       // TODO implement SHA
+                                       throw new UnsupportedOperationException(
+                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
+                               }
+                       }
+
+                       // Regular password
+//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
+                       if (hasCredential(LdapAttr.userPassword.name(), DirectoryDigestUtils.charsToBytes(password)))
+                               return true;
+                       return false;
+               }
+
+               Object storedValue = getCredentials().get(key);
+               if (storedValue == null || value == null)
+                       return false;
+               if (!(value instanceof String || value instanceof byte[]))
+                       return false;
+               if (storedValue instanceof String && value instanceof String)
+                       return storedValue.equals(value);
+               if (storedValue instanceof byte[] && value instanceof byte[]) {
+                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
+                       String passwordScheme = null;
+                       if (storedBase64.charAt(0) == '{') {
+                               int index = storedBase64.indexOf('}');
+                               if (index > 0) {
+                                       passwordScheme = storedBase64.substring(1, index);
+                                       String storedValueBase64 = storedBase64.substring(index + 1);
+                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
+                                       char[] passwordValue = DirectoryDigestUtils.bytesToChars((byte[]) value);
+                                       byte[] valueBytes;
+                                       if (DirectoryDigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+                                               valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null,
+                                                               null);
+                                       } else if (DirectoryDigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
+                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
+                                               BigInteger iterations = new BigInteger(iterationsArr);
+                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
+                                                               iterationsArr.length + 64);
+                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
+                                                               storedValueBytes.length);
+                                               int keyLengthBits = keyArr.length * 8;
+                                               valueBytes = DirectoryDigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+                                                               iterations.intValue(), keyLengthBits);
+                                       } else {
+                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
+                                       }
+                                       return Arrays.equals(storedValueBytes, valueBytes);
+                               }
+                       }
+               }
+//             if (storedValue instanceof byte[] && value instanceof byte[]) {
+//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
+//             }
+               return false;
+       }
+
+       /** Hash the password */
+       private static byte[] sha1hash(char[] password) {
+               byte[] hashedPassword = ("{SHA}" + Base64.getEncoder()
+                               .encodeToString(DirectoryDigestUtils.sha1(DirectoryDigestUtils.charsToBytes(password))))
+                               .getBytes(StandardCharsets.UTF_8);
+               return hashedPassword;
+       }
+
+       public AbstractLdapDirectory getDirectory() {
+               return directory;
+       }
+
+       public LdapDirectoryDao getDirectoryDao() {
+               return directory.getDirectoryDao();
+       }
+
+       @Override
+       public int hashCode() {
+               return dn.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj instanceof LdapEntry) {
+                       LdapEntry that = (LdapEntry) obj;
+                       return this.dn.equals(that.getDn());
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return dn.toString();
+       }
+
+       private static boolean isAsciiPrintable(String str) {
+               if (str == null) {
+                       return false;
+               }
+               int sz = str.length();
+               for (int i = 0; i < sz; i++) {
+                       if (isAsciiPrintable(str.charAt(i)) == false) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       private static boolean isAsciiPrintable(char ch) {
+               return ch >= 32 && ch < 127;
+       }
+
+       protected class AttributeDictionary extends Dictionary<String, Object> {
+               private final List<String> effectiveKeys = new ArrayList<String>();
+               private final List<String> attrFilter;
+               private final Boolean includeFilter;
+
+               public AttributeDictionary(Boolean credentials) {
+                       this.attrFilter = getDirectory().getCredentialAttributeIds();
+                       this.includeFilter = credentials;
+                       try {
+                               NamingEnumeration<String> ids = getAttributes().getIDs();
+                               while (ids.hasMore()) {
+                                       String id = ids.next();
+                                       if (credentials && attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                                       else if (!credentials && !attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                               }
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot initialise attribute dictionary", e);
+                       }
+                       if (!credentials)
+                               effectiveKeys.add(LdapAttr.objectClasses.name());
+               }
+
+               @Override
+               public int size() {
+                       return effectiveKeys.size();
+               }
+
+               @Override
+               public boolean isEmpty() {
+                       return effectiveKeys.size() == 0;
+               }
+
+               @Override
+               public Enumeration<String> keys() {
+                       return Collections.enumeration(effectiveKeys);
+               }
+
+               @Override
+               public Enumeration<Object> elements() {
+                       final Iterator<String> it = effectiveKeys.iterator();
+                       return new Enumeration<Object>() {
+
+                               @Override
+                               public boolean hasMoreElements() {
+                                       return it.hasNext();
+                               }
+
+                               @Override
+                               public Object nextElement() {
+                                       String key = it.next();
+                                       return get(key);
+                               }
+
+                       };
+               }
+
+               @Override
+               public Object get(Object key) {
+                       try {
+                               Attribute attr = !key.equals(LdapAttr.objectClasses.name()) ? getAttributes().get(key.toString())
+                                               : getAttributes().get(LdapAttr.objectClass.name());
+                               if (attr == null)
+                                       return null;
+                               Object value = attr.get();
+                               if (value instanceof byte[]) {
+                                       if (key.equals(LdapAttr.userPassword.name()))
+                                               // TODO other cases (certificates, images)
+                                               return value;
+                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
+                               }
+                               if (attr.size() == 1)
+                                       return value;
+                               // special case for object class
+                               if (key.equals(LdapAttr.objectClass.name())) {
+                                       // TODO support multiple object classes
+                                       NamingEnumeration<?> en = attr.getAll();
+                                       String first = null;
+                                       attrs: while (en.hasMore()) {
+                                               String v = en.next().toString();
+                                               if (v.equalsIgnoreCase(LdapObj.top.name()))
+                                                       continue attrs;
+                                               if (first == null)
+                                                       first = v;
+                                               if (v.equalsIgnoreCase(getDirectory().getUserObjectClass()))
+                                                       return getDirectory().getUserObjectClass();
+                                               else if (v.equalsIgnoreCase(getDirectory().getGroupObjectClass()))
+                                                       return getDirectory().getGroupObjectClass();
+                                       }
+                                       if (first != null)
+                                               return first;
+                                       throw new IllegalStateException("Cannot find objectClass in " + value);
+                               } else {
+                                       NamingEnumeration<?> en = attr.getAll();
+                                       StringJoiner values = new StringJoiner("\n");
+                                       while (en.hasMore()) {
+                                               String v = en.next().toString();
+                                               values.add(v);
+                                       }
+                                       return values.toString();
+                               }
+//                             else
+//                                     return value;
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object put(String key, Object value) {
+                       Objects.requireNonNull(value, "Value for key " + key + " is null");
+                       try {
+                               if (key == null) {
+                                       // FIXME remove this "feature", a key should be specified
+                                       // TODO persist to other sources (like PKCS12)
+                                       char[] password = DirectoryDigestUtils.bytesToChars(value);
+                                       byte[] hashedPassword = sha1hash(password);
+                                       return put(LdapAttr.userPassword.name(), hashedPassword);
+                               }
+                               if (key.startsWith("X-")) {
+                                       return put(LdapAttr.authPassword.name(), value);
+                               }
+
+                               // start editing
+                               getDirectory().checkEdit();
+                               if (!isEditing())
+                                       startEditing();
+
+                               // object classes special case.
+                               if (key.equals(LdapAttr.objectClasses.name())) {
+                                       Attribute attribute = new BasicAttribute(LdapAttr.objectClass.name());
+                                       String[] objectClasses = value.toString().split("\n");
+                                       for (String objectClass : objectClasses) {
+                                               if (objectClass.trim().equals(""))
+                                                       continue;
+                                               attribute.add(objectClass);
+                                       }
+                                       Attribute previousAttribute = getModifiedAttributes().put(attribute);
+                                       if (previousAttribute != null)
+                                               return previousAttribute.get();
+                                       else
+                                               return null;
+                               }
+
+                               if (!(value instanceof String || value instanceof byte[]))
+                                       throw new IllegalArgumentException("Value must be String or byte[]");
+
+                               if (includeFilter && !attrFilter.contains(key))
+                                       throw new IllegalArgumentException("Key " + key + " not included");
+                               else if (!includeFilter && attrFilter.contains(key))
+                                       throw new IllegalArgumentException("Key " + key + " excluded");
+
+                               Attribute attribute = getModifiedAttributes().get(key.toString());
+                               // if (attribute == null) // block unit tests
+                               attribute = new BasicAttribute(key.toString());
+                               if (value instanceof String && !isAsciiPrintable(((String) value)))
+                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
+                               else
+                                       attribute.add(value);
+                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
+                               if (previousAttribute != null)
+                                       return previousAttribute.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object remove(Object key) {
+                       getDirectory().checkEdit();
+                       if (!isEditing())
+                               startEditing();
+
+                       if (includeFilter && !attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " not included");
+                       else if (!includeFilter && attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " excluded");
+
+                       try {
+                               Attribute attr = getModifiedAttributes().remove(key.toString());
+                               if (attr != null)
+                                       return attr.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new IllegalStateException("Cannot remove attribute " + key, e);
+                       }
+               }
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/IpaUtils.java
new file mode 100644 (file)
index 0000000..b14c090
--- /dev/null
@@ -0,0 +1,150 @@
+package org.argeo.cms.directory.ldap;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.StringJoiner;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.cms.dns.DnsBrowser;
+import org.argeo.cms.runtime.DirectoryConf;
+
+/** Free IPA specific conventions. */
+public class IpaUtils {
+       public final static String IPA_USER_BASE = "cn=users";
+       public final static String IPA_GROUP_BASE = "cn=groups";
+       public final static String IPA_ROLE_BASE = "cn=roles";
+       public final static String IPA_SERVICE_BASE = "cn=services";
+
+       public final static String IPA_ACCOUNTS_BASE = "cn=accounts";
+
+       private final static String KRB_PRINCIPAL_NAME = LdapAttr.krbPrincipalName.name().toLowerCase();
+
+       public final static String IPA_USER_DIRECTORY_CONFIG = DirectoryConf.userBase + "=" + IPA_USER_BASE + "&"
+                       + DirectoryConf.groupBase + "=" + IPA_GROUP_BASE + "&" + DirectoryConf.systemRoleBase + "=" + IPA_ROLE_BASE
+                       + "&" + DirectoryConf.readOnly + "=true";
+
+       @Deprecated
+       static String domainToUserDirectoryConfigPath(String realm) {
+               return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + DirectoryConf.realm.name() + "=" + realm;
+       }
+
+       public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
+               properties.put(DirectoryConf.baseDn.name(), domainToBaseDn(realm));
+               properties.put(DirectoryConf.realm.name(), realm);
+               properties.put(DirectoryConf.userBase.name(), IPA_USER_BASE);
+               properties.put(DirectoryConf.groupBase.name(), IPA_GROUP_BASE);
+               properties.put(DirectoryConf.systemRoleBase.name(), IPA_ROLE_BASE);
+               properties.put(DirectoryConf.readOnly.name(), Boolean.TRUE.toString());
+       }
+
+       public static String domainToBaseDn(String domain) {
+               String[] dcs = domain.split("\\.");
+               StringJoiner sj = new StringJoiner(",");
+               for (int i = 0; i < dcs.length; i++) {
+                       String dc = dcs[i];
+                       sj.add(LdapAttr.dc.name() + '=' + dc.toLowerCase());
+               }
+               return IPA_ACCOUNTS_BASE + ',' + sj.toString();
+       }
+
+       public static LdapName kerberosToDn(String kerberosName) {
+               String[] kname = kerberosName.split("@");
+               String username = kname[0];
+               String baseDn = domainToBaseDn(kname[1]);
+               String dn;
+               if (!username.contains("/"))
+                       dn = LdapAttr.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
+               else
+                       dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
+               try {
+                       return new LdapName(dn);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
+               }
+       }
+
+       private IpaUtils() {
+
+       }
+
+       public static String kerberosDomainFromDns() {
+               String kerberosDomain;
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       // TODO retrieve hostname from CMS config
+                       InetAddress localhost = InetAddress.getLocalHost();
+                       String hostname = localhost.getHostName();
+                       int dotIndex = hostname.indexOf('.');
+                       if (dotIndex <= 0) {
+                               hostname = localhost.getCanonicalHostName();
+                               dotIndex = hostname.indexOf('.');
+                               if (dotIndex <= 0)
+                                       throw new IllegalArgumentException(
+                                                       "Cannot extract DNS zone from hostname " + hostname + " (" + localhost + ")");
+                       }
+                       String dnsZone = hostname.substring(dotIndex + 1);
+                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       return kerberosDomain;
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot determine Kerberos domain from DNS", e);
+               }
+
+       }
+
+       public static Dictionary<String, Object> convertIpaUri(URI uri) {
+               String path = uri.getPath();
+               String kerberosRealm;
+               if (path == null || path.length() <= 1) {
+                       kerberosRealm = kerberosDomainFromDns();
+               } else {
+                       kerberosRealm = path.substring(1);
+               }
+
+               if (kerberosRealm == null)
+                       throw new IllegalStateException("No Kerberos domain available for " + uri);
+               // TODO intergrate CA certificate in truststore
+               // String schemeToUse = SCHEME_LDAPS;
+               String schemeToUse = DirectoryConf.SCHEME_LDAP;
+               List<String> ldapHosts;
+               String ldapHostsStr = uri.getHost();
+               if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+                       try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                               ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
+                                               schemeToUse.equals(DirectoryConf.SCHEME_LDAP) ? true : false);
+                               if (ldapHosts == null || ldapHosts.size() == 0) {
+                                       throw new IllegalStateException("Cannot configure LDAP for IPA " + uri);
+                               } else {
+                                       ldapHostsStr = ldapHosts.get(0);
+                               }
+                       } catch (IOException e) {
+                               throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+                       }
+               } else {
+                       ldapHosts = new ArrayList<>();
+                       ldapHosts.add(ldapHostsStr);
+               }
+
+               StringBuilder uriStr = new StringBuilder();
+               try {
+                       for (String host : ldapHosts) {
+                               URI convertedUri = new URI(schemeToUse + "://" + host + "/");
+                               uriStr.append(convertedUri).append(' ');
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot convert IPA uri " + uri, e);
+               }
+
+               Hashtable<String, Object> res = new Hashtable<>();
+               res.put(DirectoryConf.uri.name(), uriStr.toString());
+               addIpaConfig(kerberosRealm, res);
+               return res;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapConnection.java
new file mode 100644 (file)
index 0000000..efc8cbc
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.directory.ldap;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.transaction.WorkingCopy;
+
+/** A synchronized wrapper for a single {@link InitialLdapContext}. */
+// TODO implement multiple contexts and connection pooling.
+public class LdapConnection {
+       private InitialLdapContext initialLdapContext = null;
+
+       public LdapConnection(String url, Dictionary<String, ?> properties) {
+               try {
+                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
+                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+                       connEnv.put(Context.PROVIDER_URL, url);
+                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttr.userPassword.name());
+                       // use pooling in order to avoid connection timeout
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
+
+                       initialLdapContext = new InitialLdapContext(connEnv, null);
+                       // StartTlsResponse tls = (StartTlsResponse) ctx
+                       // .extendedOperation(new StartTlsRequest());
+                       // tls.negotiate();
+                       Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
+                       if (securityAuthentication != null)
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+                       else
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
+                       Object principal = properties.get(Context.SECURITY_PRINCIPAL);
+                       if (principal != null) {
+                               initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
+                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
+                               if (creds != null) {
+                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
+                               }
+                       }
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot connect to LDAP", e);
+               }
+
+       }
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+               try {
+                       // tls.close();
+                       initialLdapContext.close();
+                       initialLdapContext = null;
+               } catch (NamingException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       protected InitialLdapContext getLdapContext() {
+               return initialLdapContext;
+       }
+
+       protected void reconnect() throws NamingException {
+               initialLdapContext.reconnect(initialLdapContext.getConnectControls());
+       }
+
+       public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
+                       SearchControls searchControls) throws NamingException {
+               NamingEnumeration<SearchResult> results;
+               try {
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               }
+               return results;
+       }
+
+       public synchronized Attributes getAttributes(LdapName name) throws NamingException {
+               try {
+                       return getLdapContext().getAttributes(name);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       return getLdapContext().getAttributes(name);
+               }
+       }
+
+       public synchronized boolean entryExists(LdapName name) throws NamingException {
+               String[] noAttrOID = new String[] { "1.1" };
+               try {
+                       getLdapContext().getAttributes(name, noAttrOID);
+                       return true;
+               } catch (CommunicationException e) {
+                       reconnect();
+                       getLdapContext().getAttributes(name, noAttrOID);
+                       return true;
+               } catch (NameNotFoundException e) {
+                       return false;
+               }
+       }
+
+       public synchronized void prepareChanges(WorkingCopy<?, ?, LdapName> wc) throws NamingException {
+               // make sure connection will work
+               reconnect();
+
+               // delete
+               for (LdapName dn : wc.getDeletedData().keySet()) {
+                       if (!entryExists(dn))
+                               throw new IllegalStateException("User to delete no found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       if (entryExists(dn))
+                               throw new IllegalStateException("User to create found " + dn);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       if (!wc.getNewData().containsKey(dn) && !entryExists(dn))
+                               throw new IllegalStateException("User to modify not found " + dn);
+               }
+
+       }
+
+//     protected boolean entryExists(LdapName dn) throws NamingException {
+//             try {
+//                     return getAttributes(dn).size() != 0;
+//             } catch (NameNotFoundException e) {
+//                     return false;
+//             }
+//     }
+
+       public synchronized void commitChanges(LdapEntryWorkingCopy wc) throws NamingException {
+               // delete
+               for (LdapName dn : wc.getDeletedData().keySet()) {
+                       getLdapContext().destroySubcontext(dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       LdapEntry user = wc.getNewData().get(dn);
+                       getLdapContext().createSubcontext(dn, user.getAttributes());
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+                       getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java
new file mode 100644 (file)
index 0000000..cdc1c9f
--- /dev/null
@@ -0,0 +1,264 @@
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.AuthenticationNotSupportedException;
+import javax.naming.Binding;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+/** A user admin based on a LDAP server. */
+public class LdapDao extends AbstractLdapDirectoryDao {
+       private LdapConnection ldapConnection;
+
+       public LdapDao(AbstractLdapDirectory directory) {
+               super(directory);
+       }
+
+       @Override
+       public void init() {
+               ldapConnection = new LdapConnection(getDirectory().getUri().toString(), getDirectory().cloneConfigProperties());
+       }
+
+       public void destroy() {
+               ldapConnection.destroy();
+       }
+
+       @Override
+       public boolean checkConnection() {
+               try {
+                       return ldapConnection.entryExists(getDirectory().getBaseDn());
+               } catch (NamingException e) {
+                       return false;
+               }
+       }
+
+       @Override
+       public boolean entryExists(LdapName dn) {
+               try {
+                       return ldapConnection.entryExists(dn);
+               } catch (NameNotFoundException e) {
+                       return false;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot check " + dn, e);
+               }
+       }
+
+       @Override
+       public LdapEntry doGetEntry(LdapName name) throws NameNotFoundException {
+//             if (!entryExists(name))
+//                     throw new NameNotFoundException(name + " was not found in " + getDirectory().getBaseDn());
+               try {
+                       Attributes attrs = ldapConnection.getAttributes(name);
+
+                       LdapEntry res;
+                       Rdn technicalRdn = LdapNameUtils.getParentRdn(name);
+                       if (getDirectory().getGroupBaseRdn().equals(technicalRdn)) {
+                               if (attrs.size() == 0) {// exists but not accessible
+                                       attrs = new BasicAttributes();
+                                       attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+                                       attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass());
+                               }
+                               res = newGroup(name);
+                       } else if (getDirectory().getSystemRoleBaseRdn().equals(technicalRdn)) {
+                               if (attrs.size() == 0) {// exists but not accessible
+                                       attrs = new BasicAttributes();
+                                       attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+                                       attrs.put(LdapAttr.objectClass.name(), getDirectory().getGroupObjectClass());
+                               }
+                               res = newGroup(name);
+                       } else if (getDirectory().getUserBaseRdn().equals(technicalRdn)) {
+                               if (attrs.size() == 0) {// exists but not accessible
+                                       attrs = new BasicAttributes();
+                                       attrs.put(LdapAttr.objectClass.name(), LdapObj.top.name());
+                                       attrs.put(LdapAttr.objectClass.name(), getDirectory().getUserObjectClass());
+                               }
+                               res = newUser(name);
+                       } else {
+                               res = new DefaultLdapEntry(getDirectory(), name);
+                       }
+                       return res;
+               } catch (NameNotFoundException e) {
+                       throw e;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot retrieve entry " + name, e);
+               }
+       }
+
+       @Override
+       public Attributes doGetAttributes(LdapName name) {
+               try {
+                       Attributes attrs = ldapConnection.getAttributes(name);
+                       return attrs;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get attributes for " + name);
+               }
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               ArrayList<LdapEntry> res = new ArrayList<>();
+               try {
+                       String searchFilter = f != null ? f.toString()
+                                       : "(|(" + objectClass.name() + "=" + getDirectory().getUserObjectClass() + ")(" + objectClass.name()
+                                                       + "=" + getDirectory().getGroupObjectClass() + "))";
+                       SearchControls searchControls = new SearchControls();
+                       // only attribute needed is objectClass
+                       searchControls.setReturningAttributes(new String[] { objectClass.name() });
+                       // FIXME make one level consistent with deep
+                       searchControls.setSearchScope(deep ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
+
+                       // LdapName searchBase = getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       results: while (results.hasMoreElements()) {
+                               SearchResult searchResult = results.next();
+                               Attributes attrs = searchResult.getAttributes();
+                               Attribute objectClassAttr = attrs.get(objectClass.name());
+                               LdapName dn = toDn(searchBase, searchResult);
+                               LdapEntry role;
+                               if (objectClassAttr.contains(getDirectory().getGroupObjectClass())
+                                               || objectClassAttr.contains(getDirectory().getGroupObjectClass().toLowerCase()))
+                                       role = newGroup(dn);
+                               else if (objectClassAttr.contains(getDirectory().getUserObjectClass())
+                                               || objectClassAttr.contains(getDirectory().getUserObjectClass().toLowerCase()))
+                                       role = newUser(dn);
+                               else {
+//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
+                                       continue results;
+                               }
+                               res.add(role);
+                       }
+                       return res;
+               } catch (AuthenticationNotSupportedException e) {
+                       // ignore (typically an unsupported anonymous bind)
+                       // TODO better logging
+                       return res;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get roles for filter " + f, e);
+               }
+       }
+
+       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
+               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               try {
+                       String searchFilter = "(&(" + objectClass + "=" + getDirectory().getGroupObjectClass() + ")("
+                                       + getDirectory().getMemberAttributeId() + "=" + dn + "))";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+                       LdapName searchBase = getDirectory().getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       while (results.hasMoreElements()) {
+                               SearchResult searchResult = (SearchResult) results.nextElement();
+                               directGroups.add(toDn(searchBase, searchResult));
+                       }
+                       return directGroups;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot populate direct members of " + dn, e);
+               }
+       }
+
+       @Override
+       public void prepare(LdapEntryWorkingCopy wc) {
+               try {
+                       ldapConnection.prepareChanges(wc);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot prepare LDAP", e);
+               }
+       }
+
+       @Override
+       public void commit(LdapEntryWorkingCopy wc) {
+               try {
+                       ldapConnection.commitChanges(wc);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot commit LDAP", e);
+               }
+       }
+
+       @Override
+       public void rollback(LdapEntryWorkingCopy wc) {
+               // prepare not impacting
+       }
+
+       /*
+        * HIERARCHY
+        */
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               List<HierarchyUnit> res = new ArrayList<>();
+               try {
+                       String structuralFilter = functionalOnly ? ""
+                                       : "(" + getDirectory().getUserBaseRdn() + ")(" + getDirectory().getGroupBaseRdn() + ")("
+                                                       + getDirectory().getSystemRoleBaseRdn() + ")";
+                       String searchFilter = "(|(" + objectClass + "=" + LdapObj.organizationalUnit.name() + ")(" + objectClass
+                                       + "=" + LdapObj.organization.name() + ")" + structuralFilter + ")";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+                       // no attributes needed
+                       searchControls.setReturningAttributes(new String[0]);
+
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       while (results.hasMoreElements()) {
+                               SearchResult searchResult = (SearchResult) results.nextElement();
+                               LdapName dn = toDn(searchBase, searchResult);
+//                             Attributes attrs = searchResult.getAttributes();
+                               LdapHierarchyUnit hierarchyUnit = new LdapHierarchyUnit(getDirectory(), dn);
+                               if (functionalOnly) {
+                                       if (hierarchyUnit.isFunctional())
+                                               res.add(hierarchyUnit);
+                               } else {
+                                       res.add(hierarchyUnit);
+                               }
+                       }
+                       return res;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get direct hierarchy units ", e);
+               }
+       }
+
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               try {
+                       if (getDirectory().getBaseDn().equals(dn))
+                               return getDirectory();
+                       if (!dn.startsWith(getDirectory().getBaseDn()))
+                               throw new IllegalArgumentException(dn + " does not start with base DN " + getDirectory().getBaseDn());
+                       if (!ldapConnection.entryExists(dn))
+                               return null;
+                       return new LdapHierarchyUnit(getDirectory(), dn);
+               } catch (NameNotFoundException e) {
+                       return null;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get hierarchy unit " + dn, e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDirectoryDao.java
new file mode 100644 (file)
index 0000000..03b03ea
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.cms.directory.ldap;
+
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.transaction.WorkingCopyProcessor;
+
+/** Low-level access to an LDAP/LDIF directory. */
+public interface LdapDirectoryDao extends WorkingCopyProcessor<LdapEntryWorkingCopy> {
+       boolean checkConnection();
+
+       boolean entryExists(LdapName dn);
+
+       LdapEntry doGetEntry(LdapName name) throws NameNotFoundException;
+
+       Attributes doGetAttributes(LdapName name);
+
+       List<LdapEntry> doGetEntries(LdapName searchBase, String filter, boolean deep);
+
+       List<LdapName> getDirectGroups(LdapName dn);
+
+       Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly);
+
+       HierarchyUnit doGetHierarchyUnit(LdapName dn);
+
+       LdapEntry newUser(LdapName name);
+
+       LdapEntry newGroup(LdapName name);
+
+       void init();
+
+       void destroy();
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntry.java
new file mode 100644 (file)
index 0000000..fa95c96
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.directory.ldap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.TreeSet;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** An LDAP entry. */
+public interface LdapEntry {
+       LdapName getDn();
+
+       Attributes getAttributes();
+
+       void publishAttributes(Attributes modifiedAttributes);
+
+       List<LdapName> getReferences(String attributeId);
+
+       Dictionary<String, Object> getProperties();
+
+       boolean hasCredential(String key, Object value);
+
+       /*
+        * UTILITIES
+        */
+       /**
+        * Convert a collection of object classes to the format expected by an LDAP
+        * backend.
+        */
+       public static void addObjectClasses(Dictionary<String, Object> properties, Collection<String> objectClasses) {
+               String value = properties.get(LdapAttr.objectClasses.name()).toString();
+               Set<String> currentObjectClasses = new TreeSet<>(Arrays.asList(value.toString().split("\n")));
+               currentObjectClasses.addAll(objectClasses);
+               StringJoiner values = new StringJoiner("\n");
+               currentObjectClasses.forEach((s) -> values.add(s));
+               properties.put(LdapAttr.objectClasses.name(), values.toString());
+       }
+
+       public static Object getLocalized(Dictionary<String, Object> properties, String key, Locale locale) {
+               if (locale == null)
+                       return null;
+               Object value = null;
+               value = properties.get(key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry());
+               if (value == null)
+                       value = properties.get(key + ";lang-" + locale.getLanguage());
+               return value;
+       }
+
+       public static String toLocalizedKey(String key, Locale locale) {
+               String country = locale.getCountry();
+               if ("".equals(country)) {
+                       return key + ";lang-" + locale.getLanguage();
+               } else {
+                       return key + ";lang-" + locale.getLanguage() + "-" + locale.getCountry();
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapEntryWorkingCopy.java
new file mode 100644 (file)
index 0000000..58e565a
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.transaction.AbstractWorkingCopy;
+
+/** Working copy for a user directory being edited. */
+public class LdapEntryWorkingCopy extends AbstractWorkingCopy<LdapEntry, Attributes, LdapName> {
+       @Override
+       protected LdapName getId(LdapEntry entry) {
+               return (LdapName) entry.getDn().clone();
+       }
+
+       @Override
+       protected Attributes cloneAttributes(LdapEntry entry) {
+               return (Attributes) entry.getAttributes().clone();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java
new file mode 100644 (file)
index 0000000..b60ee0c
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.directory.ldap;
+
+import java.util.Locale;
+
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.cms.directory.HierarchyUnit;
+
+/** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
+public class LdapHierarchyUnit extends DefaultLdapEntry implements HierarchyUnit {
+//     private final boolean functional;
+
+       private final Type type;
+
+       public LdapHierarchyUnit(AbstractLdapDirectory directory, LdapName dn) {
+               super(directory, dn);
+
+               Rdn rdn = LdapNameUtils.getLastRdn(dn);
+               if (directory.getUserBaseRdn().equals(rdn))
+                       type = Type.PEOPLE;
+               else if (directory.getGroupBaseRdn().equals(rdn))
+                       type = Type.GROUPS;
+               else if (directory.getSystemRoleBaseRdn().equals(rdn))
+                       type = Type.ROLES;
+               else
+                       type = Type.FUNCTIONAL;
+//             functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)
+//                             || directory.getSystemRoleBaseRdn().equals(rdn));
+       }
+
+       @Override
+       public HierarchyUnit getParent() {
+               return getDirectoryDao().doGetHierarchyUnit(LdapNameUtils.getParent(getDn()));
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> getDirectHierarchyUnits(boolean functionalOnly) {
+               return getDirectoryDao().doGetDirectHierarchyUnits(getDn(), functionalOnly);
+       }
+
+       @Override
+       public HierarchyUnit getDirectChild(Type type) {
+               return switch (type) {
+               case ROLES ->
+                       getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getSystemRoleBaseRdn()));
+               case PEOPLE -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getUserBaseRdn()));
+               case GROUPS -> getDirectoryDao().doGetHierarchyUnit((LdapName) getDn().add(getDirectory().getGroupBaseRdn()));
+               case FUNCTIONAL -> throw new IllegalArgumentException("Type must be a technical type");
+               };
+       }
+
+       @Override
+       public boolean isType(Type type) {
+               return this.type.equals(type);
+       }
+
+       @Override
+       public String getHierarchyUnitName() {
+               String name = LdapNameUtils.getLastRdnValue(getDn());
+               // TODO check ou, o, etc.
+               return name;
+       }
+
+       @Override
+       public String getHierarchyUnitLabel(Locale locale) {
+               String key = LdapNameUtils.getLastRdn(getDn()).getType();
+               Object value = LdapEntry.getLocalized(getProperties(), key, locale);
+               if (value == null)
+                       value = getHierarchyUnitName();
+               assert value != null;
+               return value.toString();
+       }
+
+       @Override
+       public String getBase() {
+               return getDn().toString();
+       }
+
+       @Override
+       public String toString() {
+               return "Hierarchy Unit " + getDn().toString();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java
new file mode 100644 (file)
index 0000000..74f23da
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.cms.directory.ldap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+       public static LdapName relativeName(LdapName prefix, LdapName dn) {
+               try {
+                       if (!dn.startsWith(prefix))
+                               throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+                       LdapName res = (LdapName) dn.clone();
+                       for (int i = 0; i < prefix.size(); i++) {
+                               res.remove(0);
+                       }
+                       return res;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot find realtive name", e);
+               }
+       }
+
+       public static LdapName getParent(LdapName dn) {
+               try {
+                       LdapName parent = (LdapName) dn.clone();
+                       parent.remove(parent.size() - 1);
+                       return parent;
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+               }
+       }
+
+       public static Rdn getParentRdn(LdapName dn) {
+               if (dn.size() < 2)
+                       throw new IllegalArgumentException(dn + " has no parent");
+               Rdn parentRdn = dn.getRdn(dn.size() - 2);
+               return parentRdn;
+       }
+
+       public static LdapName toLdapName(String distinguishedName) {
+               try {
+                       return new LdapName(distinguishedName);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+               }
+       }
+
+       public static Rdn getLastRdn(LdapName dn) {
+               return dn.getRdn(dn.size() - 1);
+       }
+
+       public static String getLastRdnAsString(LdapName dn) {
+               return getLastRdn(dn).toString();
+       }
+
+       public static String getLastRdnValue(String dn) {
+               return getLastRdnValue(toLdapName(dn));
+       }
+
+       public static String getLastRdnValue(LdapName dn) {
+               return getLastRdn(dn).getValue().toString();
+       }
+
+       /** singleton */
+       private LdapNameUtils() {
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifDao.java
new file mode 100644 (file)
index 0000000..52148df
--- /dev/null
@@ -0,0 +1,306 @@
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapObj;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Role;
+
+/** A user admin based on a LDIF files. */
+public class LdifDao extends AbstractLdapDirectoryDao {
+       private NavigableMap<LdapName, LdapEntry> entries = new TreeMap<>();
+       private NavigableMap<LdapName, LdapHierarchyUnit> hierarchy = new TreeMap<>();
+
+       private NavigableMap<LdapName, Attributes> values = new TreeMap<>();
+
+       public LdifDao(AbstractLdapDirectory directory) {
+               super(directory);
+       }
+
+       public void init() {
+               String uri = getDirectory().getUri();
+               if (uri == null)
+                       return;
+               try {
+                       URI u = new URI(uri);
+                       if (u.getScheme().equals("file")) {
+                               File file = new File(u);
+                               if (!file.exists())
+                                       return;
+                       }
+                       load(u.toURL().openStream());
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot open URL " + getDirectory().getUri(), e);
+               }
+       }
+
+       public void save() {
+               if (getDirectory().getUri() == null)
+                       return; // ignore
+               if (getDirectory().isReadOnly())
+                       throw new IllegalStateException(
+                                       "Cannot save LDIF user admin: " + getDirectory().getUri() + " is read-only");
+               try (FileOutputStream out = new FileOutputStream(new File(new URI(getDirectory().getUri())))) {
+                       save(out);
+               } catch (IOException | URISyntaxException e) {
+                       throw new IllegalStateException("Cannot save user admin to " + getDirectory().getUri(), e);
+               }
+       }
+
+       public void save(OutputStream out) throws IOException {
+               try {
+                       LdifWriter ldifWriter = new LdifWriter(out);
+                       for (LdapName name : hierarchy.keySet())
+                               ldifWriter.writeEntry(name, hierarchy.get(name).getAttributes());
+                       for (LdapName name : entries.keySet())
+                               ldifWriter.writeEntry(name, entries.get(name).getAttributes());
+               } finally {
+                       out.close();
+               }
+       }
+
+       public void load(InputStream in) {
+               try {
+                       entries.clear();
+                       hierarchy.clear();
+
+                       LdifParser ldifParser = new LdifParser();
+                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
+                       for (LdapName key : allEntries.keySet()) {
+                               Attributes attributes = allEntries.get(key);
+                               // check for inconsistency
+                               Set<String> lowerCase = new HashSet<String>();
+                               NamingEnumeration<String> ids = attributes.getIDs();
+                               while (ids.hasMoreElements()) {
+                                       String id = ids.nextElement().toLowerCase();
+                                       if (lowerCase.contains(id))
+                                               throw new IllegalStateException(key + " has duplicate id " + id);
+                                       lowerCase.add(id);
+                               }
+
+                               values.put(key, attributes);
+
+                               // analyse object classes
+                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
+                               // System.out.println(key);
+                               objectClasses: while (objectClasses.hasMore()) {
+                                       String objectClass = objectClasses.next().toString();
+                                       // System.out.println(" " + objectClass);
+                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
+                                               entries.put(key, newUser(key));
+                                               break objectClasses;
+                                       } else if (objectClass.toLowerCase().equals(getDirectory().getGroupObjectClass().toLowerCase())) {
+                                               entries.put(key, newGroup(key));
+                                               break objectClasses;
+                                       } else if (objectClass.equalsIgnoreCase(LdapObj.organizationalUnit.name())) {
+                                               // TODO skip if it does not contain groups or users
+                                               hierarchy.put(key, new LdapHierarchyUnit(getDirectory(), key));
+                                               break objectClasses;
+                                       }
+                               }
+                       }
+
+               } catch (NamingException | IOException e) {
+                       throw new IllegalStateException("Cannot load user admin service from LDIF", e);
+               }
+       }
+
+       public void destroy() {
+//             if (users == null || groups == null)
+               if (entries == null)
+                       throw new IllegalStateException("User directory " + getDirectory().getBaseDn() + " is already destroyed");
+//             users = null;
+//             groups = null;
+               entries = null;
+       }
+
+       /*
+        * USER ADMIN
+        */
+
+       @Override
+       public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
+               if (entries.containsKey(key))
+                       return entries.get(key);
+               throw new NameNotFoundException(key + " not persisted");
+       }
+
+       @Override
+       public Attributes doGetAttributes(LdapName name) {
+               if (!values.containsKey(name))
+                       throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn());
+               return values.get(name);
+       }
+
+       @Override
+       public boolean checkConnection() {
+               return true;
+       }
+
+       @Override
+       public boolean entryExists(LdapName dn) {
+               return entries.containsKey(dn);// || groups.containsKey(dn);
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               Objects.requireNonNull(searchBase);
+               ArrayList<LdapEntry> res = new ArrayList<>();
+               if (f == null && deep && getDirectory().getBaseDn().equals(searchBase)) {
+                       res.addAll(entries.values());
+               } else {
+                       filterRoles(entries, searchBase, f, deep, res);
+               }
+               return res;
+       }
+
+       private void filterRoles(SortedMap<LdapName, ? extends LdapEntry> map, LdapName searchBase, String f, boolean deep,
+                       List<LdapEntry> res) {
+               // FIXME get rid of OSGi references
+               try {
+                       // TODO reduce map with search base ?
+                       Filter filter = f != null ? FrameworkUtil.createFilter(f) : null;
+                       roles: for (LdapEntry user : map.values()) {
+                               LdapName dn = user.getDn();
+                               if (dn.startsWith(searchBase)) {
+                                       if (!deep && dn.size() != (searchBase.size() + 1))
+                                               continue roles;
+                                       if (filter == null)
+                                               res.add(user);
+                                       else {
+                                               if (user instanceof Role) {
+                                                       if (filter.match(((Role) user).getProperties()))
+                                                               res.add(user);
+                                               }
+                                       }
+                               }
+                       }
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot create filter " + f, e);
+               }
+
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               entries: for (LdapName name : entries.keySet()) {
+                       LdapEntry group;
+                       try {
+                               LdapEntry entry = doGetEntry(name);
+                               if (AbstractLdapDirectory.hasObjectClass(entry.getAttributes(), getDirectory().getGroupObjectClass())) {
+                                       group = entry;
+                               } else {
+                                       continue entries;
+                               }
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalArgumentException("Group " + dn + " not found", e);
+                       }
+                       if (group.getReferences(getDirectory().getMemberAttributeId()).contains(dn)) {
+                               directGroups.add(group.getDn());
+                       }
+               }
+               return directGroups;
+       }
+
+       @Override
+       public void prepare(LdapEntryWorkingCopy wc) {
+               // delete
+               for (LdapName dn : wc.getDeletedData().keySet()) {
+                       if (entries.containsKey(dn))
+                               entries.remove(dn);
+                       else
+                               throw new IllegalStateException("User to delete not found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewData().keySet()) {
+                       LdapEntry user = (LdapEntry) wc.getNewData().get(dn);
+                       if (entries.containsKey(dn))
+                               throw new IllegalStateException("User to create found " + dn);
+                       entries.put(dn, user);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedData().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedData().get(dn);
+                       LdapEntry user;
+                       try {
+                               user = doGetEntry(dn);
+                       } catch (NameNotFoundException e) {
+                               throw new IllegalStateException("User to modify no found " + dn, e);
+                       }
+                       if (user == null)
+                               throw new IllegalStateException("User to modify no found " + dn);
+                       user.publishAttributes(modifiedAttrs);
+               }
+       }
+
+       @Override
+       public void commit(LdapEntryWorkingCopy wc) {
+               save();
+       }
+
+       @Override
+       public void rollback(LdapEntryWorkingCopy wc) {
+               init();
+       }
+
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               if (getDirectory().getBaseDn().equals(dn))
+                       return getDirectory();
+               return hierarchy.get(dn);
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               List<HierarchyUnit> res = new ArrayList<>();
+               for (LdapName n : hierarchy.keySet()) {
+                       if (n.size() == searchBase.size() + 1) {
+                               if (n.startsWith(searchBase)) {
+                                       HierarchyUnit hu = hierarchy.get(n);
+                                       if (functionalOnly) {
+                                               if (hu.isFunctional())
+                                                       res.add(hu);
+                                       } else {
+                                               res.add(hu);
+                                       }
+                               }
+                       }
+               }
+               return res;
+       }
+
+       public void scope(LdifDao scoped) {
+               scoped.entries = Collections.unmodifiableNavigableMap(entries);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifParser.java
new file mode 100644 (file)
index 0000000..d0e6b76
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.cms.directory.ldap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+
+/** Basic LDIF parser. */
+public class LdifParser {
+       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+       protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
+                       Attributes currentAttributes) {
+               try {
+                       Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
+                       Attribute nameAttr = currentAttributes.get(nameRdn.getType());
+                       if (nameAttr == null)
+                               currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
+                       else if (!nameAttr.get().equals(nameRdn.getValue()))
+                               throw new IllegalStateException(
+                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
+                                                               + " (shortly before line " + lineNumber + " in LDIF file)");
+                       Attributes previous = res.put(currentDn, currentAttributes);
+                       return previous;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot add " + currentDn, e);
+               }
+       }
+
+       /** With UTF-8 charset */
+       public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
+               try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
+                       return read(reader);
+               } finally {
+                       try {
+                               in.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+               }
+       }
+
+       /** Will close the reader. */
+       public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
+               SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
+               try {
+                       List<String> lines = new ArrayList<>();
+                       try (BufferedReader br = new BufferedReader(reader)) {
+                               String line;
+                               while ((line = br.readLine()) != null) {
+                                       lines.add(line);
+                               }
+                       }
+                       if (lines.size() == 0)
+                               return res;
+                       // add an empty new line since the last line is not checked
+                       if (!lines.get(lines.size() - 1).equals(""))
+                               lines.add("");
+
+                       LdapName currentDn = null;
+                       Attributes currentAttributes = null;
+                       StringBuilder currentEntry = new StringBuilder();
+
+                       readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
+                               String line = lines.get(lineNumber);
+                               boolean isLastLine = false;
+                               if (lineNumber == lines.size() - 1)
+                                       isLastLine = true;
+                               if (line.startsWith(" ")) {
+                                       currentEntry.append(line.substring(1));
+                                       if (!isLastLine)
+                                               continue readLines;
+                               }
+
+                               if (currentEntry.length() != 0 || isLastLine) {
+                                       // read previous attribute
+                                       StringBuilder attrId = new StringBuilder(8);
+                                       boolean isBase64 = false;
+                                       readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
+                                               char c = currentEntry.charAt(i);
+                                               if (c == ':') {
+                                                       if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
+                                                               isBase64 = true;
+                                                       currentEntry.delete(0, i + (isBase64 ? 2 : 1));
+                                                       break readAttrId;
+                                               } else {
+                                                       attrId.append(c);
+                                               }
+                                       }
+
+                                       String attributeId = attrId.toString();
+                                       // TODO should we really trim the end of the string as well?
+                                       String cleanValueStr = currentEntry.toString().trim();
+                                       Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
+
+                                       // manage DN attributes
+                                       if (attributeId.equals(LdapAttr.DN) || isLastLine) {
+                                               if (currentDn != null) {
+                                                       //
+                                                       // ADD
+                                                       //
+                                                       Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
+                                                       if (previous != null) {
+//                                                             log.warn("There was already an entry with DN " + currentDn
+//                                                                             + ", which has been discarded by a subsequent one.");
+                                                       }
+                                               }
+
+                                               if (attributeId.equals(LdapAttr.DN))
+                                                       try {
+                                                               currentDn = new LdapName(attributeValue.toString());
+                                                               currentAttributes = new BasicAttributes(true);
+                                                       } catch (InvalidNameException e) {
+//                                                             log.error(attributeValue + " not a valid DN, skipping the entry.");
+                                                               currentDn = null;
+                                                               currentAttributes = null;
+                                                       }
+                                       }
+
+                                       // store attribute
+                                       if (currentAttributes != null) {
+                                               Attribute attribute = currentAttributes.get(attributeId);
+                                               if (attribute == null) {
+                                                       attribute = new BasicAttribute(attributeId);
+                                                       currentAttributes.put(attribute);
+                                               }
+                                               attribute.add(attributeValue);
+                                       }
+                                       currentEntry = new StringBuilder();
+                               }
+                               currentEntry.append(line);
+                       }
+               } finally {
+                       try {
+                               reader.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+               }
+               return res;
+       }
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdifWriter.java
new file mode 100644 (file)
index 0000000..69a8672
--- /dev/null
@@ -0,0 +1,104 @@
+package org.argeo.cms.directory.ldap;
+
+import static org.argeo.api.acr.ldap.LdapAttr.DN;
+import static org.argeo.api.acr.ldap.LdapAttr.member;
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapAttr.uniqueMember;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Basic LDIF writer */
+public class LdifWriter {
+       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+       private final Writer writer;
+
+       /** Writer must be closed by caller */
+       public LdifWriter(Writer writer) {
+               this.writer = writer;
+       }
+
+       /** Stream must be closed by caller */
+       public LdifWriter(OutputStream out) {
+               this(new OutputStreamWriter(out, DEFAULT_CHARSET));
+       }
+
+       public void writeEntry(LdapName name, Attributes attributes) throws IOException {
+               try {
+                       // check consistency
+                       Rdn nameRdn = name.getRdn(name.size() - 1);
+                       Attribute nameAttr = attributes.get(nameRdn.getType());
+                       if (!nameAttr.get().equals(nameRdn.getValue()))
+                               throw new IllegalArgumentException(
+                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
+
+                       writer.append(DN + ": ").append(name.toString()).append('\n');
+                       Attribute objectClassAttr = attributes.get(objectClass.name());
+                       if (objectClassAttr != null)
+                               writeAttribute(objectClassAttr);
+                       attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+                               Attribute attribute = attrs.next();
+                               if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
+                                       continue attributes;// skip DN attribute
+                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+                                       continue attributes;// skip member and uniqueMember attributes, so that they are always written last
+                               writeAttribute(attribute);
+                       }
+                       // write member and uniqueMember attributes last
+                       for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+                               Attribute attribute = attrs.next();
+                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+                                       writeMemberAttribute(attribute);
+                       }
+                       writer.append('\n');
+                       writer.flush();
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot write LDIF", e);
+               }
+       }
+
+       public void write(Map<LdapName, Attributes> entries) throws IOException {
+               for (LdapName dn : entries.keySet())
+                       writeEntry(dn, entries.get(dn));
+       }
+
+       protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
+               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+                       Object value = attrValues.next();
+                       if (value instanceof byte[]) {
+                               String encoded = Base64.getEncoder().encodeToString((byte[]) value);
+                               writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
+                       } else {
+                               writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
+                       }
+               }
+       }
+
+       protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
+               // Note: duplicate entries will be swallowed
+               SortedSet<String> values = new TreeSet<>();
+               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+                       String value = attrValues.next().toString();
+                       values.add(value);
+               }
+
+               for (String value : values) {
+                       writer.append(attribute.getID()).append(": ").append(value).append('\n');
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/SharedSecret.java
new file mode 100644 (file)
index 0000000..2c52ee1
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.directory.ldap;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+
+public class SharedSecret extends AuthPassword {
+       public final static String X_SHARED_SECRET = "X-SharedSecret";
+       private final Instant expiry;
+
+       public SharedSecret(String authInfo, String authValue) {
+               super(authInfo, authValue);
+               expiry = null;
+       }
+
+       public SharedSecret(AuthPassword authPassword) {
+               super(authPassword);
+               String authInfo = getAuthInfo();
+               if (authInfo.length() == 16) {
+                       expiry = NamingUtils.ldapDateToInstant(authInfo);
+               } else {
+                       expiry = null;
+               }
+       }
+
+       public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
+               super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
+               expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
+       }
+
+       public SharedSecret(int hours, String value) {
+               this(ZonedDateTime.now().plusHours(hours), value);
+       }
+
+       @Override
+       protected String getExpectedAuthScheme() {
+               return X_SHARED_SECRET;
+       }
+
+       public boolean isExpired() {
+               if (expiry == null)
+                       return false;
+               return expiry.isBefore(Instant.now());
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java b/org.argeo.cms/src/org/argeo/cms/dns/DnsBrowser.java
new file mode 100644 (file)
index 0000000..c6b6530
--- /dev/null
@@ -0,0 +1,216 @@
+package org.argeo.cms.dns;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedSet;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+public class DnsBrowser implements Closeable {
+       private final DirContext initialCtx;
+
+       public DnsBrowser() {
+               this(new ArrayList<>());
+       }
+
+       public DnsBrowser(List<String> dnsServerUrls) {
+               try {
+                       Objects.requireNonNull(dnsServerUrls);
+                       Hashtable<String, Object> env = new Hashtable<>();
+                       env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+                       if (!dnsServerUrls.isEmpty()) {
+                               boolean specified = false;
+                               StringJoiner providerUrl = new StringJoiner(" ");
+                               for (String dnsUrl : dnsServerUrls) {
+                                       if (dnsUrl != null) {
+                                               providerUrl.add(dnsUrl);
+                                               specified = true;
+                                       }
+                               }
+                               if (specified)
+                                       env.put(Context.PROVIDER_URL, providerUrl.toString());
+                       }
+                       initialCtx = new InitialDirContext(env);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot initialise DNS borowser.", e);
+               }
+       }
+
+       public Map<String, List<String>> getAllRecords(String name) {
+               try {
+                       Map<String, List<String>> res = new TreeMap<>();
+                       Attributes attrs = initialCtx.getAttributes(name);
+                       NamingEnumeration<String> ids = attrs.getIDs();
+                       while (ids.hasMore()) {
+                               String recordType = ids.next();
+                               List<String> lst = new ArrayList<String>();
+                               res.put(recordType, lst);
+                               Attribute attr = attrs.get(recordType);
+                               addValues(attr, lst);
+                       }
+                       return Collections.unmodifiableMap(res);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get allrecords of " + name, e);
+               }
+       }
+
+       /**
+        * Return a single record (typically A, AAAA, etc. or null if not available.
+        * Will fail if multiple records.
+        */
+       public String getRecord(String name, String recordType) {
+               try {
+                       Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+                       if (attrs.size() == 0)
+                               return null;
+                       Attribute attr = attrs.get(recordType);
+                       if (attr.size() > 1)
+                               throw new IllegalArgumentException("Multiple record type " + recordType);
+                       assert attr.size() != 0;
+                       Object value = attr.get();
+                       assert value != null;
+                       return value.toString();
+               } catch (NameNotFoundException e) {
+                       return null;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get DNS entry " + recordType + " of " + name, e);
+               }
+       }
+
+       /**
+        * Return records of a given type.
+        */
+       public List<String> getRecords(String name, String recordType) {
+               try {
+                       List<String> res = new ArrayList<String>();
+                       Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+                       Attribute attr = attrs.get(recordType);
+                       addValues(attr, res);
+                       return res;
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot get records " + recordType + " of " + name, e);
+               }
+       }
+
+       /** Ordered, with preferred first. */
+       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) {
+               List<String> raw = getRecords(name, "SRV");
+               if (raw.size() == 0)
+                       return null;
+               SortedSet<SrvRecord> res = new TreeSet<>();
+               for (int i = 0; i < raw.size(); i++) {
+                       String record = raw.get(i);
+                       String[] arr = record.split(" ");
+                       Integer priority = Integer.parseInt(arr[0]);
+                       Integer weight = Integer.parseInt(arr[1]);
+                       Integer port = Integer.parseInt(arr[2]);
+                       String hostname = arr[3];
+                       SrvRecord order = new SrvRecord(priority, weight, port, hostname);
+                       res.add(order);
+               }
+               List<String> lst = new ArrayList<>();
+               for (SrvRecord order : res) {
+                       lst.add(order.toHost(withPort));
+               }
+               return Collections.unmodifiableList(lst);
+       }
+
+       private void addValues(Attribute attr, List<String> lst) throws NamingException {
+               NamingEnumeration<?> values = attr.getAll();
+               while (values.hasMore()) {
+                       Object value = values.next();
+                       if (value != null) {
+                               if (value instanceof byte[]) {
+                                       String str = Base64.getEncoder().encodeToString((byte[]) value);
+                                       lst.add(str);
+                               } else
+                                       lst.add(value.toString());
+                       }
+               }
+
+       }
+
+       public List<String> listEntries(String name) {
+               try {
+                       List<String> res = new ArrayList<String>();
+                       NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
+                       while (ne.hasMore()) {
+                               Binding b = ne.next();
+                               res.add(b.getName());
+                       }
+                       return Collections.unmodifiableList(res);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot list entries of " + name, e);
+               }
+       }
+
+       @Override
+       public void close() throws IOException {
+               destroy();
+       }
+
+       public void destroy() {
+               try {
+                       initialCtx.close();
+               } catch (NamingException e) {
+                       // silent
+               }
+       }
+
+       public static void main(String[] args) {
+               if (args.length == 0) {
+                       printUsage(System.err);
+                       System.exit(1);
+               }
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       String hostname = args[0];
+                       String recordType = args.length > 1 ? args[1] : "A";
+                       if (recordType.equals("*")) {
+                               Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
+                               for (String type : records.keySet()) {
+                                       for (String record : records.get(type)) {
+                                               String typeLabel;
+                                               if ("44".equals(type))
+                                                       typeLabel = "SSHFP";
+                                               else if ("46".equals(type))
+                                                       typeLabel = "RRSIG";
+                                               else if ("48".equals(type))
+                                                       typeLabel = "DNSKEY";
+                                               else
+                                                       typeLabel = type;
+                                               System.out.println(typeLabel + "\t" + record);
+                                       }
+                               }
+                       } else {
+                               System.out.println(dnsBrowser.getRecord(hostname, recordType));
+                       }
+
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static void printUsage(PrintStream out) {
+               out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java b/org.argeo.cms/src/org/argeo/cms/dns/SrvRecord.java
new file mode 100644 (file)
index 0000000..bdbdc76
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.dns;
+
+class SrvRecord implements Comparable<SrvRecord> {
+       private final Integer priority;
+       private final Integer weight;
+       private final Integer port;
+       private final String hostname;
+
+       public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
+               this.priority = priority;
+               this.weight = weight;
+               this.port = port;
+               this.hostname = hostname;
+       }
+
+       @Override
+       public int compareTo(SrvRecord other) {
+               // https: // en.wikipedia.org/wiki/SRV_record
+               if (priority != other.priority)
+                       return priority - other.priority;
+               if (weight != other.weight)
+                       return other.weight - other.weight;
+               String host = toHost(false);
+               String otherHost = other.toHost(false);
+               if (host.length() == otherHost.length())
+                       return host.compareTo(otherHost);
+               else
+                       return host.length() - otherHost.length();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof SrvRecord) {
+                       SrvRecord other = (SrvRecord) obj;
+                       return priority == other.priority && weight == other.weight && port == other.port
+                                       && hostname.equals(other.hostname);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return priority + " " + weight;
+       }
+
+       public String toHost(boolean withPort) {
+               String hostStr = hostname;
+               if (hostname.charAt(hostname.length() - 1) == '.')
+                       hostStr = hostname.substring(0, hostname.length() - 1);
+               return hostStr + (withPort ? ":" + port : "");
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java b/org.argeo.cms/src/org/argeo/cms/file/BasicSyncFileVisitor.java
new file mode 100644 (file)
index 0000000..ac81329
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+/** Synchronises two directory structures. */
+public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
+       // TODO make it configurable
+       private boolean trace = false;
+
+       private final Path sourceBasePath;
+       private final Path targetBasePath;
+       private final boolean delete;
+       private final boolean recursive;
+
+       private SyncResult<Path> syncResult = new SyncResult<>();
+
+       public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+               this.sourceBasePath = sourceBasePath;
+               this.targetBasePath = targetBasePath;
+               this.delete = delete;
+               this.recursive = recursive;
+       }
+
+       @Override
+       public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
+               if (!recursive && !sourceDir.equals(sourceBasePath))
+                       return FileVisitResult.SKIP_SUBTREE;
+               Path targetDir = toTargetPath(sourceDir);
+               Files.createDirectories(targetDir);
+               return FileVisitResult.CONTINUE;
+       }
+
+       @Override
+       public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
+               if (delete) {
+                       Path targetDir = toTargetPath(sourceDir);
+                       for (Path targetPath : Files.newDirectoryStream(targetDir)) {
+                               Path sourcePath = sourceDir.resolve(targetPath.getFileName());
+                               if (!Files.exists(sourcePath)) {
+                                       try {
+                                               FsSyncUtils.delete(targetPath);
+                                               deleted(targetPath);
+                                       } catch (Exception e) {
+                                               deleteFailed(targetPath, exc);
+                                       }
+                               }
+                       }
+               }
+               return FileVisitResult.CONTINUE;
+       }
+
+       @Override
+       public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
+               Path targetFile = toTargetPath(sourceFile);
+               try {
+                       if (!Files.exists(targetFile)) {
+                               Files.copy(sourceFile, targetFile);
+                               added(sourceFile, targetFile);
+                       } else {
+                               if (shouldOverwrite(sourceFile, targetFile)) {
+                                       Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+                               }
+                       }
+               } catch (Exception e) {
+                       copyFailed(sourceFile, targetFile, e);
+               }
+               return FileVisitResult.CONTINUE;
+       }
+
+       protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
+               long sourceSize = Files.size(sourceFile);
+               long targetSize = Files.size(targetFile);
+               if (sourceSize != targetSize) {
+                       return true;
+               }
+               FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
+               FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
+               if (sourceLastModif.compareTo(targetLastModif) > 0)
+                       return true;
+               return shouldOverwriteLaterSameSize(sourceFile, targetFile);
+       }
+
+       protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
+               return false;
+       }
+
+//     @Override
+//     public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
+//             error("Cannot sync " + sourceFile, exc);
+//             return FileVisitResult.CONTINUE;
+//     }
+
+       private Path toTargetPath(Path sourcePath) {
+               Path relativePath = sourceBasePath.relativize(sourcePath);
+               Path targetPath = targetBasePath.resolve(relativePath.toString());
+               return targetPath;
+       }
+
+       public Path getSourceBasePath() {
+               return sourceBasePath;
+       }
+
+       public Path getTargetBasePath() {
+               return targetBasePath;
+       }
+
+       protected void added(Path sourcePath, Path targetPath) {
+               syncResult.getAdded().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Added " + sourcePath + " as " + targetPath);
+       }
+
+       protected void modified(Path sourcePath, Path targetPath) {
+               syncResult.getModified().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Overwritten from " + sourcePath + " to " + targetPath);
+       }
+
+       protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
+               syncResult.addError(sourcePath, targetPath, e);
+               if (isTraceEnabled())
+                       error("Cannot copy " + sourcePath + " to " + targetPath, e);
+       }
+
+       protected void deleted(Path targetPath) {
+               syncResult.getDeleted().add(targetPath);
+               if (isTraceEnabled())
+                       trace("Deleted " + targetPath);
+       }
+
+       protected void deleteFailed(Path targetPath, Exception e) {
+               syncResult.addError(null, targetPath, e);
+               if (isTraceEnabled())
+                       error("Cannot delete " + targetPath, e);
+       }
+
+       /** Log error. */
+       protected void error(Object obj, Throwable e) {
+               System.err.println(obj);
+               e.printStackTrace();
+       }
+
+       protected boolean isTraceEnabled() {
+               return trace;
+       }
+
+       protected void trace(Object obj) {
+               System.out.println(obj);
+       }
+
+       public SyncResult<Path> getSyncResult() {
+               return syncResult;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java b/org.argeo.cms/src/org/argeo/cms/file/ChecksumFactory.java
new file mode 100644 (file)
index 0000000..6aea8be
--- /dev/null
@@ -0,0 +1,149 @@
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.zip.Checksum;
+
+/** Allows to fine tune how files are read. */
+public class ChecksumFactory {
+       private int regionSize = 10 * 1024 * 1024;
+
+       public byte[] digest(Path path, final String algo) {
+               try {
+                       final MessageDigest md = MessageDigest.getInstance(algo);
+                       if (Files.isDirectory(path)) {
+                               long begin = System.currentTimeMillis();
+                               Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+
+                                       @Override
+                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                               if (!Files.isDirectory(file)) {
+                                                       byte[] digest = digest(file, algo);
+                                                       md.update(digest);
+                                               }
+                                               return FileVisitResult.CONTINUE;
+                                       }
+
+                               });
+                               byte[] digest = md.digest();
+                               long duration = System.currentTimeMillis() - begin;
+                               System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)");
+                               return digest;
+                       } else {
+                               long begin = System.nanoTime();
+                               long length = -1;
+                               try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
+                                       length = channel.size();
+                                       long cursor = 0;
+                                       while (cursor < length) {
+                                               long effectiveSize = Math.min(regionSize, length - cursor);
+                                               MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
+                                               // md.update(mb);
+                                               byte[] buffer = new byte[1024];
+                                               while (mb.hasRemaining()) {
+                                                       mb.get(buffer);
+                                                       md.update(buffer);
+                                               }
+
+                                               // sub digest
+                                               // mb.flip();
+                                               // MessageDigest subMd =
+                                               // MessageDigest.getInstance(algo);
+                                               // subMd.update(mb);
+                                               // byte[] subDigest = subMd.digest();
+                                               // System.out.println(" -> " + cursor);
+                                               // System.out.println(IOUtils.encodeHexString(subDigest));
+                                               // System.out.println(new BigInteger(1,
+                                               // subDigest).toString(16));
+                                               // System.out.println(new BigInteger(1, subDigest)
+                                               // .toString(Character.MAX_RADIX));
+                                               // System.out.println(printBase64Binary(subDigest));
+
+                                               cursor = cursor + regionSize;
+                                       }
+                                       byte[] digest = md.digest();
+                                       long duration = System.nanoTime() - begin;
+                                       System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000
+                                                       + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024)
+                                                       + " MB/s)");
+                                       return digest;
+                               }
+                       }
+               } catch (NoSuchAlgorithmException | IOException e) {
+                       throw new IllegalStateException("Cannot digest " + path, e);
+               }
+       }
+
+       /** Whether the file should be mapped. */
+       protected boolean mapFile(FileChannel fileChannel) throws IOException {
+               long size = fileChannel.size();
+               if (size > (regionSize / 10))
+                       return true;
+               return false;
+       }
+
+       public long checksum(Path path, Checksum crc) {
+               final int bufferSize = 2 * 1024 * 1024;
+               long begin = System.currentTimeMillis();
+               try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
+                       byte[] bytes = new byte[bufferSize];
+                       long length = channel.size();
+                       long cursor = 0;
+                       while (cursor < length) {
+                               long effectiveSize = Math.min(regionSize, length - cursor);
+                               MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
+                               int nGet;
+                               while (mb.hasRemaining()) {
+                                       nGet = Math.min(mb.remaining(), bufferSize);
+                                       mb.get(bytes, 0, nGet);
+                                       crc.update(bytes, 0, nGet);
+                               }
+                               cursor = cursor + regionSize;
+                       }
+                       return crc.getValue();
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot checksum " + path, e);
+               } finally {
+                       long duration = System.currentTimeMillis() - begin;
+                       System.out.println(duration / 1000 + "s");
+               }
+       }
+
+       public static void main(String... args) {
+               ChecksumFactory cf = new ChecksumFactory();
+               // Path path =
+               // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz");
+               Path path;
+               if (args.length > 0) {
+                       path = Paths.get(args[0]);
+               } else {
+                       path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/"
+                                       + "CentOS-7-x86_64-DVD-1503-01.iso");
+               }
+               // long adler = cf.checksum(path, new Adler32());
+               // System.out.format("Adler=%d%n", adler);
+               // long crc = cf.checksum(path, new CRC32());
+               // System.out.format("CRC=%d%n", crc);
+               String algo = "SHA1";
+               byte[] digest = cf.digest(path, algo);
+               System.out.println(algo + " " + printBase64Binary(digest));
+               System.out.println(algo + " " + new BigInteger(1, digest).toString(16));
+               // String sha1 = printBase64Binary(cf.digest(path, "SHA1"));
+               // System.out.format("SHA1=%s%n", sha1);
+       }
+
+       private static String printBase64Binary(byte[] arr) {
+               return Base64.getEncoder().encodeToString(arr);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java b/org.argeo.cms/src/org/argeo/cms/file/FsSyncUtils.java
new file mode 100644 (file)
index 0000000..68eb5ab
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.cms.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class FsSyncUtils {
+       /** Sync a source path with a target path. */
+       public static void sync(Path sourceBasePath, Path targetBasePath) {
+               sync(sourceBasePath, targetBasePath, false);
+       }
+
+       /** Sync a source path with a target path. */
+       public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
+               sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
+       }
+
+       public static void sync(BasicSyncFileVisitor syncFileVisitor) {
+               try {
+                       Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
+                                       + syncFileVisitor.getTargetBasePath(), e);
+               }
+       }
+
+       /**
+        * Deletes this path, recursively if needed. Does nothing if the path does not
+        * exist.
+        */
+       public static void delete(Path path) {
+               try {
+                       if (!Files.exists(path))
+                               return;
+                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                               @Override
+                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+                                       if (e != null)
+                                               throw e;
+                                       Files.delete(directory);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Files.delete(file);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot delete " + path, e);
+               }
+       }
+
+       /** Singleton. */
+       private FsSyncUtils() {
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/PathSync.java b/org.argeo.cms/src/org/argeo/cms/file/PathSync.java
new file mode 100644 (file)
index 0000000..dc26aa2
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.cms.file;
+
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.concurrent.Callable;
+
+/** Synchronises two paths. */
+public class PathSync implements Callable<SyncResult<Path>> {
+       private final URI sourceUri, targetUri;
+       private final boolean delete;
+       private final boolean recursive;
+
+       public PathSync(URI sourceUri, URI targetUri) {
+               this(sourceUri, targetUri, false, false);
+       }
+
+       public PathSync(URI sourceUri, URI targetUri, boolean delete, boolean recursive) {
+               this.sourceUri = sourceUri;
+               this.targetUri = targetUri;
+               this.delete = delete;
+               this.recursive = recursive;
+       }
+
+       @Override
+       public SyncResult<Path> call() {
+               try {
+                       Path sourceBasePath = createPath(sourceUri);
+                       Path targetBasePath = createPath(targetUri);
+                       SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete, recursive);
+                       Files.walkFileTree(sourceBasePath, syncFileVisitor);
+                       return syncFileVisitor.getSyncResult();
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot sync " + sourceUri + " to " + targetUri, e);
+               }
+       }
+
+       private Path createPath(URI uri) {
+               Path path;
+               if (uri.getScheme() == null) {
+                       path = Paths.get(uri.getPath());
+               } else if (uri.getScheme().equals("file")) {
+                       FileSystemProvider fsProvider = FileSystems.getDefault().provider();
+                       path = fsProvider.getPath(uri);
+               } else if (uri.getScheme().equals("davex")) {
+                       throw new UnsupportedOperationException();
+//                     FileSystemProvider fsProvider = new DavexFsProvider();
+//                     path = fsProvider.getPath(uri);
+//             } else if (uri.getScheme().equals("sftp")) {
+//                     Sftp sftp = new Sftp(uri);
+//                     path = sftp.getBasePath();
+               } else
+                       throw new IllegalArgumentException("URI scheme not supported for " + uri);
+               return path;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java b/org.argeo.cms/src/org/argeo/cms/file/SyncFileVisitor.java
new file mode 100644 (file)
index 0000000..a69d2bf
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.cms.file;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.argeo.api.cms.CmsLog;
+
+/** Synchronises two directory structures. */
+public class SyncFileVisitor extends BasicSyncFileVisitor {
+       private final static CmsLog log = CmsLog.getLog(SyncFileVisitor.class);
+
+       public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+               super(sourceBasePath, targetBasePath, delete, recursive);
+       }
+
+       @Override
+       protected void error(Object obj, Throwable e) {
+               log.error(Objects.toString(obj), e);
+       }
+
+       @Override
+       protected boolean isTraceEnabled() {
+               return log.isTraceEnabled();
+       }
+
+       @Override
+       protected void trace(Object obj) {
+               log.error(Objects.toString(obj));
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/SyncResult.java b/org.argeo.cms/src/org/argeo/cms/file/SyncResult.java
new file mode 100644 (file)
index 0000000..35ab3f9
--- /dev/null
@@ -0,0 +1,101 @@
+package org.argeo.cms.file;
+
+import java.time.Instant;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Describes what happendend during a sync operation. */
+public class SyncResult<T> {
+       private final Set<T> added = new TreeSet<>();
+       private final Set<T> modified = new TreeSet<>();
+       private final Set<T> deleted = new TreeSet<>();
+       private final Set<Error> errors = new TreeSet<>();
+
+       public Set<T> getAdded() {
+               return added;
+       }
+
+       public Set<T> getModified() {
+               return modified;
+       }
+
+       public Set<T> getDeleted() {
+               return deleted;
+       }
+
+       public Set<Error> getErrors() {
+               return errors;
+       }
+
+       public void addError(T sourcePath, T targetPath, Exception e) {
+               Error error = new Error(sourcePath, targetPath, e);
+               errors.add(error);
+       }
+
+       public boolean noModification() {
+               return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
+       }
+
+       @Override
+       public String toString() {
+               if (noModification())
+                       return "No modification.";
+               StringBuffer sb = new StringBuffer();
+               for (T p : modified)
+                       sb.append("MOD ").append(p).append('\n');
+               for (T p : deleted)
+                       sb.append("DEL ").append(p).append('\n');
+               for (T p : added)
+                       sb.append("ADD ").append(p).append('\n');
+               for (Error error : errors)
+                       sb.append(error).append('\n');
+               return sb.toString();
+       }
+
+       public class Error implements Comparable<Error> {
+               private final T sourcePath;// if null this is a failed delete
+               private final T targetPath;
+               private final Exception exception;
+               private final Instant timestamp = Instant.now();
+
+               public Error(T sourcePath, T targetPath, Exception e) {
+                       super();
+                       this.sourcePath = sourcePath;
+                       this.targetPath = targetPath;
+                       this.exception = e;
+               }
+
+               public T getSourcePath() {
+                       return sourcePath;
+               }
+
+               public T getTargetPath() {
+                       return targetPath;
+               }
+
+               public Exception getException() {
+                       return exception;
+               }
+
+               public Instant getTimestamp() {
+                       return timestamp;
+               }
+
+               @Override
+               public int compareTo(Error o) {
+                       return timestamp.compareTo(o.timestamp);
+               }
+
+               @Override
+               public int hashCode() {
+                       return timestamp.hashCode();
+               }
+
+               @Override
+               public String toString() {
+                       return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
+                                       + targetPath + " " + exception.getMessage();
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileStore.java
new file mode 100644 (file)
index 0000000..a4da893
--- /dev/null
@@ -0,0 +1,71 @@
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileStoreAttributeView;
+
+import org.argeo.api.acr.fs.AbstractFsStore;
+
+public class CmsFileStore extends AbstractFsStore {
+
+       @Override
+       public String name() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public String type() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public boolean isReadOnly() {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public long getTotalSpace() throws IOException {
+               // TODO Auto-generated method stub
+               return 0;
+       }
+
+       @Override
+       public long getUsableSpace() throws IOException {
+               // TODO Auto-generated method stub
+               return 0;
+       }
+
+       @Override
+       public long getUnallocatedSpace() throws IOException {
+               // TODO Auto-generated method stub
+               return 0;
+       }
+
+       @Override
+       public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public boolean supportsFileAttributeView(String name) {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public <V extends FileStoreAttributeView> V getFileStoreAttributeView(Class<V> type) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Object getAttribute(String attribute) throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystem.java
new file mode 100644 (file)
index 0000000..6d4eea2
--- /dev/null
@@ -0,0 +1,100 @@
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Set;
+
+import org.argeo.api.acr.fs.AbstractFsSystem;
+
+public class CmsFileSystem extends AbstractFsSystem<CmsFileStore> {
+
+       @Override
+       public CmsFileStore getBaseFileStore() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public CmsFileStore getFileStore(String path) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public FileSystemProvider provider() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public void close() throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public boolean isOpen() {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public boolean isReadOnly() {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public String getSeparator() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Iterable<Path> getRootDirectories() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Iterable<FileStore> getFileStores() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Set<String> supportedFileAttributeViews() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Path getPath(String first, String... more) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public PathMatcher getPathMatcher(String syntaxAndPattern) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public UserPrincipalLookupService getUserPrincipalLookupService() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public WatchService newWatchService() throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsFileSystemProvider.java
new file mode 100644 (file)
index 0000000..51eb84e
--- /dev/null
@@ -0,0 +1,133 @@
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.AccessMode;
+import java.nio.file.CopyOption;
+import java.nio.file.DirectoryStream;
+import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.Map;
+import java.util.Set;
+
+public class CmsFileSystemProvider extends FileSystemProvider {
+
+       @Override
+       public String getScheme() {
+               return "cms";
+       }
+
+       @Override
+       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public FileSystem getFileSystem(URI uri) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Path getPath(URI uri) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)
+                       throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void delete(Path path) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void copy(Path source, Path target, CopyOption... options) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void move(Path source, Path target, CopyOption... options) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public boolean isSameFile(Path path, Path path2) throws IOException {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public boolean isHidden(Path path) throws IOException {
+               // TODO Auto-generated method stub
+               return false;
+       }
+
+       @Override
+       public FileStore getFileStore(Path path) throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public void checkAccess(Path path, AccessMode... modes) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
+                       throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Map<String, Object> readAttributes(Path path, String attributes, LinkOption... options) throws IOException {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException {
+               // TODO Auto-generated method stub
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java b/org.argeo.cms/src/org/argeo/cms/file/provider/CmsPath.java
new file mode 100644 (file)
index 0000000..504e69b
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.file.provider;
+
+import org.argeo.api.acr.fs.AbstractFsPath;
+
+public class CmsPath extends AbstractFsPath<CmsFileSystem, CmsFileStore> {
+
+       public CmsPath(CmsFileSystem filesSystem, CmsFileStore fileStore, String[] segments, boolean absolute) {
+               super(filesSystem, fileStore, segments, absolute);
+       }
+
+       public CmsPath(CmsFileSystem filesSystem, String path) {
+               super(filesSystem, path);
+       }
+
+       @Override
+       protected AbstractFsPath<CmsFileSystem, CmsFileStore> newInstance(String path) {
+               return new CmsPath(getFileSystem(), path);
+       }
+
+       @Override
+       protected AbstractFsPath<CmsFileSystem, CmsFileStore> newInstance(String[] segments, boolean absolute) {
+               return new CmsPath(getFileSystem(), getFileStore(), segments, absolute);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java b/org.argeo.cms/src/org/argeo/cms/http/HttpHeader.java
new file mode 100644 (file)
index 0000000..ef7385d
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.cms.http;
+
+/** Selection of standard or common HTTP headers (including WebDav). */
+public enum HttpHeader {
+       AUTHORIZATION("Authorization"), //
+       WWW_AUTHENTICATE("WWW-Authenticate"), //
+       ALLOW("Allow"), //
+       VIA("Via"), //
+
+       // WebDav
+       DAV("DAV"), //
+       DEPTH("Depth"), //
+
+       // Non-standard
+       X_FORWARDED_HOST("X-Forwarded-Host"), //
+       ;
+
+       public final static String BASIC = "Basic";
+       public final static String REALM = "realm";
+       public final static String NEGOTIATE = "Negotiate";
+
+       private final String name;
+
+       private HttpHeader(String headerName) {
+               this.name = headerName;
+       }
+
+       public String getHeaderName() {
+               return name;
+       }
+
+       @Override
+       public String toString() {
+               return getHeaderName();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java b/org.argeo.cms/src/org/argeo/cms/http/HttpMethod.java
new file mode 100644 (file)
index 0000000..7869045
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.http;
+
+/** Generic HTTP methods. */
+public enum HttpMethod {
+       OPTIONS, //
+       HEAD, //
+       GET, //
+       POST, //
+       PUT, //
+       DELETE, //
+
+       // WebDav
+       PROPFIND, //
+       PROPPATCH, //
+       MKCOL, //
+       MOVE, //
+       COPY, //
+       ;
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java b/org.argeo.cms/src/org/argeo/cms/http/HttpStatus.java
new file mode 100644 (file)
index 0000000..3b9a47a
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.cms.http;
+
+/**
+ * Standard HTTP response status codes (including WebDav ones).
+ * 
+ * @see "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status"
+ */
+public enum HttpStatus {
+       // Successful responses (200–299)
+       OK(200, "OK"), //
+       NO_CONTENT(204, "No Content"), //
+       MULTI_STATUS(207, "Multi-Status"), // WebDav
+       // Client error responses (400–499)
+       UNAUTHORIZED(401, "Unauthorized"), //
+       FORBIDDEN(403, "Forbidden"), //
+       NOT_FOUND(404, "Not Found"), //
+       // Server error responses (500-599)
+       INTERNAL_SERVER_ERROR(500, "Internal Server Error"), //
+       NOT_IMPLEMENTED(501, "Not Implemented"), //
+       ;
+
+       private final int code;
+       private final String reasonPhrase;
+
+       HttpStatus(int statusCode, String reasonPhrase) {
+               this.code = statusCode;
+               this.reasonPhrase = reasonPhrase;
+       }
+
+       public int getCode() {
+               return code;
+       }
+
+       public String getReasonPhrase() {
+               return reasonPhrase;
+       }
+
+       /**
+        * The status line, as defined by RFC2616.
+        * 
+        * @see "https://www.rfc-editor.org/rfc/rfc2616#section-6.1"
+        */
+       public String getStatusLine(String httpVersion) {
+               return httpVersion + " " + code + " " + reasonPhrase;
+       }
+
+       public static HttpStatus parseStatusLine(String statusLine) {
+               try {
+                       String[] arr = statusLine.split(" ");
+                       int code = Integer.parseInt(arr[1]);
+                       for (HttpStatus status : values()) {
+                               if (status.getCode() == code)
+                                       return status;
+                       }
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Invalid status line: " + statusLine, e);
+               }
+               throw new IllegalArgumentException("Unkown status code: " + statusLine);
+       }
+
+       @Override
+       public String toString() {
+               return code + " " + reasonPhrase;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java b/org.argeo.cms/src/org/argeo/cms/http/server/HttpServerUtils.java
new file mode 100644 (file)
index 0000000..ab033f0
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.http.server;
+
+import java.net.URI;
+import java.util.Objects;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+
+/** HTTP utilities on the server-side. */
+public class HttpServerUtils {
+       private final static String SLASH = "/";
+
+       private static String extractPathWithingContext(HttpContext httpContext, String fullPath, boolean startWithSlash) {
+               Objects.requireNonNull(fullPath);
+               String contextPath = httpContext.getPath();
+               if (!fullPath.startsWith(contextPath))
+                       throw new IllegalArgumentException(fullPath + " does not belong to context" + contextPath);
+               String path = fullPath.substring(contextPath.length());
+               // TODO optimise?
+               if (!startWithSlash && path.startsWith(SLASH)) {
+                       path = path.substring(1);
+               } else if (startWithSlash && !path.startsWith(SLASH)) {
+                       path = SLASH + path;
+               }
+               return path;
+       }
+
+       /** Path within the context, NOT starting with a slash. */
+       public static String relativize(HttpExchange exchange) {
+               URI uri = exchange.getRequestURI();
+               HttpContext httpContext = exchange.getHttpContext();
+               return extractPathWithingContext(httpContext, uri.getPath(), false);
+       }
+
+       /** Path within the context, starting with a slash. */
+       public static String subPath(HttpExchange exchange) {
+               URI uri = exchange.getRequestURI();
+               HttpContext httpContext = exchange.getHttpContext();
+               return extractPathWithingContext(httpContext, uri.getPath(), true);
+       }
+
+       /** singleton */
+       private HttpServerUtils() {
+
+       }
+}
index aa3a6ad17dd3e61fa4bd4dc751c6533a2a1f8b4a..4f5a85ddfc998a0bbd24b33d4b4fc567bf21e565 100644 (file)
 package org.argeo.cms.internal.auth;
 
 import java.io.Serializable;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.function.Consumer;
 
-import javax.crypto.SecretKey;
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.x500.X500Principal;
 
 import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
-import org.argeo.cms.security.NodeSecurityUtils;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.osgi.service.useradmin.Authorization;
 
 /** Default CMS session implementation. */
 public class CmsSessionImpl implements CmsSession, Serializable {
        private static final long serialVersionUID = 1867719354246307225L;
-       private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
        private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class);
 
-       // private final Subject initialSubject;
-       private transient AccessControlContext accessControlContext;
+       private transient Subject subject;
        private final UUID uuid;
        private final String localSessionId;
        private Authorization authorization;
-       private final LdapName userDn;
+//     private final LdapName userDn;
+       private final String userDn;
        private final boolean anonymous;
 
        private final ZonedDateTime creationTime;
        private ZonedDateTime end;
        private final Locale locale;
 
-       private ServiceRegistration<CmsSession> serviceRegistration;
-
        private Map<String, Object> views = new HashMap<>();
 
        private List<Consumer<CmsSession>> onCloseCallbacks = Collections.synchronizedList(new ArrayList<>());
 
-       public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) {
+       public CmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
+                       String localSessionId) {
+               Objects.requireNonNull(uuid);
+
                this.creationTime = ZonedDateTime.now();
                this.locale = locale;
-               this.accessControlContext = Subject.doAs(initialSubject, new PrivilegedAction<AccessControlContext>() {
-
-                       @Override
-                       public AccessControlContext run() {
-                               return AccessController.getContext();
-                       }
-
-               });
-               // this.initialSubject = initialSubject;
+               this.subject = initialSubject;
                this.localSessionId = localSessionId;
                this.authorization = authorization;
-               if (authorization.getName() != null)
-                       try {
-                               this.userDn = new LdapName(authorization.getName());
-                               this.anonymous = false;
-                       } catch (InvalidNameException e) {
-                               throw new IllegalArgumentException("Invalid user name " + authorization.getName(), e);
-                       }
-               else {
-                       this.userDn = NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
+               if (authorization.getName() != null) {
+                       this.userDn = authorization.getName();
+                       this.anonymous = false;
+               } else {
+                       this.userDn = CmsConstants.ROLE_ANONYMOUS;
                        this.anonymous = true;
                }
-               this.uuid = UUID.randomUUID();
-               // register as service
-               Hashtable<String, String> props = new Hashtable<>();
-               props.put(CmsSession.USER_DN, userDn.toString());
-               props.put(CmsSession.SESSION_UUID, uuid.toString());
-               props.put(CmsSession.SESSION_LOCAL_ID, localSessionId);
-               serviceRegistration = bc.registerService(CmsSession.class, this, props);
+               this.uuid = uuid;
        }
 
        public void close() {
                end = ZonedDateTime.now();
-               serviceRegistration.unregister();
+               CmsContextImpl.getCmsContext().unregisterCmsSession(this);
+//             serviceRegistration.unregister();
 
                for (Consumer<CmsSession> onClose : onCloseCallbacks) {
                        onClose.accept(this);
@@ -105,15 +76,15 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                try {
                        LoginContext lc;
                        if (isAnonymous()) {
-                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, getSubject());
+                               lc = CmsAuth.ANONYMOUS.newLoginContext(getSubject());
                        } else {
-                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, getSubject());
+                               lc = CmsAuth.USER.newLoginContext(getSubject());
                        }
                        lc.logout();
                } catch (LoginException e) {
                        log.warn("Could not logout " + getSubject() + ": " + e);
                } finally {
-                       accessControlContext = null;
+                       subject = null;
                }
                log.debug("Closed " + this);
        }
@@ -124,13 +95,13 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        }
 
        public Subject getSubject() {
-               return Subject.getSubject(accessControlContext);
+               return subject;
        }
 
-       public Set<SecretKey> getSecretKeys() {
-               checkValid();
-               return getSubject().getPrivateCredentials(SecretKey.class);
-       }
+//     public Set<SecretKey> getSecretKeys() {
+//             checkValid();
+//             return getSubject().getPrivateCredentials(SecretKey.class);
+//     }
 
        @Override
        public boolean isValid() {
@@ -162,7 +133,7 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        }
 
        @Override
-       public LdapName getUserDn() {
+       public String getUserDn() {
                return userDn;
        }
 
@@ -205,59 +176,6 @@ public class CmsSessionImpl implements CmsSession, Serializable {
        }
 
        public String toString() {
-               return "CMS Session " + userDn + " local=" + localSessionId + ", uuid=" + uuid;
-       }
-
-       public static CmsSessionImpl getByLocalId(String localId) {
-               Collection<ServiceReference<CmsSession>> sr;
-               try {
-                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_LOCAL_ID + "=" + localId + ")");
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS session for id " + localId, e);
-               }
-               ServiceReference<CmsSession> cmsSessionRef;
-               if (sr.size() == 1) {
-                       cmsSessionRef = sr.iterator().next();
-                       return (CmsSessionImpl) bc.getService(cmsSessionRef);
-               } else if (sr.size() == 0) {
-                       return null;
-               } else
-                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + localId);
-
-       }
-
-       public static CmsSessionImpl getByUuid(Object uuid) {
-               Collection<ServiceReference<CmsSession>> sr;
-               try {
-                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
-               }
-               ServiceReference<CmsSession> cmsSessionRef;
-               if (sr.size() == 1) {
-                       cmsSessionRef = sr.iterator().next();
-                       return (CmsSessionImpl) bc.getService(cmsSessionRef);
-               } else if (sr.size() == 0) {
-                       return null;
-               } else
-                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
-
-       }
-
-       public static void closeInvalidSessions() {
-               Collection<ServiceReference<CmsSession>> srs;
-               try {
-                       srs = bc.getServiceReferences(CmsSession.class, null);
-                       for (ServiceReference<CmsSession> sr : srs) {
-                               CmsSession cmsSession = bc.getService(sr);
-                               if (!cmsSession.isValid()) {
-                                       ((CmsSessionImpl) cmsSession).close();
-                                       if (log.isDebugEnabled())
-                                               log.debug("Closed expired CMS session " + cmsSession);
-                               }
-                       }
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS sessions", e);
-               }
+               return "CMS Session " + userDn + " localId=" + localSessionId + ", uuid=" + uuid;
        }
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
deleted file mode 100644 (file)
index 1913660..0000000
+++ /dev/null
@@ -1,491 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import static org.argeo.util.naming.LdapAttrs.cn;
-import static org.argeo.util.naming.LdapAttrs.description;
-import static org.argeo.util.naming.LdapAttrs.owner;
-
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.TokenUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.NamingUtils;
-import org.argeo.util.naming.SharedSecret;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Canonical implementation of the people {@link CmsUserManager}. Wraps
- * interaction with users and groups.
- * 
- * In a *READ-ONLY* mode. We want to be able to:
- * <ul>
- * <li>Retrieve my user and corresponding information (main info,
- * groups...)</li>
- * <li>List all local groups (not the system roles)</li>
- * <li>If sufficient rights: retrieve a given user and its information</li>
- * </ul>
- */
-public class CmsUserManagerImpl implements CmsUserManager {
-       private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
-
-       private UserAdmin userAdmin;
-//     private Map<String, String> serviceProperties;
-       private WorkTransaction userTransaction;
-
-       private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
-                       .synchronizedMap(new LinkedHashMap<>());
-
-       @Override
-       public String getMyMail() {
-               return getUserMail(CurrentUser.getUsername());
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               return userAdmin.getRoles(filter);
-       }
-
-       // ALL USER: WARNING access to this will be later reduced
-
-       /** Retrieve a user given his dn */
-       public User getUser(String dn) {
-               return (User) getUserAdmin().getRole(dn);
-       }
-
-       /** Can be a group or a user */
-       public String getUserDisplayName(String dn) {
-               // FIXME: during initialisation phase, the system logs "admin" as user
-               // name rather than the corresponding dn
-               if ("admin".equals(dn))
-                       return "System Administrator";
-               else
-                       return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
-       }
-
-       @Override
-       public String getUserMail(String dn) {
-               return UserAdminUtils.getUserMail(getUserAdmin(), dn);
-       }
-
-       /** Lists all roles of the given user */
-       @Override
-       public String[] getUserRoles(String dn) {
-               Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
-               return currAuth.getRoles();
-       }
-
-       @Override
-       public boolean isUserInRole(String userDn, String roleDn) {
-               String[] roles = getUserRoles(userDn);
-               for (String role : roles) {
-                       if (role.equalsIgnoreCase(roleDn))
-                               return true;
-               }
-               return false;
-       }
-
-       private final String[] knownProps = { LdapAttrs.cn.name(), LdapAttrs.sn.name(), LdapAttrs.givenName.name(),
-                       LdapAttrs.uid.name() };
-
-       public Set<User> listUsersInGroup(String groupDn, String filter) {
-               Group group = (Group) userAdmin.getRole(groupDn);
-               if (group == null)
-                       throw new IllegalArgumentException("Group " + groupDn + " not found");
-               Set<User> users = new HashSet<User>();
-               addUsers(users, group, filter);
-               return users;
-       }
-
-       /** Recursively add users to list */
-       private void addUsers(Set<User> users, Group group, String filter) {
-               Role[] roles = group.getMembers();
-               for (Role role : roles) {
-                       if (role.getType() == Role.GROUP) {
-                               addUsers(users, (Group) role, filter);
-                       } else if (role.getType() == Role.USER) {
-                               if (match(role, filter))
-                                       users.add((User) role);
-                       } else {
-                               // ignore
-                       }
-               }
-       }
-
-       public List<User> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
-               Role[] roles = null;
-               try {
-                       roles = getUserAdmin().getRoles(filter);
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
-               }
-
-               List<User> users = new ArrayList<User>();
-               for (Role role : roles) {
-                       if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
-                                       && (includeSystemRoles || !role.getName().toLowerCase().endsWith(CmsConstants.ROLES_BASEDN))) {
-                               if (match(role, filter))
-                                       users.add((User) role);
-                       }
-               }
-               return users;
-       }
-
-       private boolean match(Role role, String filter) {
-               boolean doFilter = filter != null && !"".equals(filter);
-               if (doFilter) {
-                       for (String prop : knownProps) {
-                               Object currProp = null;
-                               try {
-                                       currProp = role.getProperties().get(prop);
-                               } catch (Exception e) {
-                                       throw e;
-                               }
-                               if (currProp != null) {
-                                       String currPropStr = ((String) currProp).toLowerCase();
-                                       if (currPropStr.contains(filter.toLowerCase())) {
-                                               return true;
-                                       }
-                               }
-                       }
-                       return false;
-               } else
-                       return true;
-       }
-
-       @Override
-       public User getUserFromLocalId(String localId) {
-               User user = getUserAdmin().getUser(LdapAttrs.uid.name(), localId);
-               if (user == null)
-                       user = getUserAdmin().getUser(LdapAttrs.cn.name(), localId);
-               return user;
-       }
-
-       @Override
-       public String buildDefaultDN(String localId, int type) {
-               return buildDistinguishedName(localId, getDefaultDomainName(), type);
-       }
-
-       @Override
-       public String getDefaultDomainName() {
-               Map<String, String> dns = getKnownBaseDns(true);
-               if (dns.size() == 1)
-                       return dns.keySet().iterator().next();
-               else
-                       throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
-                                       + dns.keySet().toString() + ". Unable to chose a default one.");
-       }
-
-//     public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-//             Map<String, String> dns = new HashMap<String, String>();
-//             String[] propertyKeys = serviceProperties.keySet().toArray(new String[serviceProperties.size()]);
-//             for (String uri : propertyKeys) {
-//                     if (!uri.startsWith("/"))
-//                             continue;
-//                     Dictionary<String, ?> props = UserAdminConf.uriAsProperties(uri);
-//                     String readOnly = UserAdminConf.readOnly.getValue(props);
-//                     String baseDn = UserAdminConf.baseDn.getValue(props);
-//
-//                     if (onlyWritable && "true".equals(readOnly))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
-//                             continue;
-//                     if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
-//                             continue;
-//                     dns.put(baseDn, uri);
-//             }
-//             return dns;
-//     }
-
-       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
-               Map<String, String> dns = new HashMap<String, String>();
-               for (UserDirectory userDirectory : userDirectories.keySet()) {
-                       Boolean readOnly = userDirectory.isReadOnly();
-                       String baseDn = userDirectory.getBaseDn().toString();
-
-                       if (onlyWritable && readOnly)
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
-                               continue;
-                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
-                               continue;
-                       dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
-
-               }
-               return dns;
-       }
-
-       public String buildDistinguishedName(String localId, String baseDn, int type) {
-               Map<String, String> dns = getKnownBaseDns(true);
-               Dictionary<String, ?> props = UserAdminConf.uriAsProperties(dns.get(baseDn));
-               String dn = null;
-               if (Role.GROUP == type)
-                       dn = LdapAttrs.cn.name() + "=" + localId + "," + UserAdminConf.groupBase.getValue(props) + "," + baseDn;
-               else if (Role.USER == type)
-                       dn = LdapAttrs.uid.name() + "=" + localId + "," + UserAdminConf.userBase.getValue(props) + "," + baseDn;
-               else
-                       throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
-               return dn;
-       }
-
-       @Override
-       public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
-               String name = CurrentUser.getUsername();
-               LdapName dn;
-               try {
-                       dn = new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Invalid user dn " + name, e);
-               }
-               User user = (User) userAdmin.getRole(dn.toString());
-               if (!user.hasCredential(null, oldPassword))
-                       throw new IllegalArgumentException("Invalid password");
-               if (Arrays.equals(newPassword, new char[0]))
-                       throw new IllegalArgumentException("New password empty");
-               try {
-                       userTransaction.begin();
-                       user.getCredentials().put(null, newPassword);
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot change password", e);
-               }
-       }
-
-       public void resetPassword(String username, char[] newPassword) {
-               LdapName dn;
-               try {
-                       dn = new LdapName(username);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Invalid user dn " + username, e);
-               }
-               User user = (User) userAdmin.getRole(dn.toString());
-               if (Arrays.equals(newPassword, new char[0]))
-                       throw new IllegalArgumentException("New password empty");
-               try {
-                       userTransaction.begin();
-                       user.getCredentials().put(null, newPassword);
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot change password", e);
-               }
-       }
-
-       public String addSharedSecret(String email, int hours) {
-               User user = (User) userAdmin.getUser(LdapAttrs.mail.name(), email);
-               try {
-                       userTransaction.begin();
-                       String uuid = UUID.randomUUID().toString();
-                       SharedSecret sharedSecret = new SharedSecret(hours, uuid);
-                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
-                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
-                       userTransaction.commit();
-                       return tokenStr;
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot change password", e);
-               }
-       }
-
-       @Deprecated
-       public String addSharedSecret(String username, String authInfo, String authToken) {
-               try {
-                       userTransaction.begin();
-                       User user = (User) userAdmin.getRole(username);
-                       SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
-                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
-                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
-                       userTransaction.commit();
-                       return tokenStr;
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add shared secret", e1);
-               }
-       }
-
-       @Override
-       public void expireAuthToken(String token) {
-               try {
-                       userTransaction.begin();
-                       String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
-                       Group tokenGroup = (Group) userAdmin.getRole(dn);
-                       String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
-                       tokenGroup.getProperties().put(description.name(), ldapDate);
-                       userTransaction.commit();
-                       if (log.isDebugEnabled())
-                               log.debug("Token " + token + " expired.");
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot expire token", e1);
-               }
-       }
-
-       @Override
-       public void expireAuthTokens(Subject subject) {
-               Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
-               for (String token : tokens)
-                       expireAuthToken(token);
-       }
-
-       @Override
-       public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
-               addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
-       }
-
-       @Override
-       public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
-               try {
-                       userTransaction.begin();
-                       User user = (User) userAdmin.getRole(userDn);
-                       String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
-                       Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
-                       if (roles != null)
-                               for (String role : roles) {
-                                       Role r = userAdmin.getRole(role);
-                                       if (r != null)
-                                               tokenGroup.addMember(r);
-                                       else {
-                                               if (!role.equals(CmsConstants.ROLE_USER)) {
-                                                       throw new IllegalStateException(
-                                                                       "Cannot add role " + role + " to token " + token + " for " + userDn);
-                                               }
-                                       }
-                               }
-                       tokenGroup.getProperties().put(owner.name(), user.getName());
-                       if (expiryDate != null) {
-                               String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
-                               tokenGroup.getProperties().put(description.name(), ldapDate);
-                       }
-                       userTransaction.commit();
-               } catch (Exception e1) {
-                       try {
-                               if (!userTransaction.isNoTransactionStatus())
-                                       userTransaction.rollback();
-                       } catch (Exception e2) {
-                               if (log.isTraceEnabled())
-                                       log.trace("Cannot rollback transaction", e2);
-                       }
-                       throw new RuntimeException("Cannot add token", e1);
-               }
-       }
-
-//     public User createUserFromPerson(Node person) {
-//             String email = JcrUtils.get(person, LdapAttrs.mail.property());
-//             String dn = buildDefaultDN(email, Role.USER);
-//             User user;
-//             try {
-//                     userTransaction.begin();
-//                     user = (User) userAdmin.createRole(dn, Role.USER);
-//                     Dictionary<String, Object> userProperties = user.getProperties();
-//                     String name = JcrUtils.get(person, LdapAttrs.displayName.property());
-//                     userProperties.put(LdapAttrs.cn.name(), name);
-//                     userProperties.put(LdapAttrs.displayName.name(), name);
-//                     String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
-//                     String surname = JcrUtils.get(person, LdapAttrs.sn.property());
-//                     userProperties.put(LdapAttrs.givenName.name(), givenName);
-//                     userProperties.put(LdapAttrs.sn.name(), surname);
-//                     userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
-//                     userTransaction.commit();
-//             } catch (Exception e) {
-//                     try {
-//                             userTransaction.rollback();
-//                     } catch (Exception e1) {
-//                             log.error("Could not roll back", e1);
-//                     }
-//                     if (e instanceof RuntimeException)
-//                             throw (RuntimeException) e;
-//                     else
-//                             throw new RuntimeException("Cannot create user", e);
-//             }
-//             return user;
-//     }
-
-       public UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-//     public UserTransaction getUserTransaction() {
-//             return userTransaction;
-//     }
-
-       /* DEPENDENCY INJECTION */
-       public void setUserAdmin(UserAdmin userAdmin) {
-               this.userAdmin = userAdmin;
-//             this.serviceProperties = serviceProperties;
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-       public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
-               userDirectories.put(userDirectory, new Hashtable<>(properties));
-       }
-
-       public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
-               userDirectories.remove(userDirectory);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java
deleted file mode 100644 (file)
index 0979a21..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import java.io.Console;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-/** Callback handler to be used with a command line UI. */
-public class ConsoleCallbackHandler implements CallbackHandler {
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               Console console = System.console();
-               if (console == null)
-                       throw new IllegalStateException("No console available");
-
-               PrintWriter writer = console.writer();
-               for (int i = 0; i < callbacks.length; i++) {
-                       if (callbacks[i] instanceof TextOutputCallback) {
-                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
-                               writer.write(callback.getMessage());
-                       } else if (callbacks[i] instanceof NameCallback) {
-                               NameCallback callback = (NameCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               if (callback.getDefaultName() != null)
-                                       writer.write(" (" + callback.getDefaultName() + ")");
-                               writer.write(" : ");
-                               String answer = console.readLine();
-                               if (callback.getDefaultName() != null && answer.trim().equals(""))
-                                       callback.setName(callback.getDefaultName());
-                               else
-                                       callback.setName(answer);
-                       } else if (callbacks[i] instanceof PasswordCallback) {
-                               PasswordCallback callback = (PasswordCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               char[] answer = console.readPassword();
-                               callback.setPassword(answer);
-                               Arrays.fill(answer, ' ');
-                       }
-//                     else if (callbacks[i] instanceof LocaleChoice) {
-//                             LocaleChoice callback = (LocaleChoice) callbacks[i];
-//                             writer.write("Language");
-//                             writer.write("\n");
-//                             for (int j = 0; j < callback.getLocales().size(); j++) {
-//                                     Locale locale = callback.getLocales().get(j);
-//                                     writer.print(j + " : " + locale.getDisplayName() + "\n");
-//                             }
-//                             writer.write("(" + callback.getDefaultIndex() + ") : ");
-//                             String answer = console.readLine();
-//                             if (answer.trim().equals(""))
-//                                     callback.setSelectedIndex(callback.getDefaultIndex());
-//                             else
-//                                     callback.setSelectedIndex(new Integer(answer.trim()));
-//                     }
-               }
-       }
-
-}
index c753601296657c9e69d03499a7b931ad9bdf8ee4..9e0ebce97d230ec24828ccbbb548786855013ae6 100644 (file)
@@ -1,17 +1,13 @@
 package org.argeo.cms.internal.auth;
 
 import java.security.Principal;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
 
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
+import javax.xml.namespace.QName;
 
+import org.argeo.cms.RoleNameUtils;
 import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
 
 /**
  * A {@link Principal} which has been implied by an {@link Authorization}. If it
@@ -21,67 +17,42 @@ import org.osgi.service.useradmin.Role;
  * identity is removed, the related {@link ImpliedByPrincipal}s can thus be
  * removed.
  */
-public final class ImpliedByPrincipal implements Principal, Role {
-       private final LdapName name;
-       private Set<Principal> causes = new HashSet<Principal>();
+public final class ImpliedByPrincipal implements Principal {
+       private final String name;
+       private final QName roleName;
+       private final boolean systemRole;
+       private final String context;
 
-       private int type = Role.ROLE;
+       private Set<Principal> causes = new HashSet<Principal>();
 
        public ImpliedByPrincipal(String name, Principal userPrincipal) {
-               try {
-                       this.name = new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Badly formatted role name", e);
-               }
-               if (userPrincipal != null)
-                       causes.add(userPrincipal);
-       }
-
-       public ImpliedByPrincipal(LdapName name, Principal userPrincipal) {
                this.name = name;
+               roleName = RoleNameUtils.getLastRdnAsName(name);
+               systemRole = RoleNameUtils.isSystemRole(roleName);
+               context = RoleNameUtils.getContext(name);
                if (userPrincipal != null)
                        causes.add(userPrincipal);
        }
 
        public String getName() {
-               return name.toString();
-       }
-
-       public boolean addMember(Principal user) {
-               throw new UnsupportedOperationException();
-       }
-
-       public boolean removeMember(Principal user) {
-               throw new UnsupportedOperationException();
-       }
-
-       public boolean isMember(Principal member) {
-               return causes.contains(member);
-       }
-
-       public Enumeration<? extends Principal> members() {
-               return Collections.enumeration(causes);
+               return name;
        }
 
        /*
-        * USER ADMIN
+        * OBJECT
         */
 
-       @Override
-       /** Type of {@link Role}, if known. */
-       public int getType() {
-               return type;
+       public QName getRoleName() {
+               return roleName;
        }
 
-       @Override
-       /** Not supported for the time being. */
-       public Dictionary<String, Object> getProperties() {
-               throw new UnsupportedOperationException();
+       public String getContext() {
+               return context;
        }
 
-       /*
-        * OBJECT
-        */
+       public boolean isSystemRole() {
+               return systemRole;
+       }
 
        @Override
        public int hashCode() {
@@ -90,8 +61,6 @@ public final class ImpliedByPrincipal implements Principal, Role {
 
        @Override
        public boolean equals(Object obj) {
-               // if (this == obj)
-               // return true;
                if (obj instanceof ImpliedByPrincipal) {
                        ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
                        // TODO check members too?
@@ -102,7 +71,6 @@ public final class ImpliedByPrincipal implements Principal, Role {
 
        @Override
        public String toString() {
-               // return name.toString() + " implied by " + causes;
                return name.toString();
        }
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/RemoteCmsSessionImpl.java
new file mode 100644 (file)
index 0000000..23f5d84
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms.internal.auth;
+
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.osgi.service.useradmin.Authorization;
+
+/** CMS session implementation in a web context. */
+public class RemoteCmsSessionImpl extends CmsSessionImpl {
+       private static final long serialVersionUID = -5178883380637048025L;
+       private RemoteAuthSession httpSession;
+
+       public RemoteCmsSessionImpl(UUID uuid, Subject initialSubject, Authorization authorization, Locale locale,
+                       RemoteAuthRequest request) {
+               super(uuid, initialSubject, authorization, locale,
+                               request.getSession() != null ? request.getSession().getId() : null);
+               httpSession = request.getSession();
+       }
+
+       @Override
+       public boolean isValid() {
+               if (isClosed())
+                       return false;
+               if (httpSession == null)
+                       return true;
+               return httpSession.isValid();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java
new file mode 100644 (file)
index 0000000..e17a089
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.cms.internal.http;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthUtils;
+
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
+
+/** An {@link Authenticator} implementation based on CMS authentication. */
+public class CmsAuthenticator extends Authenticator {
+       // TODO make it configurable
+       private final String httpAuthRealm = "Argeo";
+       private final boolean forceBasic = false;
+
+       @Override
+       public Result authenticate(HttpExchange exch) {
+               RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(exch);
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+               LoginContext lc;
+               try {
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
+                       lc.login();
+               } catch (LoginException e) {
+                       if (authIsRequired(remoteAuthExchange, remoteAuthExchange)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, remoteAuthExchange, httpAuthRealm,
+                                               forceBasic);
+                               return new Authenticator.Retry(statusCode);
+
+                       } else {
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthExchange, remoteAuthExchange);
+                       }
+                       if (lc == null)
+                               return new Authenticator.Failure(403);
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
+
+               Subject subject = lc.getSubject();
+
+               String username = CurrentUser.getUsername(subject);
+               HttpPrincipal httpPrincipal = new HttpPrincipal(username, httpAuthRealm);
+               return new Authenticator.Success(httpPrincipal);
+       }
+
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return true;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/http/InternalHttpConstants.java
deleted file mode 100644 (file)
index c888c29..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.internal.http;
-
-/** Compatible with Jetty. */
-public interface InternalHttpConstants {
-       static final String HTTP_ENABLED = "http.enabled";
-       static final String HTTP_PORT = "http.port";
-       static final String HTTP_HOST = "http.host";
-       static final String HTTPS_ENABLED = "https.enabled";
-       static final String HTTPS_HOST = "https.host";
-       static final String HTTPS_PORT = "https.port";
-       static final String SSL_KEYSTORE = "ssl.keystore";
-       static final String SSL_PASSWORD = "ssl.password";
-       static final String SSL_KEYPASSWORD = "ssl.keypassword";
-       static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
-       static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
-       static final String SSL_PROTOCOL = "ssl.protocol";
-       static final String SSL_ALGORITHM = "ssl.algorithm";
-       static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
-       static final String JETTY_PROPERTY_PREFIX = "org.eclipse.equinox.http.jetty.";
-       // Argeo specific
-       static final String WEBSOCKET_ENABLED = "websocket.enabled";
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PublicCmsAuthenticator.java
new file mode 100644 (file)
index 0000000..14d79a6
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.internal.http;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class PublicCmsAuthenticator extends CmsAuthenticator {
+
+       @Override
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return false;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RemoteAuthHttpExchange.java
new file mode 100644 (file)
index 0000000..b7e670c
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.internal.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse {
+       private final HttpExchange httpExchange;
+       private RemoteAuthSession remoteAuthSession;
+
+       public RemoteAuthHttpExchange(HttpExchange httpExchange) {
+               this.httpExchange = httpExchange;
+               this.remoteAuthSession = (RemoteAuthSession) httpExchange.getAttribute(RemoteAuthSession.class.getName());
+               Objects.requireNonNull(this.remoteAuthSession);
+       }
+
+       @Override
+       public void setHeader(String headerName, String value) {
+               httpExchange.getResponseHeaders().put(headerName, Collections.singletonList(value));
+       }
+
+       @Override
+       public void addHeader(String headerName, String value) {
+               List<String> values = httpExchange.getResponseHeaders().getOrDefault(headerName, new ArrayList<>());
+               values.add(value);
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               return remoteAuthSession;
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               throw new UnsupportedOperationException("Cannot create remote session");
+       }
+
+       @Override
+       public Locale getLocale() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return httpExchange.getAttribute(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               httpExchange.setAttribute(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               List<String> lst = httpExchange.getRequestHeaders().get(key);
+               if (lst == null || lst.size() == 0)
+                       return null;
+               return lst.get(0);
+       }
+
+       @Override
+       public int getLocalPort() {
+               return httpExchange.getLocalAddress().getPort();
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               return httpExchange.getRemoteAddress().getHostName();
+       }
+
+       @Override
+       public int getRemotePort() {
+               return httpExchange.getRemoteAddress().getPort();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java
deleted file mode 100644 (file)
index fd51c59..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.util.Locale;
-
-import javax.security.auth.Subject;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.osgi.service.useradmin.Authorization;
-
-/** CMS session implementation in a web context. */
-public class WebCmsSessionImpl extends CmsSessionImpl {
-       private static final long serialVersionUID = -5178883380637048025L;
-       private RemoteAuthSession httpSession;
-
-       public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale,
-                       RemoteAuthRequest request) {
-               super(initialSubject, authorization, locale, request.getSession().getId());
-               httpSession = request.getSession();
-       }
-
-       @Override
-       public boolean isValid() {
-               if (isClosed())
-                       return false;
-               return httpSession.isValid();
-       }
-
-       public static CmsSessionImpl getCmsSession(RemoteAuthRequest request) {
-               return CmsSessionImpl.getByLocalId(request.getSession().getId());
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/HttpCredentialProvider.java
deleted file mode 100644 (file)
index 4a9392c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-
-/** SPNEGO credential provider */
-public class HttpCredentialProvider implements CredentialsProvider {
-
-       @Override
-       public Credentials getCredentials(AuthScheme scheme, String host, int port, boolean proxy)
-                       throws CredentialsNotAvailableException {
-               if (scheme instanceof SpnegoAuthScheme)
-                       return new SpnegoCredentials();
-               else
-                       throw new UnsupportedOperationException("Auth scheme " + scheme.getSchemeName() + " not supported");
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java
deleted file mode 100644 (file)
index 4abdd14..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import java.net.URL;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Base64;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-
-import org.apache.commons.httpclient.Credentials;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.URIException;
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.AuthenticationException;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.auth.MalformedChallengeException;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.ietf.jgss.GSSContext;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-
-/** Implementation of the SPNEGO auth scheme. */
-public class SpnegoAuthScheme implements AuthScheme {
-//     private final static Log log = LogFactory.getLog(SpnegoAuthScheme.class);
-
-       public static final String NAME = "Negotiate";
-       private final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
-
-       private boolean complete = false;
-       private String realm;
-
-       @Override
-       public void processChallenge(String challenge) throws MalformedChallengeException {
-               // if(tokenStr!=null){
-               // log.error("Received challenge while there is a token. Failing.");
-               // complete = false;
-               // }
-
-       }
-
-       @Override
-       public String getSchemeName() {
-               return NAME;
-       }
-
-       @Override
-       public String getParameter(String name) {
-               return null;
-       }
-
-       @Override
-       public String getRealm() {
-               return realm;
-       }
-
-       @Override
-       public String getID() {
-               return NAME;
-       }
-
-       @Override
-       public boolean isConnectionBased() {
-               return true;
-       }
-
-       @Override
-       public boolean isComplete() {
-               return complete;
-       }
-
-       @Override
-       public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException {
-               // log.debug("authenticate " + method + " " + uri);
-               // return null;
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public String authenticate(Credentials credentials, HttpMethod method) throws AuthenticationException {
-               GSSContext context = null;
-               String tokenStr = null;
-               String hostname;
-               try {
-                       hostname = method.getURI().getHost();
-               } catch (URIException e1) {
-                       throw new IllegalStateException("Cannot authenticate", e1);
-               }
-               String serverPrinc = KernelConstants.DEFAULT_KERBEROS_SERVICE + "@" + hostname;
-
-               try {
-                       // Get service's principal name
-                       GSSManager manager = GSSManager.getInstance();
-                       GSSName serverName = manager.createName(serverPrinc, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_OID);
-
-                       // Get the context for authentication
-                       context = manager.createContext(serverName, KERBEROS_OID, null, GSSContext.DEFAULT_LIFETIME);
-                       // context.requestMutualAuth(true); // Request mutual authentication
-                       // context.requestConf(true); // Request confidentiality
-                       context.requestCredDeleg(true);
-
-                       byte[] token = new byte[0];
-
-                       // token is ignored on the first call
-                       token = context.initSecContext(token, 0, token.length);
-
-                       // Send a token to the server if one was generated by
-                       // initSecContext
-                       if (token != null) {
-                               tokenStr = Base64.getEncoder().encodeToString(token);
-                               // complete=true;
-                       }
-                       return "Negotiate " + tokenStr;
-               } catch (GSSException e) {
-                       complete = true;
-                       throw new AuthenticationException("Cannot authenticate to " + serverPrinc, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               if (args.length == 0) {
-                       System.err.println("usage: java " + SpnegoAuthScheme.class.getName() + " <url>");
-                       System.exit(1);
-                       return;
-               }
-               String url = args[0];
-
-               URL jaasUrl = SpnegoAuthScheme.class.getResource("jaas.cfg");
-               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
-               try {
-                       LoginContext lc = new LoginContext("SINGLE_USER");
-                       lc.login();
-
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-
-                       int responseCode = Subject.doAs(lc.getSubject(), new PrivilegedExceptionAction<Integer>() {
-                               public Integer run() throws Exception {
-                                       HttpClient httpClient = new HttpClient();
-                                       return httpClient.executeMethod(new GetMethod(url));
-                               }
-                       });
-                       System.out.println("Reponse code: " + responseCode);
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java b/org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoCredentials.java
deleted file mode 100644 (file)
index f59b72a..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.cms.internal.http.client;
-
-import org.apache.commons.httpclient.Credentials;
-
-public class SpnegoCredentials implements Credentials {
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/http/client/jaas.cfg
deleted file mode 100644 (file)
index 21176b9..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-SINGLE_USER {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     principal="${user.name}"
-     useTicketCache=true;
-};
index 038d7029ca9cf6b720c1b8c01ef85e95384c32aa..b09956203cc2eb4afa0bfa60b2a1c22cf1fe9f5c 100644 (file)
@@ -4,20 +4,15 @@ import java.security.AllPermission;
 import java.util.Dictionary;
 
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoLogger;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
 import org.osgi.service.condpermadmin.BundleLocationCondition;
 import org.osgi.service.condpermadmin.ConditionInfo;
 import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
 import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
 import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.log.LogReaderService;
 import org.osgi.service.permissionadmin.PermissionInfo;
-import org.osgi.util.tracker.ServiceTracker;
 
 /**
  * Activates the kernel. Gives access to kernel information for the rest of the
@@ -26,75 +21,26 @@ import org.osgi.util.tracker.ServiceTracker;
 public class CmsActivator implements BundleActivator {
        private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
 
-//     private static Activator instance;
-
        // TODO make it configurable
        private boolean hardened = false;
 
        private static BundleContext bundleContext;
 
-       private LogReaderService logReaderService;
-
-       private CmsOsgiLogger logger;
-//     private CmsStateImpl nodeState;
-//     private CmsDeploymentImpl nodeDeployment;
-//     private CmsContextImpl nodeInstance;
-
-//     private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
-
-//     static {
-//             Bundle bundle = FrameworkUtil.getBundle(Activator.class);
-//             if (bundle != null) {
-//                     bundleContext = bundle.getBundleContext();
-//             }
-//     }
-
        void init() {
-//             Runtime.getRuntime().addShutdownHook(new CmsShutdown());
-//             instance = this;
-//             this.bc = bundleContext;
-               if (bundleContext != null)
-                       this.logReaderService = getService(LogReaderService.class);
-               initArgeoLogger();
-//             this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
-//
-//             try {
-//                     initSecurity();
-////                   initArgeoLogger();
-//                     initNode();
-//
-//                     if (log.isTraceEnabled())
-//                             log.trace("Kernel bundle started");
-//             } catch (Throwable e) {
-//                     log.error("## FATAL: CMS activator failed", e);
-//             }
        }
 
        void destroy() {
                try {
-//                     if (nodeInstance != null)
-//                             nodeInstance.shutdown();
-//                     if (nodeDeployment != null)
-//                             nodeDeployment.shutdown();
-//                     if (nodeState != null)
-//                             nodeState.shutdown();
-//
-//                     if (userAdminSt != null)
-//                             userAdminSt.close();
-
-//                     internalExecutorService.shutdown();
-//                     instance = null;
                        bundleContext = null;
-                       this.logReaderService = null;
-                       // this.configurationAdmin = null;
+//                     this.logReaderService = null;
                } catch (Exception e) {
                        log.error("CMS activator shutdown failed", e);
                }
-               
+
                new GogoShellKiller().start();
        }
 
-       private void initSecurity() {
+       protected void initSecurity() {
                // code-level permissions
                String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY);
                if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
@@ -130,26 +76,6 @@ public class CmsActivator implements BundleActivator {
 
        }
 
-       private void initArgeoLogger() {
-               logger = new CmsOsgiLogger(logReaderService);
-               if (bundleContext != null)
-                       bundleContext.registerService(ArgeoLogger.class, logger, null);
-       }
-
-//     private void initNode() throws IOException {
-//             // Node state
-//             nodeState = new CmsStateImpl();
-//             registerService(CmsState.class, nodeState, null);
-//
-//             // Node deployment
-//             nodeDeployment = new CmsDeploymentImpl();
-////           registerService(NodeDeployment.class, nodeDeployment, null);
-//
-//             // Node instance
-//             nodeInstance = new CmsContextImpl();
-//             registerService(CmsContext.class, nodeInstance, null);
-//     }
-
        public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
                if (bundleContext != null) {
                        bundleContext.registerService(clss, service, properties);
@@ -172,99 +98,18 @@ public class CmsActivator implements BundleActivator {
        @Override
        public void start(BundleContext bc) throws Exception {
                bundleContext = bc;
-//             if (!bc.getBundle().equals(bundleContext.getBundle()))
-//                     throw new IllegalStateException(
-//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
-               init();
-//             userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
-//             userAdminSt.open();
 
-               ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-
-                       @Override
-                       public HttpService addingService(ServiceReference<HttpService> sr) {
-                               Object httpPort = sr.getProperty("http.port");
-                               Object httpsPort = sr.getProperty("https.port");
-                               log.info(httpPortsMsg(httpPort, httpsPort));
-                               close();
-                               return super.addingService(sr);
-                       }
-               };
-               httpSt.open();
-       }
+               init();
 
-       private String httpPortsMsg(Object httpPort, Object httpsPort) {
-               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
        }
 
        @Override
        public void stop(BundleContext bc) throws Exception {
-//             if (!bc.getBundle().equals(bundleContext.getBundle()))
-//                     throw new IllegalStateException(
-//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+
                destroy();
                bundleContext = null;
        }
 
-//     private <T> T getService(Class<T> clazz) {
-//             ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
-//             if (sr == null)
-//                     throw new IllegalStateException("No service available for " + clazz);
-//             return bundleContext.getService(sr);
-//     }
-
-//     public static GSSCredential getAcceptorCredentials() {
-//             return getNodeUserAdmin().getAcceptorCredentials();
-//     }
-//
-//     @Deprecated
-//     public static boolean isSingleUser() {
-//             return getNodeUserAdmin().isSingleUser();
-//     }
-//
-//     public static UserAdmin getUserAdmin() {
-//             return (UserAdmin) getNodeUserAdmin();
-//     }
-//
-//     public static String getHttpProxySslHeader() {
-//             return KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
-//     }
-//
-//     private static NodeUserAdmin getNodeUserAdmin() {
-//             NodeUserAdmin res;
-//             try {
-//                     res = instance.userAdminSt.waitForService(60000);
-//             } catch (InterruptedException e) {
-//                     throw new IllegalStateException("Cannot retrieve Node user admin", e);
-//             }
-//             if (res == null)
-//                     throw new IllegalStateException("No Node user admin found");
-//
-//             return res;
-//             // ServiceReference<UserAdmin> sr =
-//             // instance.bc.getServiceReference(UserAdmin.class);
-//             // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
-//             // return userAdmin;
-//
-//     }
-
-//     public static ExecutorService getInternalExecutorService() {
-//             return instance.internalExecutorService;
-//     }
-
-       // static CmsSecurity getCmsSecurity() {
-       // return instance.nodeSecurity;
-       // }
-
-//     public String[] getLocales() {
-//             // TODO optimize?
-//             List<Locale> locales = CmsStateImpl.getNodeState().getLocales();
-//             String[] res = new String[locales.size()];
-//             for (int i = 0; i < locales.size(); i++)
-//                     res[i] = locales.get(i).toString();
-//             return res;
-//     }
-
        public static BundleContext getBundleContext() {
                return bundleContext;
        }
index 6898c4348fe098dc9113cfb25e62d1c39c98c6d2..85f045bae660b6783d2aa4448e7fdce59af327e1 100644 (file)
@@ -1,34 +1,11 @@
 package org.argeo.cms.internal.osgi;
 
-import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
 import java.security.SignatureException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
 
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ArgeoLogListener;
-import org.argeo.cms.ArgeoLogger;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.cms.runtime.DirectoryConf;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
@@ -38,48 +15,49 @@ import org.osgi.service.log.LogLevel;
 import org.osgi.service.log.LogListener;
 import org.osgi.service.log.LogReaderService;
 
-/** Not meant to be used directly in standard log4j config */
-public class CmsOsgiLogger implements ArgeoLogger, LogListener {
-       /** Internal debug for development purposes. */
-       private static Boolean debug = false;
+/** Logs OSGi events. */
+public class CmsOsgiLogger implements LogListener {
+       private final static String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
+       private final static String CONTEXT_NAME_PROP = "contextName";
+       
+       private LogReaderService logReaderService;
 
-       private Boolean disabled = false;
+//     /** Internal debug for development purposes. */
+//     private static Boolean debug = false;
 
-       private String level = null;
+//     private Boolean disabled = false;
+//
+//     private String level = null;
 
 //     private Level log4jLevel = null;
 
-       private Properties configuration;
-
-       private AppenderImpl appender;
-
-       private final List<ArgeoLogListener> everythingListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final List<ArgeoLogListener> allUsersListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
-                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+//     private Properties configuration;
 
-       private BlockingQueue<LogEvent> events;
-       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+//     private AppenderImpl appender;
 
-       private Integer maxLastEventsCount = 10 * 1000;
+//     private BlockingQueue<LogEvent> events;
+//     private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
 
-       /** Marker to prevent stack overflow */
-       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+//     private Integer maxLastEventsCount = 10 * 1000;
+//
+//     /** Marker to prevent stack overflow */
+//     private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+//
+//             @Override
+//             protected Boolean initialValue() {
+//                     return false;
+//             }
+//     };
 
-               @Override
-               protected Boolean initialValue() {
-                       return false;
-               }
-       };
+//     public CmsOsgiLogger(LogReaderService lrs) {
+//     }
 
-       public CmsOsgiLogger(LogReaderService lrs) {
-               if (lrs != null) {
-                       Enumeration<LogEntry> logEntries = lrs.getLog();
+       public void start() {
+               if (logReaderService != null) {
+                       Enumeration<LogEntry> logEntries = logReaderService.getLog();
                        while (logEntries.hasMoreElements())
                                logged(logEntries.nextElement());
-                       lrs.addLogListener(this);
+                       logReaderService.addLogListener(this);
 
                        // configure log4j watcher
 //                     String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
@@ -103,43 +81,31 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener {
 //                             }
 //                     }
                }
+//             try {
+////                   events = new LinkedBlockingQueue<LogEvent>();
+////
+////                   // if (layout != null)
+////                   // setLayout(layout);
+////                   // else
+////                   // setLayout(new PatternLayout(pattern));
+//////                 appender = new AppenderImpl();
+////                   reloadConfiguration();
+//////                 Logger.getRootLogger().addAppender(appender);
+////
+////                   logDispatcherThread = new LogDispatcherThread();
+////                   logDispatcherThread.start();
+//             } catch (Exception e) {
+//                     throw new IllegalStateException("Cannot initialize log4j");
+//             }
        }
 
-       public void init() {
-               try {
-                       events = new LinkedBlockingQueue<LogEvent>();
-
-                       // if (layout != null)
-                       // setLayout(layout);
-                       // else
-                       // setLayout(new PatternLayout(pattern));
-                       appender = new AppenderImpl();
-                       reloadConfiguration();
-//                     Logger.getRootLogger().addAppender(appender);
-
-                       logDispatcherThread = new LogDispatcherThread();
-                       logDispatcherThread.start();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot initialize log4j");
-               }
-       }
-
-       public void destroy() throws Exception {
-//             Logger.getRootLogger().removeAppender(appender);
-               allUsersListeners.clear();
-               for (List<ArgeoLogListener> lst : userListeners.values())
-                       lst.clear();
-               userListeners.clear();
-
-               events.clear();
-               events = null;
-               logDispatcherThread.interrupt();
+       public void stop() throws Exception {
+//             events.clear();
+//             events = null;
+//             logDispatcherThread.interrupt();
+               logReaderService.removeLogListener(this);
        }
 
-       // public void setLayout(Layout layout) {
-       // this.layout = layout;
-       // }
-
        public String toString() {
                return "Node Logger";
        }
@@ -203,24 +169,23 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener {
                        // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
                        // }
                        // servlets
-                       Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
+                       Object whiteBoardPattern = sr.getProperty(WHITEBOARD_PATTERN_PROP);
                        if (whiteBoardPattern != null) {
                                if (whiteBoardPattern instanceof String) {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
+                                       sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
                                } else {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
-                                                       + arrayToString((String[]) whiteBoardPattern));
+                                       sb.append(" " + WHITEBOARD_PATTERN_PROP + ": " + arrayToString((String[]) whiteBoardPattern));
                                }
                        }
                        // RWT
-                       Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
+                       Object contextName = sr.getProperty(CONTEXT_NAME_PROP);
                        if (contextName != null)
-                               sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
+                               sb.append(" " + CONTEXT_NAME_PROP + ": " + contextName);
 
                        // user directories
-                       Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
+                       Object baseDn = sr.getProperty(DirectoryConf.baseDn.name());
                        if (baseDn != null)
-                               sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
+                               sb.append(" " + DirectoryConf.baseDn.name() + ": " + baseDn);
 
                }
                return sb.toString();
@@ -247,295 +212,300 @@ public class CmsOsgiLogger implements ArgeoLogger, LogListener {
                return false;
        }
 
+       public void setLogReaderService(LogReaderService logReaderService) {
+               this.logReaderService = logReaderService;
+       }
+
+       
        //
        // ARGEO LOGGER
        //
 
-       public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
-               String username = CurrentUser.getUsername();
-               if (username == null)
-                       throw new CmsException("Only authenticated users can register a log listener");
-
-               if (!userListeners.containsKey(username)) {
-                       List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
-                       userListeners.put(username, lst);
-               }
-               userListeners.get(username).add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       dispatchEvent(listener, evt);
-       }
-
-       public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
-                       boolean everything) {
-               if (everything)
-                       everythingListeners.add(listener);
-               else
-                       allUsersListeners.add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       if (everything || evt.getUsername() != null)
-                               dispatchEvent(listener, evt);
-       }
-
-       public synchronized void unregister(ArgeoLogListener listener) {
-               String username = CurrentUser.getUsername();
-               if (username == null)// FIXME
-                       return;
-               if (!userListeners.containsKey(username))
-                       throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
-               if (!userListeners.get(username).contains(listener))
-                       throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
-               userListeners.get(username).remove(listener);
-               if (userListeners.get(username).isEmpty())
-                       userListeners.remove(username);
-
-       }
-
-       public synchronized void unregisterForAll(ArgeoLogListener listener) {
-               everythingListeners.remove(listener);
-               allUsersListeners.remove(listener);
-       }
-
-       /** For development purpose, since using regular logging is not easy here */
-       private static void stdOut(Object obj) {
-               System.out.println(obj);
-       }
-
-       private static void stdErr(Object obj) {
-               System.err.println(obj);
-       }
-
-       private static void debug(Object obj) {
-               if (debug)
-                       System.out.println(obj);
-       }
-
-       private static boolean isInternalDebugEnabled() {
-               return debug;
-       }
+//     public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
+//             String username = CurrentUser.getUsername();
+//             if (username == null)
+//                     throw new IllegalStateException("Only authenticated users can register a log listener");
+//
+//             if (!userListeners.containsKey(username)) {
+//                     List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
+//                     userListeners.put(username, lst);
+//             }
+//             userListeners.get(username).add(listener);
+//             List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
+//             for (LogEvent evt : lastEvents)
+//                     dispatchEvent(listener, evt);
+//     }
+//
+//     public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
+//                     boolean everything) {
+//             if (everything)
+//                     everythingListeners.add(listener);
+//             else
+//                     allUsersListeners.add(listener);
+//             List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
+//             for (LogEvent evt : lastEvents)
+//                     if (everything || evt.getUsername() != null)
+//                             dispatchEvent(listener, evt);
+//     }
+//
+//     public synchronized void unregister(ArgeoLogListener listener) {
+//             String username = CurrentUser.getUsername();
+//             if (username == null)// FIXME
+//                     return;
+//             if (!userListeners.containsKey(username))
+//                     throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+//             if (!userListeners.get(username).contains(listener))
+//                     throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+//             userListeners.get(username).remove(listener);
+//             if (userListeners.get(username).isEmpty())
+//                     userListeners.remove(username);
+//
+//     }
+//
+//     public synchronized void unregisterForAll(ArgeoLogListener listener) {
+//             everythingListeners.remove(listener);
+//             allUsersListeners.remove(listener);
+//     }
+
+//     /** For development purpose, since using regular logging is not easy here */
+//     private static void stdOut(Object obj) {
+//             System.out.println(obj);
+//     }
+//
+//     private static void stdErr(Object obj) {
+//             System.err.println(obj);
+//     }
+//
+//     private static void debug(Object obj) {
+//             if (debug)
+//                     System.out.println(obj);
+//     }
+//
+//     private static boolean isInternalDebugEnabled() {
+//             return debug;
+//     }
 
        // public void setPattern(String pattern) {
        // this.pattern = pattern;
        // }
 
-       public void setDisabled(Boolean disabled) {
-               this.disabled = disabled;
-       }
-
-       public void setLevel(String level) {
-               this.level = level;
-       }
-
-       public void setConfiguration(Properties configuration) {
-               this.configuration = configuration;
-       }
-
-       public void updateConfiguration(Properties configuration) {
-               setConfiguration(configuration);
-               reloadConfiguration();
-       }
-
-       public Properties getConfiguration() {
-               return configuration;
-       }
-
-       /**
-        * Reloads configuration (if the configuration {@link Properties} is set)
-        */
-       protected void reloadConfiguration() {
-               if (configuration != null) {
-//                     LogManager.resetConfiguration();
-//                     PropertyConfigurator.configure(configuration);
-               }
-       }
-
-       protected synchronized void processLoggingEvent(LogEvent event) {
-               if (disabled)
-                       return;
-
-               if (dispatching.get())
-                       return;
-
-               if (level != null && !level.trim().equals("")) {
-//                     if (log4jLevel == null || !log4jLevel.toString().equals(level))
-//                             try {
-//                                     log4jLevel = Level.toLevel(level);
-//                             } catch (Exception e) {
-//                                     System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
-//                                     e.printStackTrace();
-//                                     level = null;
-//                             }
+//     public void setDisabled(Boolean disabled) {
+//             this.disabled = disabled;
+//     }
 //
-//                     if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
-//                             return;
-//                     }
-               }
-
-               try {
-                       // admin listeners
-                       Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
-                       while (everythingIt.hasNext())
-                               dispatchEvent(everythingIt.next(), event);
-
-                       if (event.getUsername() != null) {
-                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
-                               while (allUsersIt.hasNext())
-                                       dispatchEvent(allUsersIt.next(), event);
-
-                               if (userListeners.containsKey(event.getUsername())) {
-                                       Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
-                                       while (userIt.hasNext())
-                                               dispatchEvent(userIt.next(), event);
-                               }
-                       }
-               } catch (Exception e) {
-                       stdOut("Cannot process logging event");
-                       e.printStackTrace();
-               }
-       }
-
-       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-//             LoggingEvent event = evt.getLoggingEvent();
-//             logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
-//                             event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
-       }
+//     public void setLevel(String level) {
+//             this.level = level;
+//     }
 
-       private class AppenderImpl { // extends AppenderSkeleton {
-               public boolean requiresLayout() {
-                       return false;
-               }
+//     public void setConfiguration(Properties configuration) {
+//             this.configuration = configuration;
+//     }
+//
+//     public void updateConfiguration(Properties configuration) {
+//             setConfiguration(configuration);
+//             reloadConfiguration();
+//     }
+//
+//     public Properties getConfiguration() {
+//             return configuration;
+//     }
+//
+//     /**
+//      * Reloads configuration (if the configuration {@link Properties} is set)
+//      */
+//     protected void reloadConfiguration() {
+//             if (configuration != null) {
+////                   LogManager.resetConfiguration();
+////                   PropertyConfigurator.configure(configuration);
+//             }
+//     }
 
-               public void close() {
-               }
+//     protected synchronized void processLoggingEvent(LogEvent event) {
+//             if (disabled)
+//                     return;
+//
+//             if (dispatching.get())
+//                     return;
+//
+//             if (level != null && !level.trim().equals("")) {
+////                   if (log4jLevel == null || !log4jLevel.toString().equals(level))
+////                           try {
+////                                   log4jLevel = Level.toLevel(level);
+////                           } catch (Exception e) {
+////                                   System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
+////                                   e.printStackTrace();
+////                                   level = null;
+////                           }
+////
+////                   if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
+////                           return;
+////                   }
+//             }
+//
+////           try {
+////                   // admin listeners
+////                   Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
+////                   while (everythingIt.hasNext())
+////                           dispatchEvent(everythingIt.next(), event);
+////
+////                   if (event.getUsername() != null) {
+////                           Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
+////                           while (allUsersIt.hasNext())
+////                                   dispatchEvent(allUsersIt.next(), event);
+////
+////                           if (userListeners.containsKey(event.getUsername())) {
+////                                   Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
+////                                   while (userIt.hasNext())
+////                                           dispatchEvent(userIt.next(), event);
+////                           }
+////                   }
+////           } catch (Exception e) {
+////                   stdOut("Cannot process logging event");
+////                   e.printStackTrace();
+////           }
+//     }
+
+//     protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+////           LoggingEvent event = evt.getLoggingEvent();
+////           logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
+////                           event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
+//     }
+
+//     private class AppenderImpl { // extends AppenderSkeleton {
+//             public boolean requiresLayout() {
+//                     return false;
+//             }
+//
+//             public void close() {
+//             }
+//
+////           @Override
+////           protected void append(LoggingEvent event) {
+////                   if (events != null) {
+////                           try {
+////                                   String username = CurrentUser.getUsername();
+////                                   events.put(new LogEvent(username, event));
+////                           } catch (InterruptedException e) {
+////                                   // silent
+////                           }
+////                   }
+////           }
+//
+//     }
 
-//             @Override
-//             protected void append(LoggingEvent event) {
-//                     if (events != null) {
+//     private class LogDispatcherThread extends Thread {
+//             /** encapsulated in order to simplify concurrency management */
+//             private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+//
+//             public LogDispatcherThread() {
+//                     super("Argeo Logging Dispatcher Thread");
+//             }
+//
+//             public void run() {
+//                     while (events != null) {
 //                             try {
-//                                     String username = CurrentUser.getUsername();
-//                                     events.put(new LogEvent(username, event));
+//                                     LogEvent loggingEvent = events.take();
+//                                     processLoggingEvent(loggingEvent);
+//                                     addLastEvent(loggingEvent);
 //                             } catch (InterruptedException e) {
-//                                     // silent
+//                                     if (events == null)
+//                                             return;
 //                             }
 //                     }
 //             }
+//
+//             protected synchronized void addLastEvent(LogEvent loggingEvent) {
+//                     if (lastEvents.size() >= maxLastEventsCount)
+//                             lastEvents.poll();
+//                     lastEvents.add(loggingEvent);
+//             }
+//
+//             public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
+//                     LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+//                     ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
+//                     int count = 0;
+//                     while (it.hasPrevious() && (count < maxCount)) {
+//                             LogEvent evt = it.previous();
+//                             if (username == null || username.equals(evt.getUsername())) {
+//                                     evts.push(evt);
+//                                     count++;
+//                             }
+//                     }
+//                     return evts;
+//             }
+//     }
 
-       }
-
-       private class LogDispatcherThread extends Thread {
-               /** encapsulated in order to simplify concurrency management */
-               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-
-               public LogDispatcherThread() {
-                       super("Argeo Logging Dispatcher Thread");
-               }
-
-               public void run() {
-                       while (events != null) {
-                               try {
-                                       LogEvent loggingEvent = events.take();
-                                       processLoggingEvent(loggingEvent);
-                                       addLastEvent(loggingEvent);
-                               } catch (InterruptedException e) {
-                                       if (events == null)
-                                               return;
-                               }
-                       }
-               }
-
-               protected synchronized void addLastEvent(LogEvent loggingEvent) {
-                       if (lastEvents.size() >= maxLastEventsCount)
-                               lastEvents.poll();
-                       lastEvents.add(loggingEvent);
-               }
-
-               public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
-                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
-                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
-                       int count = 0;
-                       while (it.hasPrevious() && (count < maxCount)) {
-                               LogEvent evt = it.previous();
-                               if (username == null || username.equals(evt.getUsername())) {
-                                       evts.push(evt);
-                                       count++;
-                               }
-                       }
-                       return evts;
-               }
-       }
-
-       private class LogEvent {
-               private final String username;
-//             private final LoggingEvent loggingEvent;
-
-               public LogEvent(String username) {
-                       super();
-                       this.username = username;
-//                     this.loggingEvent = loggingEvent;
-               }
-
-//             @Override
-//             public int hashCode() {
-//                     return loggingEvent.hashCode();
+//     private class LogEvent {
+//             private final String username;
+////           private final LoggingEvent loggingEvent;
+//
+//             public LogEvent(String username) {
+//                     super();
+//                     this.username = username;
+////                   this.loggingEvent = loggingEvent;
 //             }
 //
-//             @Override
-//             public boolean equals(Object obj) {
-//                     return loggingEvent.equals(obj);
+////           @Override
+////           public int hashCode() {
+////                   return loggingEvent.hashCode();
+////           }
+////
+////           @Override
+////           public boolean equals(Object obj) {
+////                   return loggingEvent.equals(obj);
+////           }
+////
+////           @Override
+////           public String toString() {
+////                   return username + "@ " + loggingEvent.toString();
+////           }
+//
+//             public String getUsername() {
+//                     return username;
 //             }
 //
-//             @Override
-//             public String toString() {
-//                     return username + "@ " + loggingEvent.toString();
+////           public LoggingEvent getLoggingEvent() {
+////                   return loggingEvent;
+////           }
+//
+//     }
+//
+//     private class Log4jConfWatcherThread extends Thread {
+//             private Path log4jConfigurationPath;
+//
+//             public Log4jConfWatcherThread(Path log4jConfigurationPath) {
+//                     super("Log4j Configuration Watcher");
+//                     try {
+//                             this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
+//                     } catch (IOException e) {
+//                             this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
+//                             stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
+//                     }
 //             }
-
-               public String getUsername() {
-                       return username;
-               }
-
-//             public LoggingEvent getLoggingEvent() {
-//                     return loggingEvent;
+//
+//             public void run() {
+//                     Path parentDir = log4jConfigurationPath.getParent();
+//                     try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
+//                             parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
+//                             WatchKey wk;
+//                             watching: while ((wk = watchService.take()) != null) {
+//                                     for (WatchEvent<?> event : wk.pollEvents()) {
+//                                             final Path changed = (Path) event.context();
+//                                             if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
+//                                                     if (isInternalDebugEnabled())
+//                                                             debug(log4jConfigurationPath + " has changed, reloading.");
+////                                                   PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
+//                                             }
+//                                     }
+//                                     // reset the key
+//                                     boolean valid = wk.reset();
+//                                     if (!valid) {
+//                                             break watching;
+//                                     }
+//                             }
+//                     } catch (IOException | InterruptedException e) {
+//                             stdErr("Log4j configuration watcher failed: " + e.getMessage());
+//                     }
 //             }
-
-       }
-
-       private class Log4jConfWatcherThread extends Thread {
-               private Path log4jConfigurationPath;
-
-               public Log4jConfWatcherThread(Path log4jConfigurationPath) {
-                       super("Log4j Configuration Watcher");
-                       try {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
-                       } catch (IOException e) {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
-                               stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
-                       }
-               }
-
-               public void run() {
-                       Path parentDir = log4jConfigurationPath.getParent();
-                       try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
-                               parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
-                               WatchKey wk;
-                               watching: while ((wk = watchService.take()) != null) {
-                                       for (WatchEvent<?> event : wk.pollEvents()) {
-                                               final Path changed = (Path) event.context();
-                                               if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
-                                                       if (isInternalDebugEnabled())
-                                                               debug(log4jConfigurationPath + " has changed, reloading.");
-//                                                     PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
-                                               }
-                                       }
-                                       // reset the key
-                                       boolean valid = wk.reset();
-                                       if (!valid) {
-                                               break watching;
-                                       }
-                               }
-                       } catch (IOException | InterruptedException e) {
-                               stdErr("Log4j configuration watcher failed: " + e.getMessage());
-                       }
-               }
-       }
+//     }
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java
deleted file mode 100644 (file)
index deb3304..0000000
+++ /dev/null
@@ -1,377 +0,0 @@
-package org.argeo.cms.internal.osgi;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.runtime.InitUtils;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.naming.AttributesDictionary;
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationEvent;
-import org.osgi.service.cm.ConfigurationListener;
-
-/** Manages the LDIF-based deployment configuration. */
-public class DeployConfig implements ConfigurationListener {
-       private final CmsLog log = CmsLog.getLog(getClass());
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
-       private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
-//     private final DataModels dataModels;
-
-       private boolean isFirstInit = false;
-
-       private final static String ROLES = "roles";
-
-       private ConfigurationAdmin configurationAdmin;
-
-       public DeployConfig() {
-//             this.dataModels = dataModels;
-               // ConfigurationAdmin configurationAdmin =
-//             // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
-//             try {
-//                     if (!isInitialized()) { // first init
-//                             isFirstInit = true;
-//                             firstInit();
-//                     }
-//                     this.configurationAdmin = configurationAdmin;
-////                   init(configurationAdmin, isClean, isFirstInit);
-//             } catch (IOException e) {
-//                     throw new RuntimeException("Could not init deploy configs", e);
-//             }
-               // FIXME check race conditions during initialization
-               // bc.registerService(ConfigurationListener.class, this, null);
-       }
-
-       private void firstInit() throws IOException {
-               log.info("## FIRST INIT ##");
-               Files.createDirectories(deployConfigPath.getParent());
-
-               // FirstInit firstInit = new FirstInit();
-               InitUtils.prepareFirstInitInstanceArea();
-
-               if (!Files.exists(deployConfigPath))
-                       deployConfigs = new TreeMap<>();
-               else// config file could have juste been copied by preparation
-                       try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                               deployConfigs = new LdifParser().read(in);
-                       }
-               save();
-       }
-
-       private void setFromFrameworkProperties(boolean isFirstInit) {
-
-               // user admin
-               List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
-               if (userDirectoryConfigs.size() != 0) {
-                       List<String> activeCns = new ArrayList<>();
-                       for (int i = 0; i < userDirectoryConfigs.size(); i++) {
-                               Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
-                               String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
-                               String cn;
-                               if (CmsConstants.ROLES_BASEDN.equals(baseDn))
-                                       cn = ROLES;
-                               else
-                                       cn = UserAdminConf.baseDnHash(userDirectoryConfig);
-                               activeCns.add(cn);
-                               userDirectoryConfig.put(CmsConstants.CN, cn);
-                               putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
-                       }
-                       // disable others
-                       LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
-                       for (LdapName name : deployConfigs.keySet()) {
-                               if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
-//                                     try {
-                                       Attributes attrs = deployConfigs.get(name);
-                                       String cn = name.getRdn(name.size() - 1).getValue().toString();
-                                       if (!activeCns.contains(cn)) {
-                                               attrs.put(UserAdminConf.disabled.name(), "true");
-                                       }
-//                                     } catch (Exception e) {
-//                                             throw new CmsException("Cannot disable user directory " + name, e);
-//                                     }
-                               }
-                       }
-               }
-
-               // http server
-               Dictionary<String, Object> webServerConfig = InitUtils
-                               .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
-               if (!webServerConfig.isEmpty()) {
-                       // TODO check for other customizers
-//                     webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
-                       putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
-               }
-//             LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
-//             if (deployConfigs.containsKey(defaultHttpServiceDn)) {
-//                     // remove old default configs since we have now to start Jetty servlet bridge
-//                     // indirectly
-//                     deployConfigs.remove(defaultHttpServiceDn);
-//             }
-
-               // SAVE
-               save();
-               //
-
-//             Dictionary<String, Object> webServerConfig = InitUtils
-//                             .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
-       }
-
-       public void start() throws IOException {
-               if (!isInitialized()) { // first init
-                       isFirstInit = true;
-                       firstInit();
-               }
-
-               boolean isClean;
-               try {
-                       Configuration[] confs = configurationAdmin
-                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-                       isClean = confs == null || confs.length == 0;
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot analyse clean state", e);
-               }
-
-               try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                       deployConfigs = new LdifParser().read(in);
-               }
-               if (isClean) {
-                       if (log.isDebugEnabled())
-                               log.debug("Clean state, loading from framework properties...");
-                       setFromFrameworkProperties(isFirstInit);
-                       loadConfigs();
-               }
-               // TODO check consistency if not clean
-       }
-
-       public void stop() {
-
-       }
-
-       public void loadConfigs() throws IOException {
-               // FIXME make it more robust
-               Configuration systemRolesConf = null;
-               LdapName systemRolesDn;
-               try {
-                       // FIXME make it more robust
-                       systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
-                       Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                       LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
-                       if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
-                               if (lastRdn.getType().equals(CmsConstants.CN)) {
-                                       // service
-                                       String pid = lastRdn.getValue().toString();
-                                       Configuration conf = configurationAdmin.getConfiguration(pid);
-                                       AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
-                                       conf.update(dico);
-                               } else {
-                                       // service factory definition
-                               }
-                       } else {
-                               Attributes config = deployConfigs.get(dn);
-                               Attribute disabled = config.get(UserAdminConf.disabled.name());
-                               if (disabled != null)
-                                       continue deployConfigs;
-                               // service factory service
-                               Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
-                               assert beforeLastRdn.getType().equals(CmsConstants.OU);
-                               String factoryPid = beforeLastRdn.getValue().toString();
-                               Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                               if (systemRolesDn.equals(dn)) {
-                                       systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                               } else {
-                                       AttributesDictionary dico = new AttributesDictionary(config);
-                                       conf.update(dico);
-                               }
-                       }
-               }
-
-               // system roles must be last since it triggers node user admin publication
-               if (systemRolesConf == null)
-                       throw new IllegalStateException("System roles are not configured.");
-               systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
-
-       }
-
-       @Override
-       public void configurationEvent(ConfigurationEvent event) {
-               try {
-                       if (ConfigurationEvent.CM_UPDATED == event.getType()) {
-                               Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
-                               LdapName serviceDn = null;
-                               String factoryPid = conf.getFactoryPid();
-                               if (factoryPid != null) {
-                                       LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-                                       if (deployConfigs.containsKey(serviceFactoryDn)) {
-                                               for (LdapName dn : deployConfigs.keySet()) {
-                                                       if (dn.startsWith(serviceFactoryDn)) {
-                                                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                                                               assert lastRdn.getType().equals(CmsConstants.CN);
-                                                               Object value = conf.getProperties().get(lastRdn.getType());
-                                                               assert value != null;
-                                                               if (value.equals(lastRdn.getValue())) {
-                                                                       serviceDn = dn;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-
-                                               Object cn = conf.getProperties().get(CmsConstants.CN);
-                                               if (cn == null)
-                                                       throw new IllegalArgumentException("Properties must contain cn");
-                                               if (serviceDn == null) {
-                                                       putFactoryDeployConfig(factoryPid, conf.getProperties());
-                                               } else {
-                                                       Attributes attrs = deployConfigs.get(serviceDn);
-                                                       assert attrs != null;
-                                                       AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               }
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
-                                       } else {
-                                               // ignore non config-registered service factories
-                                       }
-                               } else {
-                                       serviceDn = serviceDn(event.getPid());
-                                       if (deployConfigs.containsKey(serviceDn)) {
-                                               Attributes attrs = deployConfigs.get(serviceDn);
-                                               assert attrs != null;
-                                               AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn);
-                                       } else {
-                                               // ignore non config-registered services
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Could not handle configuration event", e);
-               }
-       }
-
-       public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               Object cn = props.get(CmsConstants.CN);
-               if (cn == null)
-                       throw new IllegalArgumentException("cn must be set in properties");
-               LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-               if (!deployConfigs.containsKey(serviceFactoryDn))
-                       deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
-               LdapName serviceDn = serviceDn(factoryPid, cn.toString());
-               Attributes attrs = new BasicAttributes();
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
-               LdapName serviceDn = serviceDn(servicePid);
-               Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       public void save() {
-               try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
-                       new LdifWriter(writer).write(deployConfigs);
-               } catch (IOException e) {
-                       // throw new CmsException("Cannot save deploy configs", e);
-                       log.error("Cannot save deploy configs", e);
-               }
-       }
-
-       public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
-               this.configurationAdmin = configurationAdmin;
-       }
-
-       public boolean hasDomain() {
-               Configuration[] configs;
-               try {
-                       configs = configurationAdmin
-                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-               } catch (IOException | InvalidSyntaxException e) {
-                       throw new IllegalStateException("Cannot list user directories", e);
-               }
-
-               boolean hasDomain = false;
-               for (Configuration config : configs) {
-                       Object realm = config.getProperties().get(UserAdminConf.realm.name());
-                       if (realm != null) {
-                               log.debug("Found realm: " + realm);
-                               hasDomain = true;
-                       }
-               }
-               return hasDomain;
-       }
-
-       /*
-        * UTILITIES
-        */
-       private LdapName serviceFactoryDn(String factoryPid) {
-               try {
-                       return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
-               }
-       }
-
-       private LdapName serviceDn(String servicePid) {
-               try {
-                       return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
-               }
-       }
-
-       private LdapName serviceDn(String factoryPid, String cn) {
-               try {
-                       return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
-               }
-       }
-
-       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
-               if (attrs != null)
-                       return new AttributesDictionary(attrs);
-               else
-                       return null;
-       }
-
-       private static boolean isInitialized() {
-               return Files.exists(deployConfigPath);
-       }
-
-       public boolean isFirstInit() {
-               return isFirstInit;
-       }
-
-}
index a8c901644b13e21cb9d63f565efac5832c35d1ea..9de7a4fd13f081497909bc6c08041e321a433400 100644 (file)
@@ -3,7 +3,7 @@ package org.argeo.cms.internal.osgi;
 /**
  * Workaround for killing Gogo shell by system shutdown.
  * 
- * @see https://issues.apache.org/jira/browse/FELIX-4208
+ * @see "https://issues.apache.org/jira/browse/FELIX-4208"
  */
 class GogoShellKiller extends Thread {
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java
deleted file mode 100644 (file)
index 626a057..0000000
+++ /dev/null
@@ -1,403 +0,0 @@
-package org.argeo.cms.internal.osgi;
-
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
-import org.argeo.cms.internal.runtime.KernelConstants;
-import org.argeo.cms.internal.runtime.KernelUtils;
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.osgi.transaction.WorkTransaction;
-import org.argeo.osgi.useradmin.AbstractUserDirectory;
-import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.LdapUserAdmin;
-import org.argeo.osgi.useradmin.LdifUserAdmin;
-import org.argeo.osgi.useradmin.OsUserDirectory;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.argeo.util.naming.DnsBrowser;
-import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
-       private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class);
-
-       // OSGi
-       private Map<String, LdapName> pidToBaseDn = new HashMap<>();
-//     private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
-//     private ServiceRegistration<UserAdmin> userAdminReg;
-
-       // JTA
-//     private final ServiceTracker<WorkControl, WorkControl> tmTracker;
-       // private final String cacheName = UserDirectory.class.getName();
-
-       // GSS API
-       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
-       private GSSCredential acceptorCredentials;
-
-       private boolean singleUser = false;
-//     private boolean systemRolesAvailable = false;
-
-//     CmsUserManagerImpl userManager;
-       private WorkControl transactionManager;
-       private WorkTransaction userTransaction;
-
-       public NodeUserAdmin() {
-               super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
-//             BundleContext bc = Activator.getBundleContext();
-//             if (bc != null) {
-//                     tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
-//
-//                             @Override
-//                             public WorkControl addingService(ServiceReference<WorkControl> reference) {
-//                                     WorkControl workControl = super.addingService(reference);
-//                                     userManager = new CmsUserManagerImpl();
-//                                     userManager.setUserAdmin(NodeUserAdmin.this);
-//                                     // FIXME make it more robust
-//                                     userManager.setUserTransaction((WorkTransaction) workControl);
-//                                     bc.registerService(CmsUserManager.class, userManager, null);
-//                                     return workControl;
-//                             }
-//                     };
-//                     tmTracker.open();
-//             } else {
-//                     tmTracker = null;
-//             }
-       }
-
-       public void start() {
-       }
-
-       public void stop() {
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
-               String uri = (String) properties.get(UserAdminConf.uri.name());
-               Object realm = properties.get(UserAdminConf.realm.name());
-               URI u;
-               try {
-                       if (uri == null) {
-                               String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
-                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
-                       } else if (realm != null) {
-                               u = null;
-                       } else {
-                               u = new URI(uri);
-                       }
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
-               }
-
-               // Create
-               AbstractUserDirectory userDirectory;
-               if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
-                               || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
-                       userDirectory = new LdapUserAdmin(properties);
-               } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
-                       userDirectory = new LdifUserAdmin(u, properties);
-               } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
-                       userDirectory = new OsUserDirectory(u, properties);
-                       singleUser = true;
-               } else {
-                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
-               }
-               LdapName baseDn = userDirectory.getBaseDn();
-
-               // FIXME make updates more robust
-               if (pidToBaseDn.containsValue(baseDn)) {
-                       if (log.isDebugEnabled())
-                               log.debug("Ignoring user directory update of " + baseDn);
-                       return;
-               }
-
-               addUserDirectory(userDirectory);
-
-               // OSGi
-               Hashtable<String, Object> regProps = new Hashtable<>();
-               regProps.put(Constants.SERVICE_PID, pid);
-               if (isSystemRolesBaseDn(baseDn))
-                       regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               regProps.put(UserAdminConf.baseDn.name(), baseDn);
-               // ServiceRegistration<UserDirectory> reg =
-               // bc.registerService(UserDirectory.class, userDirectory, regProps);
-               CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps);
-//             userManager.addUserDirectory(userDirectory, regProps);
-               pidToBaseDn.put(pid, baseDn);
-               // pidToServiceRegs.put(pid, reg);
-
-               if (log.isDebugEnabled()) {
-                       log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
-                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
-               }
-
-               if (isSystemRolesBaseDn(baseDn)) {
-                       addStandardSystemRoles();
-
-                       // publishes itself as user admin only when system roles are available
-                       Dictionary<String, Object> userAdminregProps = new Hashtable<>();
-                       userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT);
-                       userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-                       CmsActivator.getBundleContext().registerService(UserAdmin.class, this, userAdminregProps);
-               }
-
-//             if (isSystemRolesBaseDn(baseDn))
-//                     systemRolesAvailable = true;
-//
-//             // start publishing only when system roles are available
-//             if (systemRolesAvailable) {
-//                     // The list of baseDns is published as properties
-//                     // TODO clients should rather reference USerDirectory services
-//                     if (userAdminReg != null)
-//                             userAdminReg.unregister();
-//                     // register self as main user admin
-//                     Dictionary<String, Object> userAdminregProps = currentState();
-//                     userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
-//                     userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-//                     userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
-//             }
-       }
-
-       private void addStandardSystemRoles() {
-               // we assume UserTransaction is already available (TODO make it more robust)
-               try {
-                       userTransaction.begin();
-                       Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
-                       if (adminRole == null) {
-                               adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
-                       }
-                       if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
-                               Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
-                               userAdminRole.addMember(adminRole);
-                       }
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               // silent
-                       }
-                       throw new IllegalStateException("Cannot add standard system roles", e);
-               }
-       }
-
-       @Override
-       public void deleted(String pid) {
-               // assert pidToServiceRegs.get(pid) != null;
-               assert pidToBaseDn.get(pid) != null;
-               // pidToServiceRegs.remove(pid).unregister();
-               LdapName baseDn = pidToBaseDn.remove(pid);
-               removeUserDirectory(baseDn);
-       }
-
-       @Override
-       public String getName() {
-               return "Node User Admin";
-       }
-
-       @Override
-       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-               if (rawAuthorization.getName() == null) {
-                       sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
-               } else {
-                       sysRoles.add(CmsConstants.ROLE_USER);
-               }
-       }
-
-       protected void postAdd(AbstractUserDirectory userDirectory) {
-               // JTA
-//             WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
-//             if (tm == null)
-//                     throw new IllegalStateException("A JTA transaction manager must be available.");
-               userDirectory.setTransactionControl(transactionManager);
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (Files.exists(nodeKeyTab)) {
-                               String servicePrincipal = getKerberosServicePrincipal(realm.toString());
-                               if (servicePrincipal != null) {
-                                       CallbackHandler callbackHandler = new CallbackHandler() {
-                                               @Override
-                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                                       for (Callback callback : callbacks)
-                                                               if (callback instanceof NameCallback)
-                                                                       ((NameCallback) callback).setName(servicePrincipal);
-
-                                               }
-                                       };
-                                       try {
-                                               LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
-                                               nodeLc.login();
-                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException("Cannot log in kernel", e);
-                                       }
-                               }
-                       }
-
-                       // Register client-side SPNEGO auth scheme
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
-                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
-                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
-               }
-       }
-
-       protected void preDestroy(AbstractUserDirectory userDirectory) {
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (acceptorCredentials != null) {
-                               try {
-                                       acceptorCredentials.dispose();
-                               } catch (GSSException e) {
-                                       // silent
-                               }
-                               acceptorCredentials = null;
-                       }
-               }
-       }
-
-       private String getKerberosServicePrincipal(String realm) {
-               String hostname;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
-                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
-                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
-                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
-                       } else
-                               return null;
-               } catch (Exception e) {
-                       log.warn("Exception when determining kerberos principal", e);
-                       return null;
-               }
-       }
-
-       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
-               // GSS
-               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
-               if (!krb5It.hasNext())
-                       return null;
-               KerberosPrincipal krb5Principal = null;
-               while (krb5It.hasNext()) {
-                       KerberosPrincipal principal = krb5It.next();
-                       if (principal.getName().equals(servicePrincipal))
-                               krb5Principal = principal;
-               }
-
-               if (krb5Principal == null)
-                       return null;
-
-               GSSManager manager = GSSManager.getInstance();
-               try {
-                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
-                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
-
-                               @Override
-                               public GSSCredential run() throws GSSException {
-                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
-                                                       GSSCredential.ACCEPT_ONLY);
-
-                               }
-
-                       });
-                       if (log.isDebugEnabled())
-                               log.debug("GSS acceptor configured for " + krb5Principal);
-                       return serverCredentials;
-               } catch (Exception gsse) {
-                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
-               }
-       }
-
-       public GSSCredential getAcceptorCredentials() {
-               return acceptorCredentials;
-       }
-
-       public boolean hasAcceptorCredentials() {
-               return acceptorCredentials != null;
-       }
-
-       public boolean isSingleUser() {
-               return singleUser;
-       }
-
-       public void setTransactionManager(WorkControl transactionManager) {
-               this.transactionManager = transactionManager;
-       }
-
-       public void setUserTransaction(WorkTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-       /*
-        * STATIC
-        */
-
-       public final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java
new file mode 100644 (file)
index 0000000..c80933a
--- /dev/null
@@ -0,0 +1,149 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+import java.util.function.Consumer;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.RuntimeNamespaceContext;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.dav.DavDepth;
+import org.argeo.cms.dav.DavHttpHandler;
+import org.argeo.cms.dav.DavPropfind;
+import org.argeo.cms.dav.DavResponse;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.internal.http.RemoteAuthHttpExchange;
+import org.argeo.cms.util.StreamUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+
+/** A partial WebDav implementation based on ACR. */
+public class CmsAcrHttpHandler extends DavHttpHandler {
+       private ProvidedRepository contentRepository;
+
+       @Override
+       protected NamespaceContext getNamespaceContext(HttpExchange httpExchange, String path) {
+               // TODO be smarter?
+               return RuntimeNamespaceContext.getNamespaceContext();
+       }
+
+       @Override
+       protected void handleGET(HttpExchange exchange, String path) throws IOException {
+               ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
+                               new RemoteAuthHttpExchange(exchange));
+               if (!session.exists(path)) // not found
+                       throw new ContentNotFoundException(session, path);
+               Content content = session.get(path);
+               Optional<Long> size = content.get(DName.getcontentlength, Long.class);
+               try (InputStream in = content.open(InputStream.class)) {
+                       exchange.sendResponseHeaders(HttpStatus.OK.getCode(), size.orElse(0l));
+                       StreamUtils.copy(in, exchange.getResponseBody());
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot process " + path, e);
+               }
+       }
+
+       @Override
+       protected CompletableFuture<Void> handlePROPFIND(HttpExchange exchange, String path, DavPropfind davPropfind,
+                       Consumer<DavResponse> consumer) throws IOException {
+               ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
+                               new RemoteAuthHttpExchange(exchange));
+               if (!session.exists(path)) // not found
+                       throw new ContentNotFoundException(session, path);
+               Content content = session.get(path);
+
+               CompletableFuture<Void> published = new CompletableFuture<Void>();
+               ForkJoinPool.commonPool().execute(() -> {
+                       publishDavResponses(content, davPropfind, consumer);
+                       published.complete(null);
+               });
+               return published;
+       }
+
+       protected void publishDavResponses(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer) {
+               publishDavResponse(content, davPropfind, consumer, 0);
+       }
+
+       protected void publishDavResponse(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer,
+                       int currentDepth) {
+               DavResponse davResponse = new DavResponse();
+               String href = CmsConstants.PATH_API_ACR + content.getPath();
+               davResponse.setHref(href);
+               if (content.hasContentClass(DName.collection))
+                       davResponse.setCollection(true);
+               if (davPropfind.isAllprop()) {
+                       for (Map.Entry<QName, Object> entry : content.entrySet()) {
+                               davResponse.getPropertyNames(HttpStatus.OK).add(entry.getKey());
+                               processMapEntry(davResponse, entry.getKey(), entry.getValue());
+                       }
+                       davResponse.getResourceTypes().addAll(content.getContentClasses());
+               } else if (davPropfind.isPropname()) {
+                       for (QName key : content.keySet()) {
+                               davResponse.getPropertyNames(HttpStatus.OK).add(key);
+                       }
+               } else {
+                       for (QName key : davPropfind.getProps()) {
+                               if (content.containsKey(key)) {
+                                       davResponse.getPropertyNames(HttpStatus.OK).add(key);
+                                       Object value = content.get(key);
+                                       processMapEntry(davResponse, key, value);
+                               } else {
+                                       davResponse.getPropertyNames(HttpStatus.NOT_FOUND).add(key);
+                               }
+                               if (DName.resourcetype.qName().equals(key)) {
+                                       davResponse.getResourceTypes().addAll(content.getContentClasses());
+                               }
+                       }
+
+               }
+
+               consumer.accept(davResponse);
+
+               // recurse only on collections
+               if (content.hasContentClass(DName.collection)) {
+                       if (davPropfind.getDepth() == DavDepth.DEPTH_INFINITY
+                                       || (davPropfind.getDepth() == DavDepth.DEPTH_1 && currentDepth == 0)) {
+                               for (Content child : content) {
+                                       publishDavResponse(child, davPropfind, consumer, currentDepth + 1);
+                               }
+                       }
+               }
+       }
+
+       protected void processMapEntry(DavResponse davResponse, QName key, Object value) {
+               // ignore content classes
+               if (DName.resourcetype.qName().equals(key))
+                       return;
+               String str;
+               if (value instanceof Collection) {
+                       StringJoiner sj = new StringJoiner("\n");
+                       for (Object v : (Collection<?>) value) {
+                               sj.add(v.toString());
+                       }
+                       str = sj.toString();
+               } else {
+                       str = value.toString();
+               }
+               davResponse.getProperties().put(key, str);
+
+       }
+
+       public void setContentRepository(ProvidedRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
index 8e29f667365e10870da3f4ebf36d873af2d31594..bd54b20594b5e7200d0a3e04f975e8cfc13ab354 100644 (file)
@@ -1,33 +1,42 @@
 package org.argeo.cms.internal.runtime;
 
-import static java.util.Locale.ENGLISH;
-
 import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
-import org.argeo.api.cms.CmsConstants;
+import javax.security.auth.Subject;
+
 import org.argeo.api.cms.CmsContext;
 import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsEventBus;
 import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.CmsState;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.ietf.jgss.GSSCredential;
 import org.osgi.service.useradmin.UserAdmin;
 
+/** Reference implementation of {@link CmsContext}. */
 public class CmsContextImpl implements CmsContext {
+
        private final CmsLog log = CmsLog.getLog(getClass());
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
 
-//     private EgoRepository egoRepository;
        private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
 
        private CmsState cmsState;
        private CmsDeployment cmsDeployment;
        private UserAdmin userAdmin;
+       private UuidFactory uuidFactory;
+       private CmsEventBus cmsEventBus;
 
        // i18n
        private Locale defaultLocale;
@@ -35,38 +44,25 @@ public class CmsContextImpl implements CmsContext {
 
        private Long availableSince;
 
-//     public CmsContextImpl() {
-//             initTrackers();
-//     }
+       // CMS sessions
+       private Map<UUID, CmsSessionImpl> cmsSessionsByUuid = new HashMap<>();
+       private Map<String, CmsSessionImpl> cmsSessionsByLocalId = new HashMap<>();
 
        public void start() {
-               Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
-               defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
-                               : new Locale(ENGLISH.getLanguage());
-               locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
-               // node repository
-//             new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
-//                     @Override
-//                     public Repository addingService(ServiceReference<Repository> reference) {
-//                             Object cn = reference.getProperty(NodeConstants.CN);
-//                             if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
-////                                   egoRepository = (EgoRepository) bc.getService(reference);
-//                                     if (log.isTraceEnabled())
-//                                             log.trace("Home repository is available");
-//                             }
-//                             return super.addingService(reference);
-//                     }
-//
-//                     @Override
-//                     public void removedService(ServiceReference<Repository> reference, Repository service) {
-//                             super.removedService(reference, service);
-////                           egoRepository = null;
-//                     }
-//
-//             }.open();
-
-               checkReadiness();
+               List<String> codes = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.LOCALE);
+               locales = getLocaleList(codes);
+               if (locales.size() == 0)
+                       throw new IllegalStateException("At least one locale must be set");
+               defaultLocale = locales.get(0);
 
+               new Thread(() -> {
+                       while (!checkReadiness()) {
+                               try {
+                                       Thread.sleep(500);
+                               } catch (InterruptedException e) {
+                               }
+                       }
+               }, "Check readiness").start();
                setInstance(this);
        }
 
@@ -78,10 +74,13 @@ public class CmsContextImpl implements CmsContext {
         * Checks whether the deployment is available according to expectations, and
         * mark it as available.
         */
-       private void checkReadiness() {
+       private boolean checkReadiness() {
                if (isAvailable())
-                       return;
-               if (cmsDeployment != null && userAdmin != null) {
+                       return true;
+               if (cmsDeployment == null)
+                       return false;
+
+               if (((CmsDeploymentImpl) cmsDeployment).allExpectedServicesAvailable() && userAdmin != null) {
                        String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
                        String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
                        availableSince = System.currentTimeMillis();
@@ -98,8 +97,11 @@ public class CmsContextImpl implements CmsContext {
                        if (log.isTraceEnabled())
                                log.trace("Kernel initialization took " + initDuration + "ms");
                        tributeToFreeSoftware(initDuration);
+
+                       return true;
                } else {
-                       throw new IllegalStateException("Deployment is not available");
+                       return false;
+                       // throw new IllegalStateException("Deployment is not available");
                }
        }
 
@@ -129,6 +131,29 @@ public class CmsContextImpl implements CmsContext {
                throw new UnsupportedOperationException();
        }
 
+       /** Returns null if argument is null. */
+       private static List<Locale> getLocaleList(List<String> codes) {
+               if (codes == null)
+                       return null;
+               ArrayList<Locale> availableLocales = new ArrayList<Locale>();
+               for (String code : codes) {
+                       if (code == null)
+                               continue;
+                       // variant not supported
+                       int indexUnd = code.indexOf("_");
+                       Locale locale;
+                       if (indexUnd > 0) {
+                               String language = code.substring(0, indexUnd);
+                               String country = code.substring(indexUnd + 1);
+                               locale = new Locale(language, country);
+                       } else {
+                               locale = new Locale(code);
+                       }
+                       availableLocales.add(locale);
+               }
+               return availableLocales;
+       }
+
        public void setCmsDeployment(CmsDeployment cmsDeployment) {
                this.cmsDeployment = cmsDeployment;
        }
@@ -141,56 +166,78 @@ public class CmsContextImpl implements CmsContext {
                this.userAdmin = userAdmin;
        }
 
+       public UuidFactory getUuidFactory() {
+               return uuidFactory;
+       }
+
+       public void setUuidFactory(UuidFactory uuidFactory) {
+               this.uuidFactory = uuidFactory;
+       }
+
        @Override
        public Locale getDefaultLocale() {
                return defaultLocale;
        }
 
+       @Override
+       public UUID timeUUID() {
+               return uuidFactory.timeUUID();
+       }
+
        @Override
        public List<Locale> getLocales() {
                return locales;
        }
 
        @Override
-       public synchronized Long getAvailableSince() {
+       public Long getAvailableSince() {
                return availableSince;
        }
 
-       public synchronized boolean isAvailable() {
+       public boolean isAvailable() {
                return availableSince != null;
        }
 
+       public CmsState getCmsState() {
+               return cmsState;
+       }
+
+       @Override
+       public CmsEventBus getCmsEventBus() {
+               return cmsEventBus;
+       }
+
+       public void setCmsEventBus(CmsEventBus cmsEventBus) {
+               this.cmsEventBus = cmsEventBus;
+       }
+
        /*
         * STATIC
         */
 
-       public synchronized static CmsContext getCmsContext() {
+       public static CmsContextImpl getCmsContext() {
                return getInstance();
        }
 
-       /** Required by USER login module. */
-       public synchronized static UserAdmin getUserAdmin() {
-               return getInstance().userAdmin;
-       }
-
        /** Required by SPNEGO login module. */
-       @Deprecated
-       public synchronized static GSSCredential getAcceptorCredentials() {
-               // FIXME find a cleaner way
-               return ((NodeUserAdmin) getInstance().userAdmin).getAcceptorCredentials();
+       public GSSCredential getAcceptorCredentials() {
+               // TODO find a cleaner way
+               return ((CmsUserAdmin) userAdmin).getAcceptorCredentials();
        }
 
-       private synchronized static void setInstance(CmsContextImpl cmsContextImpl) {
+       private static void setInstance(CmsContextImpl cmsContextImpl) {
                if (cmsContextImpl != null) {
                        if (instance.isDone())
                                throw new IllegalStateException("CMS Context is already set");
                        instance.complete(cmsContextImpl);
                } else {
+                       if (!instance.isDone())
+                               instance.cancel(true);
                        instance = new CompletableFuture<CmsContextImpl>();
                }
        }
 
-       private synchronized static CmsContextImpl getInstance() {
+       private static CmsContextImpl getInstance() {
                try {
                        return instance.get();
                } catch (InterruptedException | ExecutionException e) {
@@ -198,4 +245,53 @@ public class CmsContextImpl implements CmsContext {
                }
        }
 
+       public UserAdmin getUserAdmin() {
+               return userAdmin;
+       }
+
+       /*
+        * CMS Sessions
+        */
+
+       @Override
+       public CmsSession getCmsSession(Subject subject) {
+               if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
+                       return null;
+               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
+               return getCmsSessionByUuid(cmsSessionId.getUuid());
+       }
+
+       public void registerCmsSession(CmsSessionImpl cmsSession) {
+               if (cmsSessionsByUuid.containsKey(cmsSession.getUuid())
+                               || cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
+                       throw new IllegalStateException("CMS session " + cmsSession + " is already registered.");
+               cmsSessionsByUuid.put(cmsSession.getUuid(), cmsSession);
+               cmsSessionsByLocalId.put(cmsSession.getLocalId(), cmsSession);
+       }
+
+       public void unregisterCmsSession(CmsSessionImpl cmsSession) {
+               if (!cmsSessionsByUuid.containsKey(cmsSession.getUuid())
+                               || !cmsSessionsByLocalId.containsKey(cmsSession.getLocalId()))
+                       throw new IllegalStateException("CMS session " + cmsSession + " is not registered.");
+               CmsSession removed = cmsSessionsByUuid.remove(cmsSession.getUuid());
+               assert removed == cmsSession;
+               cmsSessionsByLocalId.remove(cmsSession.getLocalId());
+       }
+
+       /**
+        * The {@link CmsSession} related to this UUID, or <code>null</null> if not
+        * registered.
+        */
+       public CmsSessionImpl getCmsSessionByUuid(UUID uuid) {
+               return cmsSessionsByUuid.get(uuid);
+       }
+
+       /**
+        * The {@link CmsSession} related to this local id, or <code>null</null> if not
+        * registered.
+        */
+       public CmsSessionImpl getCmsSessionByLocalId(String localId) {
+               return cmsSessionsByLocalId.get(localId);
+       }
+
 }
index 4ffa03a63fffe2ff37d4f18db8494b849275c97a..e2d1fb97a592d38e6a64e85918353dbb5b49ce6a 100644 (file)
 package org.argeo.cms.internal.runtime;
 
-import java.io.IOException;
-import java.net.URL;
-import java.util.Dictionary;
+import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH;
 
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsDeployment;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
-import org.argeo.cms.internal.osgi.DeployConfig;
-import org.osgi.service.http.HttpService;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.CmsSshd;
+import org.argeo.cms.internal.http.CmsAuthenticator;
+import org.argeo.cms.internal.http.PublicCmsAuthenticator;
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
 
-/** Implementation of a CMS deployment. */
+/** Reference implementation of {@link CmsDeployment}. */
 public class CmsDeploymentImpl implements CmsDeployment {
        private final CmsLog log = CmsLog.getLog(getClass());
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
 
-//     private Long availableSince;
+       private CmsState cmsState;
 
-       // Readiness
-//     private boolean nodeAvailable = false;
-//     private boolean userAdminAvailable = false;
+       // Expectations
        private boolean httpExpected = false;
-//     private boolean httpAvailable = false;
-       private HttpService httpService;
+       private boolean sshdExpected = false;
 
-       private CmsState cmsState;
-       private DeployConfig deployConfig;
-
-       public CmsDeploymentImpl() {
-//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
-//             if (nodeStateSr == null)
-//                     throw new CmsException("No node state available");
+       // HTTP
+       private HttpServer httpServer;
+       private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
+       private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
 
-//             NodeState nodeState = bc.getService(nodeStateSr);
-//             cleanState = nodeState.isClean();
+       // SSHD
+       private CmsSshd cmsSshd;
 
-//             nodeHttp = new NodeHttp();
-               initTrackers();
+       public void start() {
+               log.debug(() -> "CMS deployment available");
        }
 
-       private void initTrackers() {
-//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-//
-//                     @Override
-//                     public HttpService addingService(ServiceReference<HttpService> sr) {
-//                             httpAvailable = true;
-//                             Object httpPort = sr.getProperty("http.port");
-//                             Object httpsPort = sr.getProperty("https.port");
-//                             log.info(httpPortsMsg(httpPort, httpsPort));
-//                             checkReadiness();
-//                             return super.addingService(sr);
-//                     }
-//             };
-//             // httpSt.open();
-//             KernelUtils.asyncOpen(httpSt);
-
-//             ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
-//                     @Override
-//                     public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
-//                             UserAdmin userAdmin = super.addingService(reference);
-//                             addStandardSystemRoles(userAdmin);
-//                             userAdminAvailable = true;
-//                             checkReadiness();
-//                             return userAdmin;
-//                     }
-//             };
-//             // userAdminSt.open();
-//             KernelUtils.asyncOpen(userAdminSt);
-
-//             ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
-//                             ConfigurationAdmin.class, null) {
-//                     @Override
-//                     public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
-//                             ConfigurationAdmin configurationAdmin = bc.getService(reference);
-////                           boolean isClean;
-////                           try {
-////                                   Configuration[] confs = configurationAdmin
-////                                                   .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-////                                   isClean = confs == null || confs.length == 0;
-////                           } catch (Exception e) {
-////                                   throw new IllegalStateException("Cannot analyse clean state", e);
-////                           }
-//                             deployConfig = new DeployConfig(configurationAdmin, isClean);
-//                             Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
-////                           JcrInitUtils.addToDeployment(CmsDeployment.this);
-//                             httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-//                             try {
-//                                     Configuration[] configs = configurationAdmin
-//                                                     .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
-//
-//                                     boolean hasDomain = false;
-//                                     for (Configuration config : configs) {
-//                                             Object realm = config.getProperties().get(UserAdminConf.realm.name());
-//                                             if (realm != null) {
-//                                                     log.debug("Found realm: " + realm);
-//                                                     hasDomain = true;
-//                                             }
-//                                     }
-//                                     if (hasDomain) {
-//                                             loadIpaJaasConfiguration();
-//                                     }
-//                             } catch (Exception e) {
-//                                     throw new IllegalStateException("Cannot initialize config", e);
-//                             }
-//                             return super.addingService(reference);
-//                     }
-//             };
-//             // confAdminSt.open();
-//             KernelUtils.asyncOpen(confAdminSt);
+       public void stop() {
        }
 
-       public void start() {
-               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-               if (deployConfig.hasDomain()) {
-                       loadIpaJaasConfiguration();
-               }
-
-//             while (!isHttpAvailableOrNotExpected()) {
-//                     try {
-//                             Thread.sleep(100);
-//                     } catch (InterruptedException e) {
-//                             log.error("Interrupted while waiting for http");
-//                     }
-//             }
-       }
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
 
-       public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               deployConfig.putFactoryDeployConfig(factoryPid, props);
-               deployConfig.save();
-               try {
-                       deployConfig.loadConfigs();
-               } catch (IOException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
+               String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
+               String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
+               httpExpected = httpPort != null || httpsPort != null;
 
-       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               return deployConfig.getProps(factoryPid, cn);
+               String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+               sshdExpected = sshdPort != null;
        }
 
-//     private void addStandardSystemRoles(UserAdmin userAdmin) {
-//             // we assume UserTransaction is already available (TODO make it more robust)
-//             WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
-//             try {
-//                     userTransaction.begin();
-//                     Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
-//                     if (adminRole == null) {
-//                             adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
-//                     }
-//                     if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
-//                             Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
-//                             userAdminRole.addMember(adminRole);
-//                     }
-//                     userTransaction.commit();
-//             } catch (Exception e) {
-//                     try {
-//                             userTransaction.rollback();
-//                     } catch (Exception e1) {
-//                             // silent
-//                     }
-//                     throw new IllegalStateException("Cannot add standard system roles", e);
-//             }
-//     }
-
-       public boolean isHttpAvailableOrNotExpected() {
-               return (httpExpected ? httpService != null : true);
+       public void setHttpServer(HttpServer httpServer) {
+               this.httpServer = httpServer;
+               // create contexts whose handles had already been published
+               for (String contextPath : httpHandlers.keySet()) {
+                       HttpHandler httpHandler = httpHandlers.get(contextPath);
+                       CmsAuthenticator authenticator = httpAuthenticators.get(contextPath);
+                       createHttpContext(contextPath, httpHandler, authenticator);
+               }
        }
 
-       private void loadIpaJaasConfiguration() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
-                       URL url = getClass().getClassLoader().getResource(jaasConfig);
-                       KernelUtils.setJaasConfiguration(url);
-                       log.debug("Set IPA JAAS configuration.");
+       public void addHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+               final String contextPath = properties.get(CONTEXT_PATH);
+               if (contextPath == null) {
+                       log.warn("Property " + CONTEXT_PATH + " not set on HTTP handler " + properties + ". Ignoring it.");
+                       return;
+               }
+               boolean isPublic = Boolean.parseBoolean(properties.get(CmsConstants.CONTEXT_PUBLIC));
+               CmsAuthenticator authenticator = isPublic ? new PublicCmsAuthenticator() : new CmsAuthenticator();
+               httpHandlers.put(contextPath, httpHandler);
+               httpAuthenticators.put(contextPath, authenticator);
+               if (httpServer == null) {
+                       return;
+               } else {
+                       createHttpContext(contextPath, httpHandler, authenticator);
                }
        }
 
-       public void stop() {
-//             if (nodeHttp != null)
-//                     nodeHttp.destroy();
-
-//             try {
-//                     JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
-//             } catch (Exception e) {
-//                     log.error("Cannot stop default Jetty server.", e);
-//             }
-
-               if (deployConfig != null) {
-                       deployConfig.save();
-                       // new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
-               }
+       public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
+               HttpContext httpContext = httpServer.createContext(contextPath);
+               // we want to set the authenticator BEFORE the handler actually becomes active
+               httpContext.setAuthenticator(authenticator);
+               httpContext.setHandler(httpHandler);
+               log.debug(() -> "Added handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
 
-       public void setDeployConfig(DeployConfig deployConfig) {
-               this.deployConfig = deployConfig;
+       public void removeHttpHandler(HttpHandler httpHandler, Map<String, String> properties) {
+               final String contextPath = properties.get(CmsConstants.CONTEXT_PATH);
+               if (contextPath == null)
+                       return; // ignore silently
+               httpHandlers.remove(contextPath);
+               if (httpServer == null)
+                       return;
+               httpServer.removeContext(contextPath);
+               log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
        }
 
-       public void setCmsState(CmsState cmsState) {
-               this.cmsState = cmsState;
+       public boolean allExpectedServicesAvailable() {
+               if (httpExpected && httpServer == null)
+                       return false;
+               if (sshdExpected && cmsSshd == null)
+                       return false;
+               return true;
        }
 
-       public void setHttpService(HttpService httpService) {
-               this.httpService = httpService;
+       public void setCmsSshd(CmsSshd cmsSshd) {
+               this.cmsSshd = cmsSshd;
        }
 
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsEventBusImpl.java
new file mode 100644 (file)
index 0000000..99f6c1d
--- /dev/null
@@ -0,0 +1,101 @@
+package org.argeo.cms.internal.runtime;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.SubmissionPublisher;
+
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsEventSubscriber;
+import org.argeo.api.cms.CmsLog;
+
+/** {@link CmsEventBus} implementation based on {@link Flow}. */
+public class CmsEventBusImpl implements CmsEventBus {
+       private final CmsLog log = CmsLog.getLog(CmsEventBus.class);
+
+       private Map<String, SubmissionPublisher<Map<String, Object>>> topics = new TreeMap<>();
+
+       @Override
+       public void sendEvent(String topic, Map<String, Object> event) {
+               SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+               if (publisher == null)
+                       return; // no one is interested
+               publisher.submit(event);
+       }
+
+       @Override
+       public void addEventSubscriber(String topic, CmsEventSubscriber subscriber) {
+               synchronized (topics) {
+                       if (!topics.containsKey(topic))
+                               topics.put(topic, new SubmissionPublisher<>());
+               }
+               SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+               CmsEventFlowSubscriber flowSubscriber = new CmsEventFlowSubscriber(topic, subscriber);
+               publisher.subscribe(flowSubscriber);
+       }
+
+       @Override
+       public void removeEventSubscriber(String topic, CmsEventSubscriber subscriber) {
+               SubmissionPublisher<Map<String, Object>> publisher = topics.get(topic);
+               if (publisher == null) {
+                       log.error("There should be an event topic " + topic);
+                       return;
+               }
+               for (Flow.Subscriber<? super Map<String, Object>> flowSubscriber : publisher.getSubscribers()) {
+                       if (flowSubscriber instanceof CmsEventFlowSubscriber)
+                               ((CmsEventFlowSubscriber) flowSubscriber).unsubscribe();
+               }
+               synchronized (topics) {
+                       if (!publisher.hasSubscribers()) {
+                               publisher.close();
+                               topics.remove(topic);
+                       }
+               }
+       }
+
+       /** A subscriber to a topic. */
+       static class CmsEventFlowSubscriber implements Flow.Subscriber<Map<String, Object>> {
+               private String topic;
+               private CmsEventSubscriber eventSubscriber;
+
+               private Subscription subscription;
+
+               public CmsEventFlowSubscriber(String topic, CmsEventSubscriber eventSubscriber) {
+                       this.topic = topic;
+                       this.eventSubscriber = eventSubscriber;
+               }
+
+               @Override
+               public void onSubscribe(Subscription subscription) {
+                       this.subscription = subscription;
+                       this.subscription.request(Long.MAX_VALUE);
+               }
+
+               @Override
+               public void onNext(Map<String, Object> item) {
+                       eventSubscriber.onEvent(topic, item);
+               }
+
+               @Override
+               public void onError(Throwable throwable) {
+                       // TODO Auto-generated method stub
+
+               }
+
+               @Override
+               public void onComplete() {
+                       // TODO Auto-generated method stub
+
+               }
+
+               void unsubscribe() {
+                       if (subscription != null)
+                               subscription.cancel();
+                       else
+                               throw new IllegalStateException("No subscription to cancel");
+               }
+
+       }
+
+}
index a0c4b0c0b8a445346e34921d3d3c3a29b7fdab81..5c3838a0a3134a4a6e24e202ecd20e0c992e8711 100644 (file)
@@ -1,15 +1,44 @@
 package org.argeo.cms.internal.runtime;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.Reader;
 import java.net.InetAddress;
 import java.net.URL;
 import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 import javax.security.auth.login.Configuration;
 
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsState;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsDeployProperty;
 import org.argeo.cms.auth.ident.IdentClient;
-import org.osgi.framework.Constants;
+import org.argeo.cms.util.FsUtils;
 
 /**
  * Implementation of a {@link CmsState}, initialising the required services.
@@ -20,10 +49,47 @@ public class CmsStateImpl implements CmsState {
        // REFERENCES
        private Long availableSince;
 
-       private String stateUuid;
+       private UUID uuid;
 //     private final boolean cleanState;
        private String hostname;
 
+       private UuidFactory uuidFactory;
+
+       private final Map<CmsDeployProperty, String> deployPropertyDefaults;
+
+       public CmsStateImpl() {
+               this.deployPropertyDefaults = Collections.unmodifiableMap(createDeployPropertiesDefaults());
+       }
+
+       protected Map<CmsDeployProperty, String> createDeployPropertiesDefaults() {
+               Map<CmsDeployProperty, String> deployPropertyDefaults = new HashMap<>();
+               deployPropertyDefaults.put(CmsDeployProperty.NODE_INIT, "../../init");
+               deployPropertyDefaults.put(CmsDeployProperty.LOCALE, Locale.getDefault().toString());
+
+               // certificates
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORETYPE, KernelConstants.PKCS12);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_PASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
+               Path keyStorePath = getDataPath(KernelConstants.DEFAULT_KEYSTORE_PATH);
+               if (keyStorePath != null) {
+                       deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORE, keyStorePath.toAbsolutePath().toString());
+               }
+
+               Path trustStorePath = getDataPath(KernelConstants.DEFAULT_TRUSTSTORE_PATH);
+               if (trustStorePath != null) {
+                       deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORE, trustStorePath.toAbsolutePath().toString());
+               }
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORETYPE, KernelConstants.PKCS12);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
+
+               // SSH
+               Path authorizedKeysPath = getDataPath(KernelConstants.NODE_SSHD_AUTHORIZED_KEYS_PATH);
+               if (authorizedKeysPath != null) {
+                       deployPropertyDefaults.put(CmsDeployProperty.SSHD_AUTHORIZEDKEYS,
+                                       authorizedKeysPath.toAbsolutePath().toString());
+               }
+               return deployPropertyDefaults;
+       }
+
        public void start() {
 //             Runtime.getRuntime().addShutdownHook(new CmsShutdown());
 
@@ -34,30 +100,85 @@ public class CmsStateImpl implements CmsState {
                        if (log.isTraceEnabled())
                                log.trace("CMS State started");
 
-                       this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+//                     String stateUuidStr = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+//                     this.uuid = UUID.fromString(stateUuidStr);
+                       this.uuid = uuidFactory.timeUUID();
 //             this.cleanState = stateUuid.equals(frameworkUuid);
-                       try {
-                               this.hostname = InetAddress.getLocalHost().getHostName();
-                       } catch (UnknownHostException e) {
-                               log.error("Cannot set hostname: " + e);
+
+                       // hostname
+                       this.hostname = getDeployProperty(CmsDeployProperty.HOST);
+                       // TODO verify we have access to the IP address
+                       if (hostname == null) {
+                               final String LOCALHOST_IP = "::1";
+                               ForkJoinTask<String> hostnameFJT = ForkJoinPool.commonPool().submit(() -> {
+                                       try {
+                                               String hostname = InetAddress.getLocalHost().getHostName();
+                                               return hostname;
+                                       } catch (UnknownHostException e) {
+                                               throw new IllegalStateException("Cannot get local hostname", e);
+                                       }
+                               });
+                               try {
+                                       this.hostname = hostnameFJT.get(5, TimeUnit.SECONDS);
+                               } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                                       this.hostname = LOCALHOST_IP;
+                                       log.warn("Could not get local hostname, using " + this.hostname);
+                               }
                        }
 
                        availableSince = System.currentTimeMillis();
-                       if (log.isDebugEnabled())
+                       if (log.isDebugEnabled()) {
                                // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
                                // (clean state) " : " "));
-                               log.debug("## CMS starting... (" + stateUuid + ")");
+                               StringJoiner sb = new StringJoiner("\n");
+                               CmsDeployProperty[] deployProperties = CmsDeployProperty.values();
+                               Arrays.sort(deployProperties, (o1, o2) -> o1.name().compareTo(o2.name()));
+                               for (CmsDeployProperty deployProperty : deployProperties) {
+                                       List<String> values = getDeployProperties(deployProperty);
+                                       for (int i = 0; i < values.size(); i++) {
+                                               String value = values.get(i);
+                                               if (value != null) {
+                                                       boolean isDefault = deployPropertyDefaults.containsKey(deployProperty)
+                                                                       && value.equals(deployPropertyDefaults.get(deployProperty));
+                                                       String line = deployProperty.getProperty() + (i == 0 ? "" : "." + i) + "=" + value
+                                                                       + (isDefault ? " (default)" : "");
+                                                       sb.add(line);
+                                               }
+                                       }
+                               }
+                               log.debug("## CMS starting... (" + uuid + ")\n" + sb + "\n");
+                       }
 
-//             initI18n();
-//             initServices();
+                       Path privateBase = getDataPath(KernelConstants.DIR_PRIVATE);
+                       if (privateBase != null && !Files.exists(privateBase)) {// first init
+                               firstInit();
+                               Files.createDirectories(privateBase);
+                       }
 
-               } catch (RuntimeException e) {
-                       log.error("## FATAL: CMS activator failed", e);
+               } catch (RuntimeException | IOException e) {
+                       log.error("## FATAL: CMS state failed", e);
                }
        }
 
        private void initSecurity() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+               // private directory permissions
+               Path privateDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_PRIVATE);
+               if (privateDir != null) {
+                       // TODO rather check whether we can read and write
+                       Set<PosixFilePermission> posixPermissions = new HashSet<>();
+                       posixPermissions.add(PosixFilePermission.OWNER_READ);
+                       posixPermissions.add(PosixFilePermission.OWNER_WRITE);
+                       posixPermissions.add(PosixFilePermission.OWNER_EXECUTE);
+                       try {
+                               if (!Files.exists(privateDir))
+                                       Files.createDirectories(privateDir);
+                               Files.setPosixFilePermissions(privateDir, posixPermissions);
+                       } catch (IOException e) {
+                               log.error("Cannot set permissions on " + privateDir, e);
+                       }
+               }
+
+               if (getDeployProperty(CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) {
                        String jaasConfig = KernelConstants.JAAS_CONFIG;
                        URL url = getClass().getResource(jaasConfig);
                        // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
@@ -66,16 +187,193 @@ public class CmsStateImpl implements CmsState {
                }
                // explicitly load JAAS configuration
                Configuration.getConfiguration();
+
+               boolean initSsl = getDeployProperty(CmsDeployProperty.HTTPS_PORT) != null;
+               if (initSsl) {
+                       initCertificates();
+               }
+       }
+
+       private void initCertificates() {
+               // server certificate
+               Path keyStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
+               Path pemKeyPath = getDataPath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+               Path pemCertPath = getDataPath(KernelConstants.DEFAULT_PEM_CERT_PATH);
+               char[] keyStorePassword = getDeployProperty(CmsDeployProperty.SSL_PASSWORD).toCharArray();
+
+               // Keystore
+               // if PEM files both exists, update the PKCS12 file
+               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
+                       // TODO check certificate update time? monitor changes?
+                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword,
+                                       getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
+                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
+                                       BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(pemCertPath));) {
+                               PkiUtils.loadPrivateCertificatePem(keyStore, CmsConstants.NODE, key, keyStorePassword, cert);
+                               Files.createDirectories(keyStorePath.getParent());
+                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("PEM certificate stored in " + keyStorePath);
+                       } catch (IOException e) {
+                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
+                       }
+               }
+
+               // Truststore
+               Path trustStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORE));
+               char[] trustStorePassword = getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD).toCharArray();
+
+               // IPA CA
+               Path ipaCaCertPath = Paths.get(KernelConstants.IPA_PEM_CA_CERT_PATH);
+               if (Files.exists(ipaCaCertPath)) {
+                       KeyStore trustStore = PkiUtils.getKeyStore(trustStorePath, trustStorePassword,
+                                       getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                       try (BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(ipaCaCertPath));) {
+                               PkiUtils.loadTrustedCertificatePem(trustStore, trustStorePassword, cert);
+                               Files.createDirectories(keyStorePath.getParent());
+                               PkiUtils.saveKeyStore(trustStorePath, trustStorePassword, trustStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("IPA CA certificate stored in " + trustStorePath);
+                       } catch (IOException e) {
+                               log.error("Cannot trust CA certificate", e);
+                       }
+               }
+
+//             if (!Files.exists(keyStorePath))
+//                     PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
        }
 
        public void stop() {
                if (log.isDebugEnabled())
-                       log.debug("CMS stopping...  (" + this.stateUuid + ")");
+                       log.debug("CMS stopping...  (" + this.uuid + ")");
 
                long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
                log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
        }
 
+       private void firstInit() throws IOException {
+               log.info("## FIRST INIT ##");
+               List<String> nodeInits = getDeployProperties(CmsDeployProperty.NODE_INIT);
+//             if (nodeInits == null)
+//                     nodeInits = "../../init";
+               CmsStateImpl.prepareFirstInitInstanceArea(nodeInits);
+       }
+
+       @Override
+       public String getDeployProperty(String property) {
+               CmsDeployProperty deployProperty = CmsDeployProperty.find(property);
+               if (deployProperty == null) {
+                       // legacy
+                       if (property.startsWith("argeo.node.")) {
+                               return doGetDeployProperty(property);
+                       }
+                       if (property.equals("argeo.i18n.locales")) {
+                               String value = doGetDeployProperty(property);
+                               if (value != null) {
+                                       log.warn("Property " + property + " was ignored (value=" + value + ")");
+
+                               }
+                               return null;
+                       }
+                       throw new IllegalArgumentException("Unsupported deploy property " + property);
+               }
+               int index = CmsDeployProperty.getPropertyIndex(property);
+               return getDeployProperty(deployProperty, index);
+       }
+
+       @Override
+       public List<String> getDeployProperties(String property) {
+               CmsDeployProperty deployProperty = CmsDeployProperty.find(property);
+               if (deployProperty == null)
+                       return new ArrayList<>();
+               return getDeployProperties(deployProperty);
+       }
+
+       public static List<String> getDeployProperties(CmsState cmsState, CmsDeployProperty deployProperty) {
+               return ((CmsStateImpl) cmsState).getDeployProperties(deployProperty);
+       }
+
+       public List<String> getDeployProperties(CmsDeployProperty deployProperty) {
+               List<String> res = new ArrayList<>(deployProperty.getMaxCount());
+               for (int i = 0; i < deployProperty.getMaxCount(); i++) {
+                       // String propertyName = i == 0 ? deployProperty.getProperty() :
+                       // deployProperty.getProperty() + "." + i;
+                       String value = getDeployProperty(deployProperty, i);
+                       res.add(i, value);
+               }
+               return res;
+       }
+
+       public static String getDeployProperty(CmsState cmsState, CmsDeployProperty deployProperty) {
+               return ((CmsStateImpl) cmsState).getDeployProperty(deployProperty);
+       }
+
+       public String getDeployProperty(CmsDeployProperty deployProperty) {
+               String value = getDeployProperty(deployProperty, 0);
+               return value;
+       }
+
+       public String getDeployProperty(CmsDeployProperty deployProperty, int index) {
+               String propertyName = deployProperty.getProperty() + (index == 0 ? "" : "." + index);
+               String value = doGetDeployProperty(propertyName);
+               if (value == null && index == 0) {
+                       // try defaults
+                       if (deployPropertyDefaults.containsKey(deployProperty)) {
+                               value = deployPropertyDefaults.get(deployProperty);
+                               if (deployProperty.isSystemPropertyOnly())
+                                       System.setProperty(deployProperty.getProperty(), value);
+                       }
+
+                       if (value == null) {
+                               // try legacy properties
+                               String legacyProperty = switch (deployProperty) {
+                               case DIRECTORY -> "argeo.node.useradmin.uris";
+                               case DB_URL -> "argeo.node.dburl";
+                               case DB_USER -> "argeo.node.dbuser";
+                               case DB_PASSWORD -> "argeo.node.dbpassword";
+                               case HTTP_PORT -> "org.osgi.service.http.port";
+                               case HTTPS_PORT -> "org.osgi.service.http.port.secure";
+                               case HOST -> "org.eclipse.equinox.http.jetty.http.host";
+                               case LOCALE -> "argeo.i18n.defaultLocale";
+
+                               default -> null;
+                               };
+                               if (legacyProperty != null) {
+                                       value = doGetDeployProperty(legacyProperty);
+                                       if (value != null) {
+                                               log.warn("Retrieved deploy property " + deployProperty.getProperty()
+                                                               + " through deprecated property " + legacyProperty);
+                                       }
+                               }
+                       }
+               }
+               if (index == 0 && deployProperty.isSystemPropertyOnly()) {
+                       String systemPropertyValue = System.getProperty(deployProperty.getProperty());
+                       if (!Objects.equals(value, systemPropertyValue))
+                               throw new IllegalStateException(
+                                               "Property " + deployProperty + " must be a ssystem property, but its value is " + value
+                                                               + ", while the system property value is " + systemPropertyValue);
+               }
+               return value != null ? value.toString() : null;
+       }
+
+       protected String getLegacyProperty(String legacyProperty, CmsDeployProperty deployProperty) {
+               String value = doGetDeployProperty(legacyProperty);
+               if (value != null) {
+                       log.warn("Retrieved deploy property " + deployProperty.getProperty() + " through deprecated property "
+                                       + legacyProperty + ".");
+               }
+               return value;
+       }
+
+       protected String doGetDeployProperty(String property) {
+               return KernelUtils.getFrameworkProp(property);
+       }
+
+       @Override
+       public Path getDataPath(String relativePath) {
+               return KernelUtils.getOsgiInstancePath(relativePath);
+       }
 
        @Override
        public Long getAvailableSince() {
@@ -85,10 +383,50 @@ public class CmsStateImpl implements CmsState {
        /*
         * ACCESSORS
         */
+       @Override
+       public UUID getUuid() {
+               return uuid;
+       }
+
+       public void setUuidFactory(UuidFactory uuidFactory) {
+               this.uuidFactory = uuidFactory;
+       }
+
        public String getHostname() {
                return hostname;
        }
 
+       /**
+        * Called before node initialisation, in order populate OSGi instance are with
+        * some files (typically LDIF, etc).
+        */
+       public static void prepareFirstInitInstanceArea(List<String> nodeInits) {
+
+               for (String nodeInit : nodeInits) {
+                       if (nodeInit == null)
+                               continue;
+
+                       if (nodeInit.startsWith("http")) {
+                               // TODO reconnect it
+                               // registerRemoteInit(nodeInit);
+                       } else {
+
+                               // TODO use java.nio.file
+                               Path initDir;
+                               if (nodeInit.startsWith("."))
+                                       initDir = KernelUtils.getExecutionDir(nodeInit);
+                               else
+                                       initDir = Paths.get(nodeInit);
+                               // TODO also uncompress archives
+                               if (Files.exists(initDir)) {
+                                       Path dataPath = KernelUtils.getOsgiInstancePath("");
+                                       FsUtils.copyDirectory(initDir, dataPath);
+                                       log.info("CMS initialized from " + initDir);
+                               }
+                       }
+               }
+       }
+
        /*
         * STATIC
         */
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java
new file mode 100644 (file)
index 0000000..e6f903d
--- /dev/null
@@ -0,0 +1,414 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.dns.DnsBrowser;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.DirectoryUserAdmin;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class CmsUserAdmin extends AggregatingUserAdmin {
+       private final static CmsLog log = CmsLog.getLog(CmsUserAdmin.class);
+
+       // GSS API
+       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
+       private GSSCredential acceptorCredentials;
+
+       private boolean singleUser = false;
+
+       private WorkControl transactionManager;
+       private WorkTransaction userTransaction;
+
+       private CmsState cmsState;
+
+       public CmsUserAdmin() {
+               super(CmsConstants.SYSTEM_ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+       }
+
+       public void start() {
+               super.start();
+               List<Dictionary<String, Object>> configs = getUserDirectoryConfigs();
+               for (Dictionary<String, Object> config : configs) {
+                       enableUserDirectory(config);
+//                     if (userDirectory.getRealm().isPresent())
+//                             loadIpaJaasConfiguration();
+               }
+               log.debug(() -> "CMS user admin available");
+       }
+
+       public void stop() {
+//             for (UserDirectory userDirectory : getUserDirectories()) {
+//                     removeUserDirectory(userDirectory);
+//             }
+               super.stop();
+       }
+
+       protected List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+               List<Dictionary<String, Object>> res = new ArrayList<>();
+               Path nodeBase = cmsState.getDataPath(KernelConstants.DIR_PRIVATE);
+               List<String> uris = new ArrayList<>();
+
+               // node roles
+               String nodeRolesUri = null;// getFrameworkProp(CmsConstants.ROLES_URI);
+               String baseNodeRoleDn = CmsConstants.SYSTEM_ROLES_BASEDN;
+               if (nodeRolesUri == null && nodeBase != null) {
+                       nodeRolesUri = baseNodeRoleDn + ".ldif";
+                       Path nodeRolesFile = nodeBase.resolve(nodeRolesUri);
+                       if (!Files.exists(nodeRolesFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeRoleDn + ".ldif"), nodeRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               if (nodeRolesUri != null)
+                       uris.add(nodeRolesUri);
+
+               // node tokens
+               String nodeTokensUri = null;// getFrameworkProp(CmsConstants.TOKENS_URI);
+               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
+               if (nodeTokensUri == null && nodeBase != null) {
+                       nodeTokensUri = baseNodeTokensDn + ".ldif";
+                       Path nodeTokensFile = nodeBase.resolve(nodeTokensUri);
+                       if (!Files.exists(nodeTokensFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(baseNodeTokensDn + ".ldif"), nodeTokensFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               if (nodeTokensUri != null)
+                       uris.add(nodeTokensUri);
+
+               // Business roles
+//             String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               List<String> userAdminUris = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DIRECTORY);// getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               for (String userAdminUri : userAdminUris) {
+                       if (userAdminUri == null)
+                               continue;
+//                     if (!userAdminUri.trim().equals(""))
+                       uris.add(userAdminUri);
+               }
+
+               if (uris.size() == 0 && nodeBase != null) {
+                       // TODO put this somewhere else
+                       String demoBaseDn = "dc=example,dc=com";
+                       String userAdminUri = demoBaseDn + ".ldif";
+                       Path businessRolesFile = nodeBase.resolve(userAdminUri);
+                       Path systemRolesFile = nodeBase.resolve("ou=roles,ou=node.ldif");
+                       if (!Files.exists(businessRolesFile))
+                               try {
+                                       Files.copy(CmsUserAdmin.class.getResourceAsStream(demoBaseDn + ".ldif"), businessRolesFile);
+                                       if (!Files.exists(systemRolesFile))
+                                               Files.copy(CmsUserAdmin.class.getResourceAsStream("example-ou=roles,ou=node.ldif"),
+                                                               systemRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resources", e);
+                               }
+                       // userAdminUris = businessRolesFile.toURI().toString();
+                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
+                       // TODO downgrade security level
+               }
+
+               // Interprets URIs
+               for (String uri : uris) {
+                       URI u;
+                       try {
+                               u = new URI(uri);
+                               if (u.getPath() == null)
+                                       throw new IllegalArgumentException(
+                                                       "URI " + uri + " must have a path in order to determine base DN");
+                               if (u.getScheme() == null) {
+                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+                                               u = Paths.get(uri).toRealPath().toUri();
+                                       else if (!uri.contains("/")) {
+                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+                                               u = new URI(uri);
+                                       } else
+                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
+                               } else if (u.getScheme().equals(DirectoryConf.SCHEME_FILE)) {
+                                       u = Paths.get(u).toRealPath().toUri();
+                               }
+                       } catch (Exception e) {
+                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
+                       }
+
+                       try {
+                               Dictionary<String, Object> properties = DirectoryConf.uriAsProperties(u.toString());
+                               res.add(properties);
+                       } catch (Exception e) {
+                               log.error("Cannot load user directory " + u, e);
+                       }
+               }
+
+               return res;
+       }
+
+       public UserDirectory enableUserDirectory(Dictionary<String, ?> properties) {
+               String uri = (String) properties.get(DirectoryConf.uri.name());
+               Object realm = properties.get(DirectoryConf.realm.name());
+               URI u;
+               try {
+                       if (uri == null) {
+                               String baseDn = (String) properties.get(DirectoryConf.baseDn.name());
+                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_PRIVATE + '/' + baseDn + ".ldif");
+                       } else if (realm != null) {
+                               u = null;
+                       } else {
+                               u = new URI(uri);
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
+               }
+
+               // Create
+               UserDirectory userDirectory = new DirectoryUserAdmin(u, properties);
+//             if (realm != null || DirectoryConf.SCHEME_LDAP.equals(u.getScheme())
+//                             || DirectoryConf.SCHEME_LDAPS.equals(u.getScheme())) {
+//                     userDirectory = new LdapUserAdmin(properties);
+//             } else if (DirectoryConf.SCHEME_FILE.equals(u.getScheme())) {
+//                     userDirectory = new LdifUserAdmin(u, properties);
+//             } else if (DirectoryConf.SCHEME_OS.equals(u.getScheme())) {
+//                     userDirectory = new OsUserDirectory(u, properties);
+//                     singleUser = true;
+//             } else {
+//                     throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+//             }
+               String basePath = userDirectory.getBase();
+
+               addUserDirectory(userDirectory);
+               if (isSystemRolesBaseDn(basePath)) {
+                       addStandardSystemRoles();
+               }
+               if (log.isDebugEnabled()) {
+                       log.debug("User directory " + userDirectory.getBase() + (u != null ? " [" + u.getScheme() + "]" : "")
+                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
+               }
+               return userDirectory;
+       }
+
+       protected void addStandardSystemRoles() {
+               // we assume UserTransaction is already available (TODO make it more robust)
+               try {
+                       userTransaction.begin();
+                       Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
+                       if (adminRole == null) {
+                               adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+                       }
+                       if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+                               Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+                               userAdminRole.addMember(adminRole);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               // silent
+                       }
+                       throw new IllegalStateException("Cannot add standard system roles", e);
+               }
+       }
+
+       @Override
+       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+               if (rawAuthorization.getName() == null) {
+                       sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
+               } else {
+                       sysRoles.add(CmsConstants.ROLE_USER);
+               }
+       }
+
+       @Override
+       protected void postAdd(UserDirectory userDirectory) {
+               userDirectory.setTransactionControl(transactionManager);
+
+               Optional<String> realm = userDirectory.getRealm();
+               if (realm.isPresent()) {
+                       loadIpaJaasConfiguration();
+                       if (Files.exists(nodeKeyTab)) {
+                               String servicePrincipal = getKerberosServicePrincipal(realm.get());
+                               if (servicePrincipal != null) {
+                                       CallbackHandler callbackHandler = new CallbackHandler() {
+                                               @Override
+                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                                       for (Callback callback : callbacks)
+                                                               if (callback instanceof NameCallback)
+                                                                       ((NameCallback) callback).setName(servicePrincipal);
+
+                                               }
+                                       };
+                                       try {
+                                               LoginContext nodeLc = CmsAuth.NODE.newLoginContext(callbackHandler);
+                                               nodeLc.login();
+                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
+                                       } catch (LoginException e) {
+                                               throw new IllegalStateException("Cannot log in kernel", e);
+                                       }
+                               }
+                       }
+
+               }
+       }
+
+       @Override
+       protected void preDestroy(UserDirectory userDirectory) {
+               Optional<String> realm = userDirectory.getRealm();
+               if (realm.isPresent()) {
+                       if (acceptorCredentials != null) {
+                               try {
+                                       acceptorCredentials.dispose();
+                               } catch (GSSException e) {
+                                       // silent
+                               }
+                               acceptorCredentials = null;
+                       }
+               }
+       }
+
+       private void loadIpaJaasConfiguration() {
+               if (CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.JAVA_LOGIN_CONFIG) == null) {
+                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
+                       URL url = getClass().getClassLoader().getResource(jaasConfig);
+                       KernelUtils.setJaasConfiguration(url);
+                       log.debug("Set IPA JAAS configuration.");
+               }
+       }
+
+       protected String getKerberosServicePrincipal(String realm) {
+               if (!Files.exists(nodeKeyTab))
+                       return null;
+               List<String> dns = CmsStateImpl.getDeployProperties(cmsState, CmsDeployProperty.DNS);
+               String hostname = CmsStateImpl.getDeployProperty(cmsState, CmsDeployProperty.HOST);
+               try (DnsBrowser dnsBrowser = new DnsBrowser(dns)) {
+                       hostname = hostname != null ? hostname : InetAddress.getLocalHost().getHostName();
+                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+                       String ipv4fromDns = dnsBrowser.getRecord(hostname, "A");
+                       String ipv6fromDns = dnsBrowser.getRecord(hostname, "AAAA");
+                       if (ipv4fromDns == null && ipv6fromDns == null)
+                               throw new IllegalStateException("hostname " + hostname + " is not registered in DNS");
+                       // boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       if (kerberosDomain != null && kerberosDomain.equals(realm)) {
+                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
+                       } else
+                               return null;
+               } catch (Exception e) {
+                       log.warn("Exception when determining kerberos principal", e);
+                       return null;
+               }
+       }
+
+       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
+               // not static because class is not supported by Android
+               final Oid KERBEROS_OID;
+               try {
+                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+               } catch (GSSException e) {
+                       throw new IllegalStateException("Cannot create Kerberos OID", e);
+               }
+               // GSS
+               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
+               if (!krb5It.hasNext())
+                       return null;
+               KerberosPrincipal krb5Principal = null;
+               while (krb5It.hasNext()) {
+                       KerberosPrincipal principal = krb5It.next();
+                       if (principal.getName().equals(servicePrincipal))
+                               krb5Principal = principal;
+               }
+
+               if (krb5Principal == null)
+                       return null;
+
+               GSSManager manager = GSSManager.getInstance();
+               try {
+                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
+                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
+
+                               @Override
+                               public GSSCredential run() throws GSSException {
+                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
+                                                       GSSCredential.ACCEPT_ONLY);
+
+                               }
+
+                       });
+                       if (log.isDebugEnabled())
+                               log.debug("GSS acceptor configured for " + krb5Principal);
+                       return serverCredentials;
+               } catch (Exception gsse) {
+                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
+               }
+       }
+
+       public GSSCredential getAcceptorCredentials() {
+               return acceptorCredentials;
+       }
+
+       public boolean hasAcceptorCredentials() {
+               return acceptorCredentials != null;
+       }
+
+       public boolean isSingleUser() {
+               return singleUser;
+       }
+
+       public void setTransactionManager(WorkControl transactionManager) {
+               this.transactionManager = transactionManager;
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserManagerImpl.java
new file mode 100644 (file)
index 0000000..1eb227b
--- /dev/null
@@ -0,0 +1,755 @@
+package org.argeo.cms.internal.runtime;
+
+import static org.argeo.api.acr.ldap.LdapAttr.cn;
+import static org.argeo.api.acr.ldap.LdapAttr.description;
+import static org.argeo.api.acr.ldap.LdapAttr.owner;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.SharedSecret;
+import org.argeo.cms.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.cms.osgi.useradmin.TokenUtils;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Canonical implementation of the people {@link CmsUserManager}. Wraps
+ * interaction with users and groups.
+ * 
+ * In a *READ-ONLY* mode. We want to be able to:
+ * <ul>
+ * <li>Retrieve my user and corresponding information (main info,
+ * groups...)</li>
+ * <li>List all local groups (not the system roles)</li>
+ * <li>If sufficient rights: retrieve a given user and its information</li>
+ * </ul>
+ */
+public class CmsUserManagerImpl implements CmsUserManager {
+       private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
+
+       private UserAdmin userAdmin;
+//     private Map<String, String> serviceProperties;
+       private WorkTransaction userTransaction;
+
+       private final String[] knownProps = { LdapAttr.cn.name(), LdapAttr.sn.name(), LdapAttr.givenName.name(),
+                       LdapAttr.uid.name() };
+
+//     private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
+//                     .synchronizedMap(new LinkedHashMap<>());
+
+       private Set<UserDirectory> userDirectories = new HashSet<>();
+
+       public void start() {
+               log.debug(() -> "CMS user manager available");
+       }
+
+       public void stop() {
+
+       }
+
+       @Override
+       public String getMyMail() {
+               return getUserMail(CurrentUser.getUsername());
+       }
+
+       @Override
+       public Role[] getRoles(String filter) {
+               try {
+                       return userAdmin.getRoles(filter);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Invalid filter " + filter, e);
+               }
+       }
+
+       // ALL USER: WARNING access to this will be later reduced
+
+       /** Retrieve a user given his dn, or <code>null</code> if it doesn't exist. */
+       public CmsUser getUser(String dn) {
+               return (CmsUser) getUserAdmin().getRole(dn);
+       }
+
+       /** Can be a group or a user */
+       public String getUserDisplayName(String dn) {
+               // FIXME: during initialisation phase, the system logs "admin" as user
+               // name rather than the corresponding dn
+               if ("admin".equals(dn))
+                       return "System Administrator";
+               else
+                       return UserAdminUtils.getUserDisplayName(getUserAdmin(), dn);
+       }
+
+       @Override
+       public String getUserMail(String dn) {
+               return UserAdminUtils.getUserMail(getUserAdmin(), dn);
+       }
+
+       /** Lists all roles of the given user */
+       @Override
+       public String[] getUserRoles(String dn) {
+               Authorization currAuth = getUserAdmin().getAuthorization(getUser(dn));
+               return currAuth.getRoles();
+       }
+
+       @Override
+       public boolean isUserInRole(String userDn, String roleDn) {
+               String[] roles = getUserRoles(userDn);
+               for (String role : roles) {
+                       if (role.equalsIgnoreCase(roleDn))
+                               return true;
+               }
+               return false;
+       }
+
+       public Set<CmsUser> listUsersInGroup(String groupDn, String filter) {
+               Group group = (Group) userAdmin.getRole(groupDn);
+               if (group == null)
+                       throw new IllegalArgumentException("Group " + groupDn + " not found");
+               Set<CmsUser> users = new HashSet<>();
+               addUsers(users, group, filter);
+               return users;
+       }
+
+//     @Override
+//     public Set<User> listAccounts(HierarchyUnit hierarchyUnit, boolean deep) {
+//             if(!hierarchyUnit.isFunctional())
+//                     throw new IllegalArgumentException("Hierarchy unit "+hierarchyUnit.getBase()+" is not functional");
+//             UserDirectory directory = (UserDirectory)hierarchyUnit.getDirectory();
+//             Set<User> res = new HashSet<>();
+//             for(HierarchyUnit technicalHu:hierarchyUnit.getDirectHierarchyUnits(false)) {
+//                     if(technicalHu.isFunctional())
+//                             continue;
+//                     for(Role role:directory.getHierarchyUnitRoles(technicalHu, null, false)) {
+//                             if(role)
+//                     }
+//             }
+//             return res;
+//     }
+
+       /** Recursively add users to list */
+       private void addUsers(Set<CmsUser> users, Group group, String filter) {
+               Role[] roles = group.getMembers();
+               for (Role role : roles) {
+                       if (role.getType() == Role.GROUP) {
+                               addUsers(users, (CmsGroup) role, filter);
+                       } else if (role.getType() == Role.USER) {
+                               if (match(role, filter))
+                                       users.add((CmsUser) role);
+                       } else {
+                               // ignore
+                       }
+               }
+       }
+
+       public List<CmsUser> listGroups(String filter, boolean includeUsers, boolean includeSystemRoles) {
+               Role[] roles = null;
+               try {
+                       roles = getUserAdmin().getRoles(filter);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Unable to get roles with filter: " + filter, e);
+               }
+
+               List<CmsUser> users = new ArrayList<>();
+               for (Role role : roles) {
+                       if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
+                                       && (includeSystemRoles
+                                                       || !role.getName().toLowerCase().endsWith(CmsConstants.SYSTEM_ROLES_BASEDN))) {
+                               if (match(role, filter))
+                                       users.add((CmsUser) role);
+                       }
+               }
+               return users;
+       }
+
+       private boolean match(Role role, String filter) {
+               boolean doFilter = filter != null && !"".equals(filter);
+               if (doFilter) {
+                       for (String prop : knownProps) {
+                               Object currProp = null;
+                               try {
+                                       currProp = role.getProperties().get(prop);
+                               } catch (Exception e) {
+                                       throw e;
+                               }
+                               if (currProp != null) {
+                                       String currPropStr = ((String) currProp).toLowerCase();
+                                       if (currPropStr.contains(filter.toLowerCase())) {
+                                               return true;
+                                       }
+                               }
+                       }
+                       return false;
+               } else
+                       return true;
+       }
+
+       @Override
+       public CmsUser getUserFromLocalId(String localId) {
+               CmsUser user = (CmsUser) getUserAdmin().getUser(LdapAttr.uid.name(), localId);
+               if (user == null)
+                       user = (CmsUser) getUserAdmin().getUser(LdapAttr.cn.name(), localId);
+               return user;
+       }
+
+       @Override
+       public String buildDefaultDN(String localId, int type) {
+               return buildDistinguishedName(localId, getDefaultDomainName(), type);
+       }
+
+       /*
+        * EDITION
+        */
+       @Override
+       public CmsUser createUser(String username, Map<String, Object> properties, Map<String, Object> credentials) {
+               try {
+                       userTransaction.begin();
+                       CmsUser user = (CmsUser) userAdmin.createRole(username, Role.USER);
+                       if (properties != null) {
+                               for (String key : properties.keySet())
+                                       user.getProperties().put(key, properties.get(key));
+                       }
+                       if (credentials != null) {
+                               for (String key : credentials.keySet())
+                                       user.getCredentials().put(key, credentials.get(key));
+                       }
+                       userTransaction.commit();
+                       return user;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create user " + username, e);
+               }
+       }
+
+       @Override
+       public CmsGroup createGroup(String dn) {
+               try {
+                       userTransaction.begin();
+                       CmsGroup group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+                       userTransaction.commit();
+                       return group;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create group " + dn, e);
+               }
+       }
+
+       @Override
+       public CmsGroup getOrCreateGroup(HierarchyUnit groups, String commonName) {
+               String dn = LdapAttr.cn.name() + "=" + commonName + "," + groups.getBase();
+               CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+               if (group != null)
+                       return group;
+               try {
+                       userTransaction.begin();
+                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+                       userTransaction.commit();
+                       return group;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create group " + commonName + " in " + groups, e);
+               }
+       }
+
+       @Override
+       public CmsGroup getOrCreateSystemRole(HierarchyUnit roles, QName systemRole) {
+               String dn = LdapAttr.cn.name() + "=" + NamespaceUtils.toPrefixedName(systemRole) + "," + roles.getBase();
+               CmsGroup group = (CmsGroup) getUserAdmin().getRole(dn);
+               if (group != null)
+                       return group;
+               try {
+                       userTransaction.begin();
+                       group = (CmsGroup) userAdmin.createRole(dn, Role.GROUP);
+                       userTransaction.commit();
+                       return group;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot create system role " + systemRole + " in " + roles, e);
+               }
+       }
+
+       @Override
+       public HierarchyUnit getOrCreateHierarchyUnit(UserDirectory directory, String path) {
+               HierarchyUnit hi = directory.getHierarchyUnit(path);
+               if (hi != null)
+                       return hi;
+               try {
+                       userTransaction.begin();
+                       HierarchyUnit hierarchyUnit = directory.createHierarchyUnit(path);
+                       userTransaction.commit();
+                       return hierarchyUnit;
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot create hierarchy unit " + path + " in directory " + directory, e1);
+               }
+       }
+
+       @Override
+       public void addObjectClasses(Role role, Set<String> objectClasses, Map<String, Object> additionalProperties) {
+               try {
+                       userTransaction.begin();
+                       LdapEntry.addObjectClasses(role.getProperties(), objectClasses);
+                       for (String key : additionalProperties.keySet()) {
+                               role.getProperties().put(key, additionalProperties.get(key));
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + role, e1);
+               }
+       }
+
+       @Override
+       public void addObjectClasses(HierarchyUnit hierarchyUnit, Set<String> objectClasses,
+                       Map<String, Object> additionalProperties) {
+               try {
+                       userTransaction.begin();
+                       LdapEntry.addObjectClasses(hierarchyUnit.getProperties(), objectClasses);
+                       for (String key : additionalProperties.keySet()) {
+                               hierarchyUnit.getProperties().put(key, additionalProperties.get(key));
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add object classes " + objectClasses + " to " + hierarchyUnit, e1);
+               }
+       }
+
+       @Override
+       public void edit(Runnable action) {
+               Objects.requireNonNull(action);
+               try {
+                       userTransaction.begin();
+                       action.run();
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot edit", e1);
+               }
+       }
+
+       @Override
+       public void addMember(CmsGroup group, Role role) {
+               try {
+                       userTransaction.begin();
+                       group.addMember(role);
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add member " + role + " to group " + group, e1);
+               }
+       }
+
+       @Override
+       public void removeMember(CmsGroup group, Role role) {
+               try {
+                       userTransaction.begin();
+                       group.removeMember(role);
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot remove member " + role + " from group " + group, e1);
+               }
+       }
+
+       @Override
+       public String getDefaultDomainName() {
+               Map<String, String> dns = getKnownBaseDns(true);
+               if (dns.size() == 1)
+                       return dns.keySet().iterator().next();
+               else
+                       throw new IllegalStateException("Current context contains " + dns.size() + " base dns: "
+                                       + dns.keySet().toString() + ". Unable to chose a default one.");
+       }
+
+       public Map<String, String> getKnownBaseDns(boolean onlyWritable) {
+               Map<String, String> dns = new HashMap<String, String>();
+               for (UserDirectory userDirectory : userDirectories) {
+                       Boolean readOnly = userDirectory.isReadOnly();
+                       String baseDn = userDirectory.getBase();
+
+                       if (onlyWritable && readOnly)
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.SYSTEM_ROLES_BASEDN))
+                               continue;
+                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
+                               continue;
+                       dns.put(baseDn, DirectoryConf.propertiesAsUri(userDirectory.getProperties()).toString());
+
+               }
+               return dns;
+       }
+
+       public Set<UserDirectory> getUserDirectories() {
+               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
+               res.addAll(userDirectories);
+               return res;
+       }
+
+       public String buildDistinguishedName(String localId, String baseDn, int type) {
+               Map<String, String> dns = getKnownBaseDns(true);
+               Dictionary<String, ?> props = DirectoryConf.uriAsProperties(dns.get(baseDn));
+               String dn = null;
+               if (Role.GROUP == type)
+                       dn = LdapAttr.cn.name() + "=" + localId + "," + DirectoryConf.groupBase.getValue(props) + "," + baseDn;
+               else if (Role.USER == type)
+                       dn = LdapAttr.uid.name() + "=" + localId + "," + DirectoryConf.userBase.getValue(props) + "," + baseDn;
+               else
+                       throw new IllegalStateException("Unknown role type. " + "Cannot deduce dn for " + localId);
+               return dn;
+       }
+
+       @Override
+       public void changeOwnPassword(char[] oldPassword, char[] newPassword) {
+               String name = CurrentUser.getUsername();
+               LdapName dn;
+               try {
+                       dn = new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid user dn " + name, e);
+               }
+               User user = (User) userAdmin.getRole(dn.toString());
+               if (!user.hasCredential(null, oldPassword))
+                       throw new IllegalArgumentException("Invalid password");
+               if (Arrays.equals(newPassword, new char[0]))
+                       throw new IllegalArgumentException("New password empty");
+               try {
+                       userTransaction.begin();
+                       user.getCredentials().put(null, newPassword);
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot change password", e);
+               }
+       }
+
+       public void resetPassword(String username, char[] newPassword) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid user dn " + username, e);
+               }
+               User user = (User) userAdmin.getRole(dn.toString());
+               if (Arrays.equals(newPassword, new char[0]))
+                       throw new IllegalArgumentException("New password empty");
+               try {
+                       userTransaction.begin();
+                       user.getCredentials().put(null, newPassword);
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot change password", e);
+               }
+       }
+
+       public String addSharedSecret(String email, int hours) {
+               User user = (User) userAdmin.getUser(LdapAttr.mail.name(), email);
+               try {
+                       userTransaction.begin();
+                       String uuid = UUID.randomUUID().toString();
+                       SharedSecret sharedSecret = new SharedSecret(hours, uuid);
+                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+                       userTransaction.commit();
+                       return tokenStr;
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               log.error("Could not roll back", e1);
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new RuntimeException("Cannot change password", e);
+               }
+       }
+
+       @Deprecated
+       public String addSharedSecret(String username, String authInfo, String authToken) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.getRole(username);
+                       SharedSecret sharedSecret = new SharedSecret(authInfo, authToken);
+                       user.getCredentials().put(SharedSecret.X_SHARED_SECRET, sharedSecret.toAuthPassword());
+                       String tokenStr = sharedSecret.getAuthInfo() + '$' + sharedSecret.getAuthValue();
+                       userTransaction.commit();
+                       return tokenStr;
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add shared secret", e1);
+               }
+       }
+
+       @Override
+       public void expireAuthToken(String token) {
+               try {
+                       userTransaction.begin();
+                       String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+                       Group tokenGroup = (Group) userAdmin.getRole(dn);
+                       String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
+                       tokenGroup.getProperties().put(description.name(), ldapDate);
+                       userTransaction.commit();
+                       if (log.isDebugEnabled())
+                               log.debug("Token " + token + " expired.");
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot expire token", e1);
+               }
+       }
+
+       @Override
+       public void expireAuthTokens(Subject subject) {
+               Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
+               for (String token : tokens)
+                       expireAuthToken(token);
+       }
+
+       @Override
+       public void addAuthToken(String userDn, String token, Integer hours, String... roles) {
+               addAuthToken(userDn, token, ZonedDateTime.now().plusHours(hours), roles);
+       }
+
+       @Override
+       public void addAuthToken(String userDn, String token, ZonedDateTime expiryDate, String... roles) {
+               try {
+                       userTransaction.begin();
+                       User user = (User) userAdmin.getRole(userDn);
+                       String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
+                       Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
+                       if (roles != null)
+                               for (String role : roles) {
+                                       Role r = userAdmin.getRole(role);
+                                       if (r != null)
+                                               tokenGroup.addMember(r);
+                                       else {
+                                               if (!role.equals(CmsConstants.ROLE_USER)) {
+                                                       throw new IllegalStateException(
+                                                                       "Cannot add role " + role + " to token " + token + " for " + userDn);
+                                               }
+                                       }
+                               }
+                       tokenGroup.getProperties().put(owner.name(), user.getName());
+                       if (expiryDate != null) {
+                               String ldapDate = NamingUtils.instantToLdapDate(expiryDate);
+                               tokenGroup.getProperties().put(description.name(), ldapDate);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e1) {
+                       try {
+                               if (!userTransaction.isNoTransactionStatus())
+                                       userTransaction.rollback();
+                       } catch (Exception e2) {
+                               if (log.isTraceEnabled())
+                                       log.trace("Cannot rollback transaction", e2);
+                       }
+                       throw new RuntimeException("Cannot add token", e1);
+               }
+       }
+
+       @Override
+       public UserDirectory getDirectory(Role user) {
+               String name = user.getName();
+               NavigableMap<String, UserDirectory> possible = new TreeMap<>();
+               for (UserDirectory userDirectory : userDirectories) {
+                       if (name.endsWith(userDirectory.getBase())) {
+                               possible.put(userDirectory.getBase(), userDirectory);
+                       }
+               }
+               if (possible.size() == 0)
+                       throw new IllegalStateException("No user directory found for user " + name);
+               return possible.lastEntry().getValue();
+       }
+
+//     public User createUserFromPerson(Node person) {
+//             String email = JcrUtils.get(person, LdapAttrs.mail.property());
+//             String dn = buildDefaultDN(email, Role.USER);
+//             User user;
+//             try {
+//                     userTransaction.begin();
+//                     user = (User) userAdmin.createRole(dn, Role.USER);
+//                     Dictionary<String, Object> userProperties = user.getProperties();
+//                     String name = JcrUtils.get(person, LdapAttrs.displayName.property());
+//                     userProperties.put(LdapAttrs.cn.name(), name);
+//                     userProperties.put(LdapAttrs.displayName.name(), name);
+//                     String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
+//                     String surname = JcrUtils.get(person, LdapAttrs.sn.property());
+//                     userProperties.put(LdapAttrs.givenName.name(), givenName);
+//                     userProperties.put(LdapAttrs.sn.name(), surname);
+//                     userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
+//                     userTransaction.commit();
+//             } catch (Exception e) {
+//                     try {
+//                             userTransaction.rollback();
+//                     } catch (Exception e1) {
+//                             log.error("Could not roll back", e1);
+//                     }
+//                     if (e instanceof RuntimeException)
+//                             throw (RuntimeException) e;
+//                     else
+//                             throw new RuntimeException("Cannot create user", e);
+//             }
+//             return user;
+//     }
+
+       public UserAdmin getUserAdmin() {
+               return userAdmin;
+       }
+
+//     public UserTransaction getUserTransaction() {
+//             return userTransaction;
+//     }
+
+       /* DEPENDENCY INJECTION */
+       public void setUserAdmin(UserAdmin userAdmin) {
+               this.userAdmin = userAdmin;
+
+               if (userAdmin instanceof AggregatingUserAdmin) {
+                       userDirectories = ((AggregatingUserAdmin) userAdmin).getUserDirectories();
+               } else {
+                       throw new IllegalArgumentException("Only " + AggregatingUserAdmin.class.getName() + " is supported.");
+               }
+
+//             this.serviceProperties = serviceProperties;
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+//     public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+//             userDirectories.put(userDirectory, new Hashtable<>(properties));
+//     }
+//
+//     public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
+//             userDirectories.remove(userDirectory);
+//     }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java
new file mode 100644 (file)
index 0000000..0fd0a63
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.cms.acr.CmsContentRepository;
+import org.argeo.cms.acr.directory.DirectoryContentProvider;
+import org.argeo.cms.acr.fs.FsContentProvider;
+
+public class DeployedContentRepository extends CmsContentRepository {
+       private final static String ROOT_XML = "cr:root.xml";
+
+       private final static CmsLog log = CmsLog.getLog(DeployedContentRepository.class);
+
+       private CmsUserManager userManager;
+
+       @Override
+       public void start() {
+               long begin = System.currentTimeMillis();
+               try {
+                       super.start();
+                       Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
+                       initRootContentProvider(null);
+
+//             Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
+//             FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false);
+//             addProvider(srvContentProvider);
+
+                       // run dir
+                       Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
+                       if (runDirPath != null) {
+                               Files.createDirectories(runDirPath);
+                               FsContentProvider runContentProvider = new FsContentProvider(CmsContentRepository.RUN_BASE, runDirPath);
+                               addProvider(runContentProvider);
+                       }
+
+                       // users
+                       DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider(
+                                       CmsContentRepository.DIRECTORY_BASE, userManager);
+                       addProvider(directoryContentProvider);
+
+                       // remote
+//                     DavContentProvider davContentProvider = new DavContentProvider("/srv",
+//                                     URI.create("http://localhost/unstable/a2/"));
+//                     addProvider(davContentProvider);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot start content repository", e);
+               }
+               long duration = System.currentTimeMillis() - begin;
+               log.debug(() -> "CMS content repository available (initialisation took " + duration + " ms)");
+       }
+
+       @Override
+       public void stop() {
+               super.stop();
+       }
+
+       public void setUserManager(CmsUserManager userManager) {
+               this.userManager = userManager;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java
deleted file mode 100644 (file)
index 70ea9ec..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.argeo.cms.internal.runtime;
-
-import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.Reader;
-import java.net.InetAddress;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.commons.io.FileUtils;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.internal.http.InternalHttpConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
-
-/**
- * Interprets framework properties in order to generate the initial deploy
- * configuration.
- */
-public class InitUtils {
-       private final static CmsLog log = CmsLog.getLog(InitUtils.class);
-
-       /** Override the provided config with the framework properties */
-       public static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
-               String httpPort = getFrameworkProp("org.osgi.service.http.port");
-               String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
-               /// TODO make it more generic
-               String httpHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
-               String httpsHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
-               String webSocketEnabled = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
-
-               final Hashtable<String, Object> props = new Hashtable<String, Object>();
-               // try {
-               if (httpPort != null || httpsPort != null) {
-                       boolean httpEnabled = httpPort != null;
-                       props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
-                       boolean httpsEnabled = httpsPort != null;
-                       props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
-
-                       if (httpEnabled) {
-                               props.put(InternalHttpConstants.HTTP_PORT, httpPort);
-                               if (httpHost != null)
-                                       props.put(InternalHttpConstants.HTTP_HOST, httpHost);
-                       }
-
-                       if (httpsEnabled) {
-                               props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
-                               if (httpsHost != null)
-                                       props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
-
-                               // server certificate
-                               Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
-                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
-                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
-                               String keyStorePasswordStr = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
-                               char[] keyStorePassword;
-                               if (keyStorePasswordStr == null)
-                                       keyStorePassword = "changeit".toCharArray();
-                               else
-                                       keyStorePassword = keyStorePasswordStr.toCharArray();
-
-                               // if PEM files both exists, update the PKCS12 file
-                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
-                                       // TODO check certificate update time? monitor changes?
-                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
-                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
-                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
-                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                                               if (log.isDebugEnabled())
-                                                       log.debug("PEM certificate stored in " + keyStorePath);
-                                       } catch (IOException e) {
-                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
-                                       }
-                               }
-
-                               if (!Files.exists(keyStorePath))
-                                       createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
-                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
-
-//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
-//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
-//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
-
-                               // client certificate authentication
-                               String wantClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
-                               String needClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
-                       }
-
-                       // web socket
-                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
-                               props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
-
-                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
-               }
-               return props;
-       }
-
-       public static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
-               List<Dictionary<String, Object>> res = new ArrayList<>();
-               File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
-               List<String> uris = new ArrayList<>();
-
-               // node roles
-               String nodeRolesUri = getFrameworkProp(CmsConstants.ROLES_URI);
-               String baseNodeRoleDn = CmsConstants.ROLES_BASEDN;
-               if (nodeRolesUri == null) {
-                       nodeRolesUri = baseNodeRoleDn + ".ldif";
-                       File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
-                       if (!nodeRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
-                                                       nodeRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeRolesUri);
-
-               // node tokens
-               String nodeTokensUri = getFrameworkProp(CmsConstants.TOKENS_URI);
-               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
-               if (nodeTokensUri == null) {
-                       nodeTokensUri = baseNodeTokensDn + ".ldif";
-                       File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
-                       if (!nodeTokensFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
-                                                       nodeTokensFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeTokensUri);
-
-               // Business roles
-               String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
-               if (userAdminUris == null) {
-                       String demoBaseDn = "dc=example,dc=com";
-                       userAdminUris = demoBaseDn + ".ldif";
-                       File businessRolesFile = new File(nodeBaseDir, userAdminUris);
-                       File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
-                       if (!businessRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
-                                                       businessRolesFile);
-                                       if (!systemRolesFile.exists())
-                                               FileUtils.copyInputStreamToFile(
-                                                               InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resources", e);
-                               }
-                       // userAdminUris = businessRolesFile.toURI().toString();
-                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
-                       // TODO downgrade security level
-               }
-               for (String userAdminUri : userAdminUris.split(" "))
-                       uris.add(userAdminUri);
-
-               // Interprets URIs
-               for (String uri : uris) {
-                       URI u;
-                       try {
-                               u = new URI(uri);
-                               if (u.getPath() == null)
-                                       throw new IllegalArgumentException(
-                                                       "URI " + uri + " must have a path in order to determine base DN");
-                               if (u.getScheme() == null) {
-                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
-                                               u = new File(uri).getCanonicalFile().toURI();
-                                       else if (!uri.contains("/")) {
-                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
-                                               u = new URI(uri);
-                                       } else
-                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
-                               } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
-                                       u = new File(u).getCanonicalFile().toURI();
-                               }
-                       } catch (Exception e) {
-                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
-                       }
-                       Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
-                       res.add(properties);
-               }
-
-               return res;
-       }
-
-       /**
-        * Called before node initialisation, in order populate OSGi instance are with
-        * some files (typically LDIF, etc).
-        */
-       public static void prepareFirstInitInstanceArea() {
-               String nodeInits = getFrameworkProp(CmsConstants.NODE_INIT);
-               if (nodeInits == null)
-                       nodeInits = "../../init";
-
-               for (String nodeInit : nodeInits.split(",")) {
-
-                       if (nodeInit.startsWith("http")) {
-                               // TODO reconnect it
-                               // registerRemoteInit(nodeInit);
-                       } else {
-
-                               // TODO use java.nio.file
-                               File initDir;
-                               if (nodeInit.startsWith("."))
-                                       initDir = KernelUtils.getExecutionDir(nodeInit);
-                               else
-                                       initDir = new File(nodeInit);
-                               // TODO also uncompress archives
-                               if (initDir.exists())
-                                       try {
-                                               FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
-
-                                                       @Override
-                                                       public boolean accept(File pathname) {
-                                                               if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
-                                                                       return false;
-                                                               return true;
-                                                       }
-                                               });
-                                               log.info("CMS initialized from " + initDir.getCanonicalPath());
-                                       } catch (IOException e) {
-                                               throw new RuntimeException("Cannot initialize from " + initDir, e);
-                                       }
-                       }
-               }
-       }
-
-       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
-               // for (Provider provider : Security.getProviders())
-               // System.out.println(provider.getName());
-//             File keyStoreFile = keyStorePath.toFile();
-               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
-               if (!Files.exists(keyStorePath)) {
-                       try {
-                               Files.createDirectories(keyStorePath.getParent());
-                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
-                               PkiUtils.generateSelfSignedCertificate(keyStore,
-                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
-                                               1024, keyPwd);
-                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                               if (log.isDebugEnabled())
-                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
-                       } catch (Exception e) {
-                               try {
-                                       if (Files.size(keyStorePath) == 0)
-                                               Files.delete(keyStorePath);
-                               } catch (IOException e1) {
-                                       // silent
-                               }
-                               log.error("Cannot create keystore " + keyStorePath, e);
-                       }
-               } else {
-                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
-               }
-       }
-
-}
index dfe86cfaaf7e95a8621a80bf30fb3ae6a2b88969..e6ca1ba605a56057908722f209dbe742cf92a64c 100644 (file)
@@ -3,51 +3,36 @@ package org.argeo.cms.internal.runtime;
 import org.argeo.api.cms.CmsConstants;
 
 /** Internal CMS constants. */
-public interface KernelConstants {
+interface KernelConstants {
        // Directories
-       String DIR_NODE = "node";
-       String DIR_REPOS = "repos";
-       String DIR_INDEXES = "indexes";
-       String DIR_TRANSACTIONS = "transactions";
+       String DIR_PRIVATE = "private";
 
        // Files
-       String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif";
-       String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12";
-       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key";
-       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt";
-       String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
+       String NODE_KEY_TAB_PATH = DIR_PRIVATE + "/krb5.keytab";
+       String NODE_SSHD_AUTHORIZED_KEYS_PATH = DIR_PRIVATE + "/authorized_keys";
 
        // Security
        String JAAS_CONFIG = "/org/argeo/cms/internal/runtime/jaas.cfg";
        String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/runtime/jaas-ipa.cfg";
 
-       // Java
-       String JAAS_CONFIG_PROP = "java.security.auth.login.config";
-
-       // DEFAULTS JCR PATH
-       String DEFAULT_HOME_BASE_PATH = "/home";
-       String DEFAULT_USERS_BASE_PATH = "/users";
-       String DEFAULT_GROUPS_BASE_PATH = "/groups";
-       
        // KERBEROS
        String DEFAULT_KERBEROS_SERVICE = "HTTP";
 
+       String DEFAULT_KEYSTORE_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".p12";
+
+       String DEFAULT_TRUSTSTORE_PATH = DIR_PRIVATE + "/trusted.p12";
+
+       String DEFAULT_PEM_KEY_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".key";
+
+       String DEFAULT_PEM_CERT_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".crt";
+
+       String IPA_PEM_CA_CERT_PATH = "/etc/ipa/ca.crt";
+
+       String DEFAULT_KEYSTORE_PASSWORD = "changeit";
+
+       String PKCS12 = "PKCS12";
+
        // HTTP client
-       String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
-
-       // RWT / RAP
-       // String PATH_WORKBENCH = "/ui";
-       // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
-
-//     String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
-       String JETTY_FACTORY_PID = "org.argeo.equinox.jetty.config";
-       String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
-       // default Jetty server configured via JettyConfigurator
-       String DEFAULT_JETTY_SERVER = "default";
-       String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
-
-       // avoid dependencies
-       String CONTEXT_NAME_PROP = "contextName";
-       String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
-       String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+       // String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
+
 }
index afcb9ff26a77d68502373e86a5eb2fd84280fa3d..6e47873b35557f772ac7d702433652bdb342a3fa 100644 (file)
@@ -1,6 +1,5 @@
 package org.argeo.cms.internal.runtime;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.net.URI;
@@ -19,7 +18,7 @@ import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.internal.osgi.CmsActivator;
 
 /** Package utilities */
-public class KernelUtils implements KernelConstants {
+class KernelUtils implements KernelConstants {
        final static String OSGI_INSTANCE_AREA = "osgi.instance.area";
        final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
 
@@ -52,41 +51,28 @@ public class KernelUtils implements KernelConstants {
                return asDictionary(props);
        }
 
-       static File getExecutionDir(String relativePath) {
-               File executionDir = new File(getFrameworkProp("user.dir"));
+       static Path getExecutionDir(String relativePath) {
+               Path executionDir = Paths.get(getFrameworkProp("user.dir"));
                if (relativePath == null)
                        return executionDir;
-               try {
-                       return new File(executionDir, relativePath).getCanonicalFile();
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot get canonical file", e);
-               }
-       }
-
-       static File getOsgiInstanceDir() {
-               return new File(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
-                               .getAbsoluteFile();
+               return executionDir.resolve(relativePath);
        }
 
        public static Path getOsgiInstancePath(String relativePath) {
-               return Paths.get(getOsgiInstanceUri(relativePath));
+               URI uri = getOsgiInstanceUri(relativePath);
+               if (uri == null) // no data area available
+                       return null;
+               return Paths.get(uri);
        }
 
        public static URI getOsgiInstanceUri(String relativePath) {
                String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
-               if (osgiInstanceBaseUri != null)
-                       return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
-               else
-                       return Paths.get(System.getProperty("user.dir")).toUri();
-       }
+               if (osgiInstanceBaseUri == null) // no data area available
+                       return null;
 
-       static File getOsgiConfigurationFile(String relativePath) {
-               try {
-                       return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
-                                       .getCanonicalFile();
-               } catch (Exception e) {
-                       throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
-               }
+               if (!osgiInstanceBaseUri.endsWith("/"))
+                       osgiInstanceBaseUri = osgiInstanceBaseUri + "/";
+               return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
        }
 
        static String getFrameworkProp(String key, String def) {
@@ -100,36 +86,14 @@ public class KernelUtils implements KernelConstants {
                return value;
        }
 
-       public static String getFrameworkProp(String key) {
+       static String getFrameworkProp(String key) {
                return getFrameworkProp(key, null);
        }
 
-       // Security
-       // static Subject anonymousLogin() {
-       // Subject subject = new Subject();
-       // LoginContext lc;
-       // try {
-       // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
-       // lc.login();
-       // return subject;
-       // } catch (LoginException e) {
-       // throw new CmsException("Cannot login as anonymous", e);
-       // }
-       // }
-
        static void logFrameworkProperties(CmsLog log) {
                for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
                        log.debug(sysProp + "=" + getFrameworkProp(sysProp.toString()));
                }
-               // String[] keys = { Constants.FRAMEWORK_STORAGE,
-               // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
-               // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
-               // Constants.FRAMEWORK_TRUST_REPOSITORIES,
-               // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
-               // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
-               // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
-               // for (String key : keys)
-               // log.debug(key + "=" + bc.getProperty(key));
        }
 
        static void printSystemProperties(PrintStream out) {
@@ -140,84 +104,6 @@ public class KernelUtils implements KernelConstants {
                        out.println(key + "=" + display.get(key));
        }
 
-//     static Session openAdminSession(Repository repository) {
-//             return openAdminSession(repository, null);
-//     }
-//
-//     static Session openAdminSession(final Repository repository, final String workspaceName) {
-//             LoginContext loginContext = loginAsDataAdmin();
-//             return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-//
-//                     @Override
-//                     public Session run() {
-//                             try {
-//                                     return repository.login(workspaceName);
-//                             } catch (RepositoryException e) {
-//                                     throw new IllegalStateException("Cannot open admin session", e);
-//                             } finally {
-//                                     try {
-//                                             loginContext.logout();
-//                                     } catch (LoginException e) {
-//                                             throw new IllegalStateException(e);
-//                                     }
-//                             }
-//                     }
-//
-//             });
-//     }
-//
-//     static LoginContext loginAsDataAdmin() {
-//             ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
-//             Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
-//             LoginContext loginContext;
-//             try {
-//                     loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN);
-//                     loginContext.login();
-//             } catch (LoginException e1) {
-//                     throw new IllegalStateException("Could not login as data admin", e1);
-//             } finally {
-//                     Thread.currentThread().setContextClassLoader(currentCl);
-//             }
-//             return loginContext;
-//     }
-
-//     static void doAsDataAdmin(Runnable action) {
-//             LoginContext loginContext = loginAsDataAdmin();
-//             Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-//
-//                     @Override
-//                     public Void run() {
-//                             try {
-//                                     action.run();
-//                                     return null;
-//                             } finally {
-//                                     try {
-//                                             loginContext.logout();
-//                                     } catch (LoginException e) {
-//                                             throw new IllegalStateException(e);
-//                                     }
-//                             }
-//                     }
-//
-//             });
-//     }
-
-//     public static void asyncOpen(ServiceTracker<?, ?> st) {
-//             Runnable run = new Runnable() {
-//
-//                     @Override
-//                     public void run() {
-//                             st.open();
-//                     }
-//             };
-//             Activator.getInternalExecutorService().execute(run);
-////           new Thread(run, "Open service tracker " + st).start();
-//     }
-
-//     static BundleContext getBundleContext() {
-//             return Activator.getBundleContext();
-//     }
-
        static boolean asBoolean(String value) {
                if (value == null)
                        return false;
index 474a8995061040ca824a0b54fd8d373af54403db..378146ea15715cb9415e70ca0e5c0db0a7e42ad1 100644 (file)
@@ -1,84 +1,38 @@
 package org.argeo.cms.internal.runtime;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
-import java.math.BigInteger;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
+import java.security.KeyFactory;
 import java.security.KeyStore;
+import java.security.KeyStore.TrustedCertificateEntry;
 import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.InputDecryptorProvider;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
-import org.bouncycastle.pkcs.PKCSException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Objects;
 
 /**
  * Utilities around private keys and certificate, mostly wrapping BouncyCastle
  * implementations.
  */
 class PkiUtils {
-       final static String PKCS12 = "PKCS12";
-
-       private final static String SECURITY_PROVIDER;
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-               SECURITY_PROVIDER = "BC";
-       }
-
-       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
-                       int keySize, char[] keyPassword) {
-               try {
-                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
-                       kpGen.initialize(keySize, new SecureRandom());
-                       KeyPair pair = kpGen.generateKeyPair();
-                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
-                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
-                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
-                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
-                                       notAfter, x500Principal, pair.getPublic());
-                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
-                                       .build(pair.getPrivate());
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certGen.build(sigGen));
-                       cert.checkValidity(new Date());
-                       cert.verify(cert.getPublicKey());
-
-                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
-                       return cert;
-               } catch (GeneralSecurityException | OperatorCreationException e) {
-                       throw new RuntimeException("Cannot generate self-signed certificate", e);
-               }
-       }
-
        public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
                try {
-                       KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
+                       KeyStore store = KeyStore.getInstance(keyStoreType);
                        if (Files.exists(keyStoreFile)) {
                                try (InputStream fis = Files.newInputStream(keyStoreFile)) {
                                        store.load(fis, keyStorePassword);
@@ -102,158 +56,67 @@ class PkiUtils {
                }
        }
 
-//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
-//                     throws Exception {
-//             // Get the private key
-//             FileReader reader = new FileReader(keyFile);
-//
-//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
-//                     @Override
-//                     public char[] getPassword() {
-//                             return password.toCharArray();
-//                     }
-//             });
-//
-//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Get the certificate
-//             reader = new FileReader(cerFile);
-//             pem = new PEMReader(reader);
-//
-//             X509Certificate cert = (X509Certificate) pem.readObject();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Put them into a PKCS12 keystore and write it to a byte[]
-//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
-//             KeyStore ks = KeyStore.getInstance("PKCS12");
-//             ks.load(null);
-//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
-//             ks.store(bos, password.toCharArray());
-//             bos.close();
-//             return bos.toByteArray();
-//     }
+       public static void loadPrivateCertificatePem(KeyStore keyStore, String alias, Reader key, char[] keyPassword,
+                       BufferedInputStream cert) {
+               Objects.requireNonNull(keyStore);
+               Objects.requireNonNull(key);
+               try {
+                       X509Certificate certificate = loadPemCertificate(cert);
+                       PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
+                       keyStore.setKeyEntry(alias, privateKey, keyPassword, new java.security.cert.Certificate[] { certificate });
+               } catch (KeyStoreException e) {
+                       throw new RuntimeException("Cannot store PEM certificate", e);
+               }
+       }
 
-       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
-               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
-               X509Certificate certificate = loadPemCertificate(cert);
+       public static void loadTrustedCertificatePem(KeyStore keyStore, char[] keyStorePassword, BufferedInputStream cert) {
                try {
-                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
-                                       new java.security.cert.Certificate[] { certificate });
+                       X509Certificate certificate = loadPemCertificate(cert);
+                       TrustedCertificateEntry trustedCertificateEntry = new TrustedCertificateEntry(certificate);
+                       keyStore.setEntry(certificate.getSubjectX500Principal().getName(), trustedCertificateEntry, null);
                } catch (KeyStoreException e) {
                        throw new RuntimeException("Cannot store PEM certificate", e);
                }
        }
 
        public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
-                       Object object = pemParser.readObject();
-                       PrivateKeyInfo privateKeyInfo;
-                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
-                               if (keyPassword == null)
-                                       throw new IllegalArgumentException("A key password is required");
-                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
-                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
-                       } else if (object instanceof PrivateKeyInfo) {
-                               privateKeyInfo = (PrivateKeyInfo) object;
-                       } else {
-                               throw new IllegalArgumentException("Unsupported format for private key");
+               try {
+                       StringBuilder key = new StringBuilder();
+                       try (BufferedReader in = new BufferedReader(reader)) {
+                               String line = in.readLine();
+                               if (!"-----BEGIN PRIVATE KEY-----".equals(line))
+                                       throw new IllegalArgumentException("Not a PEM private key");
+                               lines: while ((line = in.readLine()) != null) {
+                                       if ("-----END PRIVATE KEY-----".equals(line))
+                                               break lines;
+                                       key.append(line);
+                               }
                        }
-                       return converter.getPrivateKey(privateKeyInfo);
-               } catch (IOException | OperatorCreationException | PKCSException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
 
-       public static X509Certificate loadPemCertificate(Reader reader) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certHolder);
-                       return cert;
-               } catch (IOException | CertificateException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
+                       byte[] encoded = Base64.getDecoder().decode(key.toString());
 
-       public static void main(String[] args) throws Exception {
-               final String ALGORITHM = "RSA";
-               final String provider = "BC";
-               SecureRandom secureRandom = new SecureRandom();
-               long begin = System.currentTimeMillis();
-               for (int i = 512; i < 1024; i = i + 2) {
-                       try {
-                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
-                               keyGen.initialize(i, secureRandom);
-                               keyGen.generateKeyPair();
-                       } catch (Exception e) {
-                               System.err.println(i + " : " + e.getMessage());
-                       }
+                       KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+                       PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+                       return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+               } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
+                       throw new RuntimeException("Cannot load PEM key", e);
                }
-               System.out.println((System.currentTimeMillis() - begin) + " ms");
-
-               // // String text = "a";
-               // String text =
-               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
-               // try {
-               // System.out.println(text);
-               // PrivateKey privateKey;
-               // PublicKey publicKey;
-               // char[] password = "changeit".toCharArray();
-               // String alias = "CN=test";
-               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
-               // File p12file = new File("test.p12");
-               // p12file.delete();
-               // if (!p12file.exists()) {
-               // keyStore.load(null);
-               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
-               // 513, password);
-               // try (OutputStream out = new FileOutputStream(p12file)) {
-               // keyStore.store(out, password);
-               // }
-               // }
-               // try (InputStream in = new FileInputStream(p12file)) {
-               // keyStore.load(in, password);
-               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
-               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
-               // }
-               // // KeyPair key;
-               // // final KeyPairGenerator keyGen =
-               // // KeyPairGenerator.getInstance(ALGORITHM);
-               // // keyGen.initialize(4096, new SecureRandom());
-               // // long begin = System.currentTimeMillis();
-               // // key = keyGen.generateKeyPair();
-               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
-               // // keyStore.load(null);
-               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
-               // // try(OutputStream out=new FileOutputStream(p12file)) {
-               // // keyStore.store(out, password);
-               // // }
-               // // privateKey = key.getPrivate();
-               // // publicKey = key.getPublic();
-               //
-               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
-               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
-               // byte[] encrypted = encrypt.doFinal(text.getBytes());
-               // String encryptedBase64 =
-               // Base64.getEncoder().encodeToString(encrypted);
-               // System.out.println(encryptedBase64);
-               // byte[] encryptedFromBase64 =
-               // Base64.getDecoder().decode(encryptedBase64);
-               //
-               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
-               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
-               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
-               // System.out.println(new String(decrypted));
-               // } catch (Exception e) {
-               // e.printStackTrace();
-               // }
 
        }
 
+       public static X509Certificate loadPemCertificate(BufferedInputStream in) {
+               try {
+                       CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+                       @SuppressWarnings("unchecked")
+                       Collection<X509Certificate> certificates = (Collection<X509Certificate>) certificateFactory
+                                       .generateCertificates(in);
+                       if (certificates.isEmpty())
+                               throw new IllegalArgumentException("No certificate found");
+                       if (certificates.size() != 1)
+                               throw new IllegalArgumentException(certificates.size() + " certificates found");
+                       return certificates.iterator().next();
+               } catch (CertificateException e) {
+                       throw new IllegalStateException("cannot load certifciate", e);
+               }
+       }
 }
index c7c804c649ef13b561e57d3611301c9f9c07faaf..51db582c69c091bdd7aa79036ff21005b0a9c7e8 100644 (file)
@@ -1,8 +1,10 @@
 USER {
     org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
     org.argeo.cms.auth.SpnegoLoginModule optional;
-    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
-    org.argeo.cms.auth.UserAdminLoginModule sufficient;
+    com.sun.security.auth.module.Krb5LoginModule optional
+     tryFirstPass=true
+     storeKey=true;
+    org.argeo.cms.auth.UserAdminLoginModule required;
 };
 
 ANONYMOUS {
@@ -16,7 +18,7 @@ DATA_ADMIN {
 
 NODE {
     com.sun.security.auth.module.Krb5LoginModule optional
-     keyTab="${osgi.instance.area}node/krb5.keytab" 
+     keyTab="${osgi.instance.area}private/krb5.keytab" 
      useKeyTab=true
      storeKey=true;
     org.argeo.cms.auth.DataAdminLoginModule requisite;
@@ -28,10 +30,8 @@ KEYRING {
 
 SINGLE_USER {
     com.sun.security.auth.module.Krb5LoginModule optional
-     principal="${user.name}"
      storeKey=true
-     useTicketCache=true
-     debug=true;
+     useTicketCache=true;
     org.argeo.cms.auth.SingleUserLoginModule requisite;
 };
 
index 364977d4b3b9054cac876bb96a05007f8739ba9d..8290fb339807838d878ac9d181b967b7fa711ba9 100644 (file)
@@ -1,7 +1,7 @@
 USER {
     org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
     org.argeo.cms.auth.IdentLoginModule optional;
-    org.argeo.cms.auth.UserAdminLoginModule requisite;
+    org.argeo.cms.auth.UserAdminLoginModule required;
 };
 
 ANONYMOUS {
index 6c195d45f48c742e377f92dcaf874073e843b368..f60d3352e98c61c921b260c7be244183b91c085b 100644 (file)
@@ -7,7 +7,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -17,8 +16,8 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.cms.util.StreamUtils;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 
@@ -32,11 +31,17 @@ import org.osgi.framework.BundleContext;
  * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
  */
 public class BundleCmsTheme implements CmsTheme {
-       public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
+//     public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
 
-       public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
+//     public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
+       @Deprecated
        public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
 
+       /** Declared theme ID, to be used by OSGi services to reference it as parent. */
+       public final static String THEME_ID_PROPERTY = "themeId";
+       public final static String SMALL_ICON_SIZE_PROPERTY = "smallIconSize";
+       public final static String BIG_ICON_SIZE_PROPERTY = "bigIconSize";
+
        private final static String HEADER_CSS = "header.css";
        private final static String FONTS_TXT = "fonts.txt";
        private final static String BODY_HTML = "body.html";
@@ -46,6 +51,8 @@ public class BundleCmsTheme implements CmsTheme {
        private CmsTheme parentTheme;
 
        private String themeId;
+       private String declaredThemeId;;
+
        private Set<String> webCssPaths = new TreeSet<>();
        private Set<String> rapCssPaths = new TreeSet<>();
        private Set<String> swtCssPaths = new TreeSet<>();
@@ -64,13 +71,20 @@ public class BundleCmsTheme implements CmsTheme {
 //     private String swtCssPath;
        private Bundle themeBundle;
 
-       private Integer defaultIconSize = 16;
+       private Integer smallIconSize = 16;
+       private Integer bigIconSize = 32;
 
        public BundleCmsTheme() {
 
        }
 
        public void init(BundleContext bundleContext, Map<String, String> properties) {
+               declaredThemeId = properties.get(THEME_ID_PROPERTY);
+               if (properties.containsKey(SMALL_ICON_SIZE_PROPERTY))
+                       smallIconSize = Integer.valueOf(properties.get(SMALL_ICON_SIZE_PROPERTY));
+               if (properties.containsKey(BIG_ICON_SIZE_PROPERTY))
+                       smallIconSize = Integer.valueOf(properties.get(BIG_ICON_SIZE_PROPERTY));
+
                initResources(bundleContext, null);
        }
 
@@ -103,6 +117,10 @@ public class BundleCmsTheme implements CmsTheme {
 //             swtCssPath = "/swt/";
 //             this.themeId = RWT.DEFAULT_THEME_ID;
                this.themeId = themeBundle.getSymbolicName();
+               if (declaredThemeId != null && !declaredThemeId.equals(themeId))
+                       throw new IllegalArgumentException(
+                                       "Declared theme id " + declaredThemeId + " is different from " + themeId);
+
                webCssPaths = addCss(themeBundle, "/css/");
                rapCssPaths = addCss(themeBundle, "/rap/");
                swtCssPaths = addCss(themeBundle, "/swt/");
@@ -212,7 +230,7 @@ public class BundleCmsTheme implements CmsTheme {
 
        void loadBodyHtml(URL url) {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
-                       bodyHtml = IOUtils.toString(url, StandardCharsets.UTF_8);
+                       bodyHtml = StreamUtils.toString(in);
                } catch (IOException e) {
                        throw new IllegalArgumentException("Cannot load URL " + url, e);
                }
@@ -320,17 +338,27 @@ public class BundleCmsTheme implements CmsTheme {
        }
 
        @Override
-       public Integer getDefaultIconSize() {
-               return defaultIconSize;
+       public int getSmallIconSize() {
+               return smallIconSize;
+       }
+
+       @Override
+       public int getBigIconSize() {
+               return bigIconSize;
        }
 
        @Override
        public InputStream loadPath(String path) throws IOException {
                URL url = themeBundle.getResource(path);
-               if (url == null)
-                       throw new IllegalArgumentException(
-                                       "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
-               return url.openStream();
+               if (url == null) {
+                       if (parentTheme != null)
+                               return parentTheme.loadPath(path);
+                       else
+                               throw new IllegalArgumentException(
+                                               "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
+               } else {
+                       return url.openStream();
+               }
        }
 
        private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
@@ -363,4 +391,12 @@ public class BundleCmsTheme implements CmsTheme {
                this.parentTheme = parentTheme;
        }
 
+       public void setSmallIconSize(Integer smallIconSize) {
+               this.smallIconSize = smallIconSize;
+       }
+
+       public void setBigIconSize(Integer bigIconSize) {
+               this.bigIconSize = bigIconSize;
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java
deleted file mode 100644 (file)
index 424d62f..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.cms.osgi;
-
-import java.util.Collection;
-
-import javax.security.auth.Subject;
-
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsSessionId;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-public class CmsOsgiUtils {
-
-       /** @return The {@link CmsSession} for this {@link Subject} or null. */
-       public static CmsSession getCmsSession(BundleContext bc, Subject subject) {
-               if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
-                       return null;
-               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
-               String uuid = cmsSessionId.getUuid().toString();
-               Collection<ServiceReference<CmsSession>> sr;
-               try {
-                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
-               }
-               ServiceReference<CmsSession> cmsSessionRef;
-               if (sr.size() == 1) {
-                       cmsSessionRef = sr.iterator().next();
-                       return bc.getService(cmsSessionRef);
-               } else if (sr.size() == 0) {
-                       return null;
-               } else
-                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
-       }
-
-       /** Singleton.*/
-       private CmsOsgiUtils() {
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java b/org.argeo.cms/src/org/argeo/cms/osgi/FilterRequirement.java
new file mode 100644 (file)
index 0000000..5582c34
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.osgi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/** Simplify filtering resources. */
+public class FilterRequirement implements Requirement {
+       private String namespace;
+       private String filter;
+
+       public FilterRequirement(String namespace, String filter) {
+               this.namespace = namespace;
+               this.filter = filter;
+       }
+
+       @Override
+       public Resource getResource() {
+               return null;
+       }
+
+       @Override
+       public String getNamespace() {
+               return namespace;
+       }
+
+       @Override
+       public Map<String, String> getDirectives() {
+               Map<String, String> directives = new HashMap<>();
+               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+               return directives;
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return new HashMap<>();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingAuthorization.java
new file mode 100644 (file)
index 0000000..72c4336
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.cms.directory.CmsAuthorization;
+import org.osgi.service.useradmin.Authorization;
+
+/** An {@link Authorization} which combines roles form various auth sources. */
+class AggregatingAuthorization implements CmsAuthorization {
+       private final String name;
+       private final String displayName;
+       private final Set<String> systemRoles;
+       private final Set<String> roles;
+
+       public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
+               this.name = new X500Principal(name).getName();
+               this.displayName = displayName;
+               this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
+               Set<String> temp = new HashSet<>();
+               for (String role : roles) {
+                       if (!temp.contains(role))
+                               temp.add(role);
+               }
+               this.roles = Collections.unmodifiableSet(temp);
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public boolean hasRole(String name) {
+               if (systemRoles.contains(name))
+                       return true;
+               if (roles.contains(name))
+                       return true;
+               return false;
+       }
+
+       @Override
+       public String[] getRoles() {
+               int size = systemRoles.size() + roles.size();
+               List<String> res = new ArrayList<String>(size);
+               res.addAll(systemRoles);
+               res.addAll(roles);
+               return res.toArray(new String[size]);
+       }
+
+       @Override
+       public int hashCode() {
+               if (name == null)
+                       return super.hashCode();
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof Authorization))
+                       return false;
+               Authorization that = (Authorization) obj;
+               if (name == null)
+                       return that.getName() == null;
+               return name.equals(that.getName());
+       }
+
+       @Override
+       public String toString() {
+               return displayName;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AggregatingUserAdmin.java
new file mode 100644 (file)
index 0000000..8ebb98e
--- /dev/null
@@ -0,0 +1,330 @@
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.cms.osgi.useradmin.DirectoryUserAdmin.toLdapName;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class AggregatingUserAdmin implements UserAdmin {
+       private final LdapName systemRolesBaseDn;
+       private final LdapName tokensBaseDn;
+
+       // DAOs
+       private DirectoryUserAdmin systemRoles = null;
+       private DirectoryUserAdmin tokens = null;
+       private Map<LdapName, DirectoryUserAdmin> businessRoles = new HashMap<LdapName, DirectoryUserAdmin>();
+
+       // TODO rather use an empty constructor and an init method
+       public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
+               try {
+                       this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
+                       if (tokensBaseDn != null)
+                               this.tokensBaseDn = new LdapName(tokensBaseDn);
+                       else
+                               this.tokensBaseDn = null;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot initialize " + AggregatingUserAdmin.class, e);
+               }
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               return findUserAdmin(name).createRole(name, type);
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
+               systemRoles.removeRole(name);
+               return actuallyDeleted;
+       }
+
+       @Override
+       public Role getRole(String name) {
+               return findUserAdmin(name).getRole(name);
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               List<Role> res = new ArrayList<Role>();
+               for (UserAdmin userAdmin : businessRoles.values()) {
+                       res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
+               }
+               res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
+               return res.toArray(new Role[res.size()]);
+       }
+
+       @Override
+       public User getUser(String key, String value) {
+               List<User> res = new ArrayList<User>();
+               for (UserAdmin userAdmin : businessRoles.values()) {
+                       User u = userAdmin.getUser(key, value);
+                       if (u != null)
+                               res.add(u);
+               }
+               // Note: node roles cannot contain users, so it is not searched
+               return res.size() == 1 ? res.get(0) : null;
+       }
+
+       /** Builds an authorisation by scanning all referentials. */
+       @Override
+       public Authorization getAuthorization(User user) {
+               if (user == null) {// anonymous
+                       return systemRoles.getAuthorization(null);
+               }
+               DirectoryUserAdmin userReferentialOfThisUser = findUserAdmin(user.getName());
+               Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
+               User retrievedUser = (User) userReferentialOfThisUser.getRole(user.getName());
+               String usernameToUse;
+               String displayNameToUse;
+               if (user instanceof Group) {
+                       // TODO check whether this is still working
+                       String ownerDn = TokenUtils.userDn((Group) user);
+                       if (ownerDn != null) {// tokens
+                               UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
+                               User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
+                               usernameToUse = ownerDn;
+                               displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
+                       } else {
+                               usernameToUse = rawAuthorization.getName();
+                               displayNameToUse = rawAuthorization.toString();
+                       }
+               } else {// regular users
+                       usernameToUse = rawAuthorization.getName();
+                       displayNameToUse = rawAuthorization.toString();
+               }
+
+               // gather roles from other referentials
+               List<String> rawRoles = Arrays.asList(rawAuthorization.getRoles());
+               List<String> allRoles = new ArrayList<>(rawRoles);
+               for (LdapName otherBaseDn : businessRoles.keySet()) {
+                       if (otherBaseDn.equals(userReferentialOfThisUser.getBaseDn()))
+                               continue;
+                       DirectoryUserAdmin otherUserAdmin = userAdminToUse(user, businessRoles.get(otherBaseDn));
+                       if (otherUserAdmin == null)
+                               continue;
+                       for (String roleStr : rawRoles) {
+                               User role = (User) findUserAdmin(roleStr).getRole(roleStr);
+                               Authorization auth = otherUserAdmin.getAuthorization(role);
+                               allRoles.addAll(Arrays.asList(auth.getRoles()));
+                       }
+
+               }
+
+               // integrate system roles
+               final DirectoryUserAdmin userAdminToUse = userAdminToUse(retrievedUser, userReferentialOfThisUser);
+               Objects.requireNonNull(userAdminToUse);
+
+               try {
+                       Set<String> sysRoles = new HashSet<String>();
+                       for (String role : rawAuthorization.getRoles()) {
+                               User userOrGroup = (User) userAdminToUse.getRole(role);
+                               Authorization auth = systemRoles.getAuthorization(userOrGroup);
+                               systemRoles: for (String systemRole : auth.getRoles()) {
+                                       if (role.equals(systemRole))
+                                               continue systemRoles;
+                                       sysRoles.add(systemRole);
+                               }
+//                     sysRoles.addAll(Arrays.asList(auth.getRoles()));
+                       }
+                       addAbstractSystemRoles(rawAuthorization, sysRoles);
+                       Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
+                                       allRoles.toArray(new String[allRoles.size()]));
+                       return authorization;
+               } finally {
+                       if (userAdminToUse != null && userAdminToUse.isScoped()) {
+                               userAdminToUse.destroy();
+                       }
+               }
+       }
+
+       /** Decide whether to scope or not */
+       private DirectoryUserAdmin userAdminToUse(User user, DirectoryUserAdmin userAdmin) {
+               if (userAdmin.isAuthenticated())
+                       return userAdmin;
+               if (user instanceof CmsUser) {
+                       return userAdmin;
+               } else if (user instanceof AuthenticatingUser) {
+                       return userAdmin.scope(user).orElse(null);
+               } else {
+                       throw new IllegalArgumentException("Unsupported user type " + user.getClass());
+               }
+
+       }
+
+       /**
+        * Enrich with application-specific roles which are strictly programmatic, such
+        * as anonymous/user semantics.
+        */
+       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+
+       }
+
+       //
+       // USER ADMIN AGGREGATOR
+       //
+       protected void addUserDirectory(UserDirectory ud) {
+               if (!(ud instanceof DirectoryUserAdmin))
+                       throw new IllegalArgumentException("Only " + DirectoryUserAdmin.class.getName() + " is supported");
+               DirectoryUserAdmin userDirectory = (DirectoryUserAdmin) ud;
+               String basePath = userDirectory.getBase();
+               if (isSystemRolesBaseDn(basePath)) {
+                       this.systemRoles = userDirectory;
+                       systemRoles.setExternalRoles(this);
+               } else if (isTokensBaseDn(basePath)) {
+                       this.tokens = userDirectory;
+                       tokens.setExternalRoles(this);
+               } else {
+                       LdapName baseDn = toLdapName(basePath);
+                       if (businessRoles.containsKey(baseDn))
+                               throw new IllegalStateException("There is already a user admin for " + baseDn);
+                       businessRoles.put(baseDn, userDirectory);
+               }
+               userDirectory.init();
+               postAdd(userDirectory);
+       }
+
+       /** Called after a new user directory has been added */
+       protected void postAdd(UserDirectory userDirectory) {
+       }
+
+       private DirectoryUserAdmin findUserAdmin(String name) {
+               try {
+                       return findUserAdmin(new LdapName(name));
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Badly formatted name " + name, e);
+               }
+       }
+
+       private DirectoryUserAdmin findUserAdmin(LdapName name) {
+               if (name.startsWith(systemRolesBaseDn))
+                       return systemRoles;
+               if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
+                       return tokens;
+               List<DirectoryUserAdmin> res = new ArrayList<>(1);
+               userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
+                       DirectoryUserAdmin userDirectory = businessRoles.get(baseDn);
+                       if (name.startsWith(baseDn)) {
+                               if (userDirectory.isDisabled())
+                                       continue userDirectories;
+//                             if (res.isEmpty()) {
+                               res.add(userDirectory);
+//                             } else {
+//                                     for (AbstractUserDirectory ud : res) {
+//                                             LdapName bd = ud.getBaseDn();
+//                                             if (userDirectory.getBaseDn().startsWith(bd)) {
+//                                                     // child user directory
+//                                             }
+//                                     }
+//                             }
+                       }
+               }
+               if (res.size() == 0)
+                       throw new IllegalStateException("Cannot find user admin for " + name);
+               if (res.size() > 1)
+                       throw new IllegalStateException("Multiple user admin found for " + name);
+               return res.get(0);
+       }
+
+       protected boolean isSystemRolesBaseDn(String basePath) {
+               return toLdapName(basePath).equals(systemRolesBaseDn);
+       }
+
+       protected boolean isTokensBaseDn(String basePath) {
+               return tokensBaseDn != null && toLdapName(basePath).equals(tokensBaseDn);
+       }
+
+//     protected Dictionary<String, Object> currentState() {
+//             Dictionary<String, Object> res = new Hashtable<String, Object>();
+//             // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
+//             for (LdapName name : businessRoles.keySet()) {
+//                     AbstractUserDirectory userDirectory = businessRoles.get(name);
+//                     String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
+//                     res.put(uri, "");
+//             }
+//             return res;
+//     }
+
+       public void start() {
+               if (systemRoles == null) {
+                       // TODO do we really need separate system roles?
+                       Hashtable<String, Object> properties = new Hashtable<>();
+                       properties.put(DirectoryConf.baseDn.name(), "ou=roles,ou=system");
+                       systemRoles = new DirectoryUserAdmin(properties);
+               }
+       }
+
+       public void stop() {
+               for (LdapName name : businessRoles.keySet()) {
+                       DirectoryUserAdmin userDirectory = businessRoles.get(name);
+                       destroy(userDirectory);
+               }
+               businessRoles.clear();
+               businessRoles = null;
+               destroy(systemRoles);
+               systemRoles = null;
+       }
+
+       private void destroy(DirectoryUserAdmin userDirectory) {
+               preDestroy(userDirectory);
+               userDirectory.destroy();
+       }
+
+//     protected void removeUserDirectory(UserDirectory userDirectory) {
+//             LdapName baseDn = toLdapName(userDirectory.getContext());
+//             businessRoles.remove(baseDn);
+//             if (userDirectory instanceof DirectoryUserAdmin)
+//                     destroy((DirectoryUserAdmin) userDirectory);
+//     }
+
+       @Deprecated
+       protected void removeUserDirectory(String basePath) {
+               if (isSystemRolesBaseDn(basePath))
+                       throw new IllegalArgumentException("System roles cannot be removed ");
+               LdapName baseDn = toLdapName(basePath);
+               if (!businessRoles.containsKey(baseDn))
+                       throw new IllegalStateException("No user directory registered for " + baseDn);
+               DirectoryUserAdmin userDirectory = businessRoles.remove(baseDn);
+               destroy(userDirectory);
+       }
+
+       /**
+        * Called before each user directory is destroyed, so that additional actions
+        * can be performed.
+        */
+       protected void preDestroy(UserDirectory userDirectory) {
+       }
+
+       public Set<UserDirectory> getUserDirectories() {
+               TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBase().compareTo(o2.getBase()));
+               res.addAll(businessRoles.values());
+               res.add(systemRoles);
+               return res;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/AuthenticatingUser.java
new file mode 100644 (file)
index 0000000..b87dc9b
--- /dev/null
@@ -0,0 +1,83 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.osgi.service.useradmin.User;
+
+/**
+ * A special user type used during authentication in order to provide the
+ * credentials required for scoping the user admin.
+ */
+public class AuthenticatingUser implements User {
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+
+       private final String name;
+       private final Dictionary<String, Object> credentials;
+
+       public AuthenticatingUser(LdapName name) {
+               if (name == null)
+                       throw new NullPointerException("Provided name cannot be null.");
+               this.name = name.toString();
+               this.credentials = new Hashtable<>();
+       }
+
+       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
+               this.name = name;
+               this.credentials = credentials;
+       }
+
+       public AuthenticatingUser(String name, char[] password) {
+               if (name == null)
+                       throw new NullPointerException("Provided name cannot be null.");
+               this.name = name;
+               credentials = new Hashtable<>();
+               credentials.put(SHARED_STATE_NAME, name);
+               byte[] pwd = DirectoryDigestUtils.charsToBytes(password);
+               credentials.put(SHARED_STATE_PWD, pwd);
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int getType() {
+               return User.USER;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       @Override
+       public Dictionary getProperties() {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       @Override
+       public Dictionary getCredentials() {
+               return credentials;
+       }
+
+       @Override
+       public boolean hasCredential(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "Authenticating user " + name;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java
new file mode 100644 (file)
index 0000000..03f17e6
--- /dev/null
@@ -0,0 +1,402 @@
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.api.acr.ldap.LdapAttr.objectClass;
+import static org.argeo.api.acr.ldap.LdapObj.extensibleObject;
+import static org.argeo.api.acr.ldap.LdapObj.inetOrgPerson;
+import static org.argeo.api.acr.ldap.LdapObj.organizationalPerson;
+import static org.argeo.api.acr.ldap.LdapObj.person;
+import static org.argeo.api.acr.ldap.LdapObj.top;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosTicket;
+
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.api.cms.directory.UserDirectory;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.LdapDao;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
+import org.argeo.cms.directory.ldap.LdapNameUtils;
+import org.argeo.cms.directory.ldap.LdifDao;
+import org.argeo.cms.runtime.DirectoryConf;
+import org.argeo.cms.util.CurrentSubject;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Base class for a {@link UserDirectory}. */
+public class DirectoryUserAdmin extends AbstractLdapDirectory implements UserAdmin, UserDirectory {
+
+       private UserAdmin externalRoles;
+
+       // Transaction
+       public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props) {
+               this(uriArg, props, false);
+       }
+
+       public DirectoryUserAdmin(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+               super(uriArg, props, scoped);
+       }
+
+       public DirectoryUserAdmin(Dictionary<String, ?> props) {
+               this(null, props);
+       }
+
+       /*
+        * ABSTRACT METHODS
+        */
+
+       protected Optional<DirectoryUserAdmin> scope(User user) {
+               if (getDirectoryDao() instanceof LdapDao) {
+                       return scopeLdap(user);
+               } else if (getDirectoryDao() instanceof LdifDao) {
+                       return scopeLdif(user);
+               } else {
+                       throw new IllegalStateException("Unsupported DAO " + getDirectoryDao().getClass());
+               }
+       }
+
+       protected Optional<DirectoryUserAdmin> scopeLdap(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Dictionary<String, Object> properties = cloneConfigProperties();
+               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+               } else {
+                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+               }
+               DirectoryUserAdmin scopedDirectory = new DirectoryUserAdmin(null, properties, true);
+               scopedDirectory.init();
+               // check connection
+               if (!scopedDirectory.getDirectoryDao().checkConnection())
+                       return Optional.empty();
+               return Optional.of(scopedDirectory);
+       }
+
+       protected Optional<DirectoryUserAdmin> scopeLdif(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DirectoryDigestUtils.bytesToChars(pwd);
+                       User directoryUser = (User) getRole(username);
+                       if (!directoryUser.hasCredential(null, password))
+                               throw new IllegalStateException("Invalid credentials");
+               } else {
+                       throw new IllegalStateException("Password is required");
+               }
+               Dictionary<String, Object> properties = cloneConfigProperties();
+               properties.put(DirectoryConf.readOnly.name(), "true");
+               DirectoryUserAdmin scopedUserAdmin = new DirectoryUserAdmin(null, properties, true);
+               // FIXME do it better
+               ((LdifDao) getDirectoryDao()).scope((LdifDao) scopedUserAdmin.getDirectoryDao());
+               // no need to check authentication
+               scopedUserAdmin.init();
+               return Optional.of(scopedUserAdmin);
+       }
+
+       @Override
+       public String getRolePath(Role role) {
+               return nameToRelativePath(LdapNameUtils.toLdapName(role.getName()));
+       }
+
+       @Override
+       public String getRoleSimpleName(Role role) {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               String name = LdapNameUtils.getLastRdnValue(dn);
+               return name;
+       }
+
+       @Override
+       public Role getRoleByPath(String path) {
+               LdapEntry entry = doGetRole(pathToName(path));
+               if (!(entry instanceof Role)) {
+                       return null;
+//                     throw new IllegalStateException("Path must be a UserAdmin Role.");
+               } else {
+                       return (Role) entry;
+               }
+       }
+
+       protected List<Role> getAllRoles(CmsUser user) {
+               List<Role> allRoles = new ArrayList<Role>();
+               if (user != null) {
+                       collectRoles((LdapEntry) user, allRoles);
+                       allRoles.add(user);
+               } else
+                       collectAnonymousRoles(allRoles);
+               return allRoles;
+       }
+
+       private void collectRoles(LdapEntry user, List<Role> allRoles) {
+               List<LdapEntry> allEntries = new ArrayList<>();
+               LdapEntry entry = user;
+               collectGroups(entry, allEntries);
+               for (LdapEntry e : allEntries) {
+                       if (e instanceof Role)
+                               allRoles.add((Role) e);
+               }
+       }
+
+       private void collectAnonymousRoles(List<Role> allRoles) {
+               // TODO gather anonymous roles
+       }
+
+       // USER ADMIN
+       @Override
+       public Role getRole(String name) {
+               return (Role) doGetRole(toLdapName(name));
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               List<? extends Role> res = getRoles(getBaseDn(), filter, true);
+               return res.toArray(new Role[res.size()]);
+       }
+
+       List<CmsUser> getRoles(LdapName searchBase, String filter, boolean deep) throws InvalidSyntaxException {
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+//             Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+               List<LdapEntry> searchRes = getDirectoryDao().doGetEntries(searchBase, filter, deep);
+               List<CmsUser> res = new ArrayList<>();
+               for (LdapEntry entry : searchRes)
+                       res.add((CmsUser) entry);
+               if (wc != null) {
+                       for (Iterator<CmsUser> it = res.iterator(); it.hasNext();) {
+                               CmsUser user = (CmsUser) it.next();
+                               LdapName dn = LdapNameUtils.toLdapName(user.getName());
+                               if (wc.getDeletedData().containsKey(dn))
+                                       it.remove();
+                       }
+                       Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+                       for (LdapEntry ldapEntry : wc.getNewData().values()) {
+                               CmsUser user = (CmsUser) ldapEntry;
+                               if (f == null || f.match(user.getProperties()))
+                                       res.add(user);
+                       }
+                       // no need to check modified users,
+                       // since doGetRoles was already based on the modified attributes
+               }
+               return res;
+       }
+
+       @Override
+       public User getUser(String key, String value) {
+               // TODO check value null or empty
+               List<CmsUser> collectedUsers = new ArrayList<CmsUser>();
+               if (key != null) {
+                       doGetUser(key, value, collectedUsers);
+               } else {
+                       throw new IllegalArgumentException("Key cannot be null");
+               }
+
+               if (collectedUsers.size() == 1) {
+                       return collectedUsers.get(0);
+               } else if (collectedUsers.size() > 1) {
+                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
+                       // "") + value);
+               }
+               return null;
+       }
+
+       protected void doGetUser(String key, String value, List<CmsUser> collectedUsers) {
+               String f = "(" + key + "=" + value + ")";
+               List<LdapEntry> users = getDirectoryDao().doGetEntries(getBaseDn(), f, true);
+               for (LdapEntry entry : users)
+                       collectedUsers.add((CmsUser) entry);
+       }
+
+       @Override
+       public Authorization getAuthorization(User user) {
+               if (user == null) {// anonymous
+                       return new LdifAuthorization(user, getAllRoles(null));
+               }
+               LdapName userName = toLdapName(user.getName());
+               if (isExternal(userName) && user instanceof LdapEntry) {
+                       List<Role> allRoles = new ArrayList<Role>();
+                       collectRoles((LdapEntry) user, allRoles);
+                       return new LdifAuthorization(user, allRoles);
+               } else {
+
+                       Subject currentSubject = CurrentSubject.current();
+                       if (currentSubject != null //
+                                       && getRealm().isPresent() //
+                                       && !currentSubject.getPrivateCredentials(Authorization.class).isEmpty() //
+                                       && !currentSubject.getPrivateCredentials(KerberosTicket.class).isEmpty()) //
+                       {
+                               // TODO not only Kerberos but also bind scope with kept password ?
+                               Authorization auth = currentSubject.getPrivateCredentials(Authorization.class).iterator().next();
+                               // bind with authenticating user
+                               DirectoryUserAdmin scopedUserAdmin = CurrentSubject.callAs(currentSubject, () -> {
+                                       return scope(new AuthenticatingUser(auth.getName(), new Hashtable<>())).orElseThrow();
+                               });
+                               return getAuthorizationFromScoped(scopedUserAdmin, user);
+                       }
+
+                       if (user instanceof CmsUser) {
+                               return new LdifAuthorization(user, getAllRoles((CmsUser) user));
+                       } else {
+                               // bind with authenticating user
+                               DirectoryUserAdmin scopedUserAdmin = scope(user).orElseThrow();
+                               return getAuthorizationFromScoped(scopedUserAdmin, user);
+                       }
+               }
+       }
+
+       private Authorization getAuthorizationFromScoped(DirectoryUserAdmin scopedUserAdmin, User user) {
+               try {
+                       CmsUser directoryUser = (CmsUser) scopedUserAdmin.getRole(user.getName());
+                       if (directoryUser == null)
+                               throw new IllegalStateException("No scoped user found for " + user);
+                       LdifAuthorization authorization = new LdifAuthorization(directoryUser,
+                                       scopedUserAdmin.getAllRoles(directoryUser));
+                       return authorization;
+               } finally {
+                       scopedUserAdmin.destroy();
+               }
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               checkEdit();
+               LdapEntryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = toLdapName(name);
+               if ((getDirectoryDao().entryExists(dn) && !wc.getDeletedData().containsKey(dn))
+                               || wc.getNewData().containsKey(dn))
+                       throw new IllegalArgumentException("Already a role " + name);
+               BasicAttributes attrs = new BasicAttributes(true);
+               // attrs.put(LdifName.dn.name(), dn.toString());
+               Rdn nameRdn = dn.getRdn(dn.size() - 1);
+               // TODO deal with multiple attr RDN
+               attrs.put(nameRdn.getType(), nameRdn.getValue());
+               if (wc.getDeletedData().containsKey(dn)) {
+                       wc.getDeletedData().remove(dn);
+                       wc.getModifiedData().put(dn, attrs);
+                       return getRole(name);
+               } else {
+                       wc.getModifiedData().put(dn, attrs);
+                       LdapEntry newRole = doCreateRole(dn, type, attrs);
+                       wc.getNewData().put(dn, newRole);
+                       return (Role) newRole;
+               }
+       }
+
+       private LdapEntry doCreateRole(LdapName dn, int type, Attributes attrs) {
+               LdapEntry newRole;
+               BasicAttribute objClass = new BasicAttribute(objectClass.name());
+               if (type == Role.USER) {
+                       String userObjClass = getUserObjectClass();
+                       objClass.add(userObjClass);
+                       if (inetOrgPerson.name().equals(userObjClass)) {
+                               objClass.add(organizationalPerson.name());
+                               objClass.add(person.name());
+                       } else if (organizationalPerson.name().equals(userObjClass)) {
+                               objClass.add(person.name());
+                       }
+                       objClass.add(top.name());
+                       objClass.add(extensibleObject.name());
+                       attrs.put(objClass);
+                       newRole = newUser(dn);
+               } else if (type == Role.GROUP) {
+                       String groupObjClass = getGroupObjectClass();
+                       objClass.add(groupObjClass);
+                       // objClass.add(LdifName.extensibleObject.name());
+                       objClass.add(top.name());
+                       attrs.put(objClass);
+                       newRole = newGroup(dn);
+               } else
+                       throw new IllegalArgumentException("Unsupported type " + type);
+               return newRole;
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               return removeEntry(LdapNameUtils.toLdapName(name));
+       }
+
+       /*
+        * HIERARCHY
+        */
+       @Override
+       public HierarchyUnit getHierarchyUnit(Role role) {
+               LdapName dn = LdapNameUtils.toLdapName(role.getName());
+               LdapName huDn = LdapNameUtils.getParent(dn);
+               HierarchyUnit hierarchyUnit = getDirectoryDao().doGetHierarchyUnit(huDn);
+               if (hierarchyUnit == null)
+                       throw new IllegalStateException("No hierarchy unit found for " + role);
+               return hierarchyUnit;
+       }
+
+       @Override
+       public Iterable<? extends Role> getHierarchyUnitRoles(HierarchyUnit hierarchyUnit, String filter, boolean deep) {
+               LdapName dn = LdapNameUtils.toLdapName(hierarchyUnit.getBase());
+               try {
+                       return getRoles(dn, filter, deep);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot filter " + filter + " " + dn, e);
+               }
+       }
+
+       /*
+        * ROLES CREATION
+        */
+       protected LdapEntry newUser(LdapName name) {
+               // TODO support devices, applications, etc.
+               return new LdifUser(this, name);
+       }
+
+       protected LdapEntry newGroup(LdapName name) {
+               return new LdifGroup(this, name);
+
+       }
+
+       // GETTERS
+       protected UserAdmin getExternalRoles() {
+               return externalRoles;
+       }
+
+       public void setExternalRoles(UserAdmin externalRoles) {
+               this.externalRoles = externalRoles;
+       }
+
+       /*
+        * STATIC UTILITIES
+        */
+       static LdapName toLdapName(String name) {
+               try {
+                       return new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException(name + " is not an LDAP name", e);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifAuthorization.java
new file mode 100644 (file)
index 0000000..a54050b
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Basic authorization. */
+class LdifAuthorization implements Authorization {
+       private final String name;
+       private final String displayName;
+       private final List<String> allRoles;
+
+       public LdifAuthorization(User user, List<Role> allRoles) {
+               if (user == null) {
+                       this.name = null;
+                       this.displayName = "anonymous";
+               } else {
+                       this.name = user.getName();
+                       this.displayName = extractDisplayName(user);
+               }
+               // roles
+               String[] roles = new String[allRoles.size()];
+               for (int i = 0; i < allRoles.size(); i++) {
+                       roles[i] = allRoles.get(i).getName();
+               }
+               this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public boolean hasRole(String name) {
+               return allRoles.contains(name);
+       }
+
+       @Override
+       public String[] getRoles() {
+               return allRoles.toArray(new String[allRoles.size()]);
+       }
+
+       @Override
+       public int hashCode() {
+               if (name == null)
+                       return super.hashCode();
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof Authorization))
+                       return false;
+               Authorization that = (Authorization) obj;
+               if (name == null)
+                       return that.getName() == null;
+               return name.equals(that.getName());
+       }
+
+       @Override
+       public String toString() {
+               return displayName;
+       }
+
+       final static String extractDisplayName(User user) {
+               Dictionary<String, Object> props = user.getProperties();
+               Object displayName = props.get(LdapAttr.displayName.name());
+               if (displayName == null)
+                       displayName = props.get(LdapAttr.cn.name());
+               if (displayName == null)
+                       displayName = props.get(LdapAttr.uid.name());
+               if (displayName == null)
+                       displayName = user.getName();
+               if (displayName == null)
+                       throw new IllegalStateException("Cannot set display name for " + user);
+               return displayName.toString();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifGroup.java
new file mode 100644 (file)
index 0000000..99aca1f
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsGroup;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.osgi.service.useradmin.Role;
+
+/** Directory group implementation */
+class LdifGroup extends LdifUser implements CmsGroup {
+       private final String memberAttributeId;
+
+       LdifGroup(AbstractLdapDirectory userAdmin, LdapName dn) {
+               super(userAdmin, dn);
+               memberAttributeId = userAdmin.getMemberAttributeId();
+       }
+
+       @Override
+       public boolean addMember(Role role) {
+               try {
+                       Role foundRole = findRole(new LdapName(role.getName()));
+                       if (foundRole == null)
+                               throw new UnsupportedOperationException(
+                                               "Adding role " + role.getName() + " is unsupported within this context.");
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
+               }
+
+               getUserAdmin().checkEdit();
+               if (!isEditing())
+                       startEditing();
+
+               Attribute member = getAttributes().get(memberAttributeId);
+               if (member != null) {
+                       if (member.contains(role.getName()))
+                               return false;
+                       else
+                               member.add(role.getName());
+               } else
+                       getAttributes().put(memberAttributeId, role.getName());
+               return true;
+       }
+
+       @Override
+       public boolean addRequiredMember(Role role) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean removeMember(Role role) {
+               getUserAdmin().checkEdit();
+               if (!isEditing())
+                       startEditing();
+
+               Attribute member = getAttributes().get(memberAttributeId);
+               if (member != null) {
+                       if (!member.contains(role.getName()))
+                               return false;
+                       member.remove(role.getName());
+                       return true;
+               } else
+                       return false;
+       }
+
+       @Override
+       public Role[] getMembers() {
+               List<Role> directMembers = new ArrayList<Role>();
+               for (LdapName ldapName : getReferences(memberAttributeId)) {
+                       Role role = findRole(ldapName);
+                       if (role == null) {
+                               throw new IllegalStateException("Role " + ldapName + " not found.");
+                       }
+                       directMembers.add(role);
+               }
+               return directMembers.toArray(new Role[directMembers.size()]);
+       }
+
+       /**
+        * Whether a role with this name can be found from this context.
+        * 
+        * @return The related {@link Role} or <code>null</code>.
+        */
+       protected Role findRole(LdapName ldapName) {
+               Role role = getUserAdmin().getRole(ldapName.toString());
+               if (role == null) {
+                       if (getUserAdmin().getExternalRoles() != null)
+                               role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
+               }
+               return role;
+       }
+
+//     @Override
+//     public List<LdapName> getMemberNames() {
+//             Attribute memberAttribute = getAttributes().get(memberAttributeId);
+//             if (memberAttribute == null)
+//                     return new ArrayList<LdapName>();
+//             try {
+//                     List<LdapName> roles = new ArrayList<LdapName>();
+//                     NamingEnumeration<?> values = memberAttribute.getAll();
+//                     while (values.hasMore()) {
+//                             LdapName dn = new LdapName(values.next().toString());
+//                             roles.add(dn);
+//                     }
+//                     return roles;
+//             } catch (NamingException e) {
+//                     throw new IllegalStateException("Cannot get members", e);
+//             }
+//     }
+
+       @Override
+       public Role[] getRequiredMembers() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getType() {
+               return GROUP;
+       }
+
+       protected DirectoryUserAdmin getUserAdmin() {
+               return (DirectoryUserAdmin) getDirectory();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/LdifUser.java
new file mode 100644 (file)
index 0000000..e48869a
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.osgi.useradmin;
+
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.directory.CmsUser;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.DefaultLdapEntry;
+
+/** Directory user implementation */
+class LdifUser extends DefaultLdapEntry implements CmsUser {
+       LdifUser(AbstractLdapDirectory userAdmin, LdapName dn) {
+               super(userAdmin, dn);
+       }
+
+       @Override
+       public String getName() {
+               return getDn().toString();
+       }
+
+       @Override
+       public int getType() {
+               return USER;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserDirectory.java
new file mode 100644 (file)
index 0000000..41277d3
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.cms.directory.HierarchyUnit;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
+import org.argeo.cms.directory.ldap.AbstractLdapDirectoryDao;
+import org.argeo.cms.directory.ldap.LdapEntry;
+import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
+
+/** Pseudo user directory to be used when logging in as OS user. */
+public class OsUserDirectory extends AbstractLdapDirectoryDao {
+       private final String osUsername = System.getProperty("user.name");
+       private final LdapName osUserDn;
+       private final LdapEntry osUser;
+
+       public OsUserDirectory(AbstractLdapDirectory directory) {
+               super(directory);
+               try {
+                       osUserDn = new LdapName(LdapAttr.uid.name() + "=" + osUsername + "," + directory.getUserBaseRdn() + ","
+                                       + directory.getBaseDn());
+//                     Attributes attributes = new BasicAttributes();
+//                     attributes.put(LdapAttrs.uid.name(), osUsername);
+                       osUser = newUser(osUserDn);
+               } catch (NamingException e) {
+                       throw new IllegalStateException("Cannot create system user", e);
+               }
+       }
+
+       @Override
+       public List<LdapName> getDirectGroups(LdapName dn) {
+               return new ArrayList<>();
+       }
+
+       @Override
+       public boolean entryExists(LdapName dn) {
+               return osUserDn.equals(dn);
+       }
+
+       @Override
+       public boolean checkConnection() {
+               return true;
+       }
+
+       @Override
+       public LdapEntry doGetEntry(LdapName key) throws NameNotFoundException {
+               if (osUserDn.equals(key))
+                       return osUser;
+               else
+                       throw new NameNotFoundException("Not an OS role");
+       }
+
+       @Override
+       public List<LdapEntry> doGetEntries(LdapName searchBase, String f, boolean deep) {
+               List<LdapEntry> res = new ArrayList<>();
+//             if (f == null || f.match(osUser.getProperties()))
+               res.add(osUser);
+               return res;
+       }
+
+       @Override
+       public HierarchyUnit doGetHierarchyUnit(LdapName dn) {
+               return null;
+       }
+
+       @Override
+       public Iterable<HierarchyUnit> doGetDirectHierarchyUnits(LdapName searchBase, boolean functionalOnly) {
+               return new ArrayList<>();
+       }
+
+       public void prepare(LdapEntryWorkingCopy wc) {
+
+       }
+
+       public void commit(LdapEntryWorkingCopy wc) {
+
+       }
+
+       public void rollback(LdapEntryWorkingCopy wc) {
+
+       }
+
+       @Override
+       public void init() {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void destroy() {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public Attributes doGetAttributes(LdapName name) {
+               try {
+                       return doGetEntry(name).getAttributes();
+               } catch (NameNotFoundException e) {
+                       throw new IllegalStateException(name + " doe not exist in " + getDirectory().getBaseDn(), e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/OsUserUtils.java
new file mode 100644 (file)
index 0000000..f718780
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.cms.osgi.useradmin;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.security.URIParameter;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** Log in based on JDK-provided OS integration. */
+public class OsUserUtils {
+       private final static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
+       private final static String LOGIN_CONTEXT_USER_NT = "USER_NT";
+
+       public static String getOsUsername() {
+               return System.getProperty("user.name");
+       }
+
+       public static LoginContext loginAsSystemUser(Subject subject) {
+               try {
+                       URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
+                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
+                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
+                       Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
+                       LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
+                                       null, jaasConfiguration);
+                       lc.login();
+                       return lc;
+               } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
+                       throw new RuntimeException("Cannot login as system user", e);
+               }
+       }
+
+       public static void main(String args[]) {
+               Subject subject = new Subject();
+               LoginContext loginContext = loginAsSystemUser(subject);
+               System.out.println(subject);
+               try {
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       // silent
+               }
+       }
+
+       private static boolean isWindows() {
+               return System.getProperty("os.name").startsWith("Windows");
+       }
+
+       private OsUserUtils() {
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/TokenUtils.java
new file mode 100644 (file)
index 0000000..241f609
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.cms.osgi.useradmin;
+
+import static org.argeo.api.acr.ldap.LdapAttr.description;
+import static org.argeo.api.acr.ldap.LdapAttr.owner;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.osgi.service.useradmin.Group;
+
+/**
+ * Canonically implements the Argeo token conventions.
+ */
+public class TokenUtils {
+       public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
+               Set<String> res = new HashSet<>();
+               for (Principal principal : subject.getPrincipals()) {
+                       String name = principal.getName();
+                       if (name.endsWith(tokensBaseDn)) {
+                               try {
+                                       LdapName ldapName = new LdapName(name);
+                                       String token = ldapName.getRdn(ldapName.size()).getValue().toString();
+                                       res.add(token);
+                               } catch (InvalidNameException e) {
+                                       throw new IllegalArgumentException("Invalid principal " + principal, e);
+                               }
+                       }
+               }
+               return res;
+       }
+
+       /** The user related to this token group */
+       public static String userDn(Group tokenGroup) {
+               return (String) tokenGroup.getProperties().get(owner.name());
+       }
+
+       public static boolean isExpired(Group tokenGroup) {
+               return isExpired(tokenGroup, Instant.now());
+
+       }
+
+       public static boolean isExpired(Group tokenGroup, Instant instant) {
+               String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
+               if (expiryDateStr != null) {
+                       Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
+                       if (expiryDate.isBefore(instant)) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+//     private final String token;
+//
+//     public TokenUtils(String token) {
+//             this.token = token;
+//     }
+//
+//     public String getToken() {
+//             return token;
+//     }
+//
+//     @Override
+//     public int hashCode() {
+//             return token.hashCode();
+//     }
+//
+//     @Override
+//     public boolean equals(Object obj) {
+//             if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
+//                     return true;
+//             return false;
+//     }
+//
+//     @Override
+//     public String toString() {
+//             return "Token #" + hashCode();
+//     }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/jaas-os.cfg
new file mode 100644 (file)
index 0000000..da04505
--- /dev/null
@@ -0,0 +1,8 @@
+USER_NIX {
+    com.sun.security.auth.module.UnixLoginModule requisite; 
+};
+
+USER_NT {
+    com.sun.security.auth.module.NTLoginModule requisite; 
+};
+
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java b/org.argeo.cms/src/org/argeo/cms/osgi/useradmin/package-info.java
new file mode 100644 (file)
index 0000000..766c59b
--- /dev/null
@@ -0,0 +1,2 @@
+/** LDAP and LDIF based OSGi useradmin implementation. */
+package org.argeo.cms.osgi.useradmin;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java b/org.argeo.cms/src/org/argeo/cms/runtime/DirectoryConf.java
new file mode 100644 (file)
index 0000000..a4e44cc
--- /dev/null
@@ -0,0 +1,247 @@
+package org.argeo.cms.runtime;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.acr.ldap.NamingUtils;
+import org.argeo.api.cms.directory.DirectoryDigestUtils;
+import org.argeo.cms.directory.ldap.IpaUtils;
+
+/** Properties used to configure user admins. */
+public enum DirectoryConf {
+       /** Base DN (cannot be configured externally) */
+       baseDn(null),
+
+       /** URI of the underlying resource (cannot be configured externally) */
+       uri(null),
+
+       /** User objectClass */
+       userObjectClass("inetOrgPerson"),
+
+       /** Relative base DN for users */
+       userBase("ou=People"),
+
+       /** Groups objectClass */
+       groupObjectClass("groupOfNames"),
+
+       /** Relative base DN for users */
+       groupBase("ou=Groups"),
+
+       /** Relative base DN for users */
+       systemRoleBase("ou=Roles"),
+
+       /** Read-only source */
+       readOnly(null),
+
+       /** Disabled source */
+       disabled(null),
+
+       /** Authentication realm */
+       realm(null),
+
+       /** Override all passwords with this value (typically for testing purposes) */
+       forcedPassword(null);
+
+       public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
+
+       public final static String SCHEME_LDAP = "ldap";
+       public final static String SCHEME_LDAPS = "ldaps";
+       public final static String SCHEME_FILE = "file";
+       public final static String SCHEME_OS = "os";
+       public final static String SCHEME_IPA = "ipa";
+
+       private final static String SECURITY_PRINCIPAL = "java.naming.security.principal";
+       private final static String SECURITY_CREDENTIALS = "java.naming.security.credentials";
+
+       /** The default value. */
+       private Object def;
+
+       DirectoryConf(Object def) {
+               this.def = def;
+       }
+
+       public Object getDefault() {
+               return def;
+       }
+
+       /**
+        * For use as Java property.
+        * 
+        * @deprecated use {@link #name()} instead
+        */
+       @Deprecated
+       public String property() {
+               return name();
+       }
+
+       public String getValue(Dictionary<String, ?> properties) {
+               Object res = getRawValue(properties);
+               if (res == null)
+                       return null;
+               return res.toString();
+       }
+
+       @SuppressWarnings("unchecked")
+       public <T> T getRawValue(Dictionary<String, ?> properties) {
+               Object res = properties.get(name());
+               if (res == null)
+                       res = getDefault();
+               return (T) res;
+       }
+
+       /** @deprecated use {@link #valueOf(String)} instead */
+       @Deprecated
+       public static DirectoryConf local(String property) {
+               return DirectoryConf.valueOf(property);
+       }
+
+       /** Hides host and credentials. */
+       public static URI propertiesAsUri(Dictionary<String, ?> properties) {
+               StringBuilder query = new StringBuilder();
+
+               boolean first = true;
+//             for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
+//                     String key = keys.nextElement();
+//                     // TODO clarify which keys are relevant (list only the enum?)
+//                     if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
+//                                     && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
+//                                     && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
+//                                     && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
+//                             if (first)
+//                                     first = false;
+//                             else
+//                                     query.append('&');
+//                             query.append(valueOf(key).name());
+//                             query.append('=').append(properties.get(key).toString());
+//                     }
+//             }
+
+               keys: for (DirectoryConf key : DirectoryConf.values()) {
+                       if (key.equals(baseDn) || key.equals(uri))
+                               continue keys;
+                       Object value = properties.get(key.name());
+                       if (value == null)
+                               continue keys;
+                       if (first)
+                               first = false;
+                       else
+                               query.append('&');
+                       query.append(key.name());
+                       query.append('=').append(value.toString());
+
+               }
+
+               Object bDnObj = properties.get(baseDn.name());
+               String bDn = bDnObj != null ? bDnObj.toString() : null;
+               try {
+                       return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
+                                       null);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot create URI from properties", e);
+               }
+       }
+
+       public static Dictionary<String, Object> uriAsProperties(String uriStr) {
+               try {
+                       Hashtable<String, Object> res = new Hashtable<String, Object>();
+                       URI u = new URI(uriStr);
+                       String scheme = u.getScheme();
+                       if (scheme != null && scheme.equals(SCHEME_IPA)) {
+                               return IpaUtils.convertIpaUri(u);
+//                             scheme = u.getScheme();
+                       }
+                       String path = u.getPath();
+                       // base DN
+                       String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
+                       if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
+                               bDn = getBaseDnFromHostname();
+                       }
+
+                       if (bDn.endsWith(".ldif"))
+                               bDn = bDn.substring(0, bDn.length() - ".ldif".length());
+
+                       // Normalize base DN as LDAP name
+//                     bDn = new LdapName(bDn).toString();
+
+                       String principal = null;
+                       String credentials = null;
+                       if (scheme != null)
+                               if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
+                                       // TODO additional checks
+                                       if (u.getUserInfo() != null) {
+                                               String[] userInfo = u.getUserInfo().split(":");
+                                               principal = userInfo.length > 0 ? userInfo[0] : null;
+                                               credentials = userInfo.length > 1 ? userInfo[1] : null;
+                                       }
+                               } else if (scheme.equals(SCHEME_FILE)) {
+                               } else if (scheme.equals(SCHEME_IPA)) {
+                               } else if (scheme.equals(SCHEME_OS)) {
+                               } else
+                                       throw new IllegalArgumentException("Unsupported scheme " + scheme);
+                       Map<String, List<String>> query = NamingUtils.queryToMap(u);
+                       for (String key : query.keySet()) {
+                               DirectoryConf ldapProp = DirectoryConf.valueOf(key);
+                               List<String> values = query.get(key);
+                               if (values.size() == 1) {
+                                       res.put(ldapProp.name(), values.get(0));
+                               } else {
+                                       throw new IllegalArgumentException("Only single values are supported");
+                               }
+                       }
+                       res.put(baseDn.name(), bDn);
+                       if (SCHEME_OS.equals(scheme))
+                               res.put(readOnly.name(), "true");
+                       if (principal != null)
+                               res.put(SECURITY_PRINCIPAL, principal);
+                       if (credentials != null)
+                               res.put(SECURITY_CREDENTIALS, credentials);
+                       if (scheme != null) {// relative URIs are dealt with externally
+                               if (SCHEME_OS.equals(scheme)) {
+                                       res.put(uri.name(), SCHEME_OS + ":///");
+                               } else {
+                                       URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
+                                                       scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
+                                       res.put(uri.name(), bareUri.toString());
+                               }
+                       }
+                       return res;
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Cannot convert " + uri + " to properties", e);
+               }
+       }
+
+       private static String getBaseDnFromHostname() {
+               String hostname;
+               try {
+                       hostname = InetAddress.getLocalHost().getHostName();
+               } catch (UnknownHostException e) {
+                       hostname = "localhost.localdomain";
+               }
+               int dotIdx = hostname.indexOf('.');
+               if (dotIdx >= 0) {
+                       String domain = hostname.substring(dotIdx + 1, hostname.length());
+                       String bDn = ("." + domain).replaceAll("\\.", ",dc=");
+                       bDn = bDn.substring(1, bDn.length());
+                       return bDn;
+               } else {
+                       return "dc=" + hostname;
+               }
+       }
+
+       /**
+        * Hash the base DN in order to have a deterministic string to be used as a cn
+        * for the underlying user directory.
+        */
+       public static String baseDnHash(Dictionary<String, Object> properties) {
+               String bDn = (String) properties.get(baseDn.name());
+               if (bDn == null)
+                       throw new IllegalStateException("No baseDn in " + properties);
+               return DirectoryDigestUtils.sha1str(bDn);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java b/org.argeo.cms/src/org/argeo/cms/runtime/StaticCms.java
new file mode 100644 (file)
index 0000000..76775fe
--- /dev/null
@@ -0,0 +1,169 @@
+package org.argeo.cms.runtime;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsState;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.transaction.SimpleTransactionManager;
+import org.argeo.api.cms.transaction.WorkControl;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.api.register.Component;
+import org.argeo.api.register.ComponentRegister;
+import org.argeo.api.register.SimpleRegister;
+import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.acr.CmsUuidFactory;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
+import org.argeo.cms.internal.runtime.CmsUserAdmin;
+import org.argeo.cms.internal.runtime.CmsUserManagerImpl;
+import org.argeo.cms.internal.runtime.DeployedContentRepository;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * A CMS assembly which is programmatically defined, as an alternative to OSGi
+ * deployment. Useful for testing or AOT compilation.
+ */
+public class StaticCms {
+       private SimpleRegister register = new SimpleRegister();
+
+       private CompletableFuture<Void> stopped = new CompletableFuture<Void>();
+
+       public void start() {
+               // UID factory
+               CmsUuidFactory uuidFactory = new CmsUuidFactory();
+               Component<CmsUuidFactory> uuidFactoryC = new Component.Builder<>(uuidFactory) //
+                               .addType(UuidFactory.class) //
+                               .build(register);
+
+               // CMS State
+               CmsStateImpl cmsState = new CmsStateImpl();
+               Component<CmsStateImpl> cmsStateC = new Component.Builder<>(cmsState) //
+                               .addType(CmsState.class) //
+                               .addActivation(cmsState::start) //
+                               .addDeactivation(cmsState::stop) //
+                               .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsState::setUuidFactory, null) //
+                               .build(register);
+
+               // Transaction manager
+               SimpleTransactionManager transactionManager = new SimpleTransactionManager();
+               Component<SimpleTransactionManager> transactionManagerC = new Component.Builder<>(transactionManager) //
+                               .addType(WorkControl.class) //
+                               .addType(WorkTransaction.class) //
+                               .build(register);
+
+               // User Admin
+               CmsUserAdmin userAdmin = new CmsUserAdmin();
+               Component<CmsUserAdmin> userAdminC = new Component.Builder<>(userAdmin) //
+                               .addType(UserAdmin.class) //
+                               .addActivation(userAdmin::start) //
+                               .addDeactivation(userAdmin::stop) //
+                               .addDependency(cmsStateC.getType(CmsState.class), userAdmin::setCmsState, null) //
+                               .addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) //
+                               .addDependency(transactionManagerC.getType(WorkTransaction.class), userAdmin::setUserTransaction, null) //
+                               .build(register);
+
+               // User manager
+               CmsUserManagerImpl userManager = new CmsUserManagerImpl();
+//             for (UserDirectory userDirectory : userAdmin.getUserDirectories()) {
+//                     // FIXME deal with properties
+//                     userManager.addUserDirectory(userDirectory, new HashMap<>());
+//             }
+               Component<CmsUserManagerImpl> userManagerC = new Component.Builder<>(userManager) //
+                               .addType(CmsUserManager.class) //
+                               .addActivation(userManager::start) //
+                               .addDeactivation(userManager::stop) //
+                               .addDependency(userAdminC.getType(UserAdmin.class), userManager::setUserAdmin, null) //
+                               .addDependency(transactionManagerC.getType(WorkTransaction.class), userManager::setUserTransaction,
+                                               null) //
+                               .build(register);
+
+               // Content Repository
+               DeployedContentRepository contentRepository = new DeployedContentRepository();
+               Component<DeployedContentRepository> contentRepositoryC = new Component.Builder<>(contentRepository) //
+                               .addType(ProvidedRepository.class) //
+                               .addType(ContentRepository.class) //
+                               .addActivation(contentRepository::start) //
+                               .addDeactivation(contentRepository::stop) //
+                               .addDependency(cmsStateC.getType(CmsState.class), contentRepository::setCmsState, null) //
+                               .addDependency(uuidFactoryC.getType(UuidFactory.class), contentRepository::setUuidFactory, null) //
+                               .addDependency(userManagerC.getType(CmsUserManager.class), contentRepository::setUserManager, null) //
+                               .build(register);
+
+               // CMS Deployment
+               CmsDeploymentImpl cmsDeployment = new CmsDeploymentImpl();
+               Component<CmsDeploymentImpl> cmsDeploymentC = new Component.Builder<>(cmsDeployment) //
+                               .addType(CmsDeployment.class) //
+                               .addActivation(cmsDeployment::start) //
+                               .addDeactivation(cmsDeployment::stop) //
+                               .addDependency(cmsStateC.getType(CmsState.class), cmsDeployment::setCmsState, null) //
+//                             .addDependency(deployConfigC.getType(DeployConfig.class), cmsDeployment::setDeployConfig, null) //
+                               .build(register);
+
+               // CMS Context
+               CmsContextImpl cmsContext = new CmsContextImpl();
+               Component<CmsContextImpl> cmsContextC = new Component.Builder<>(cmsContext) //
+                               .addType(CmsContext.class) //
+                               .addActivation(cmsContext::start) //
+                               .addDeactivation(cmsContext::stop) //
+                               .addDependency(cmsStateC.getType(CmsState.class), cmsContext::setCmsState, null) //
+                               .addDependency(cmsDeploymentC.getType(CmsDeployment.class), cmsContext::setCmsDeployment, null) //
+                               .addDependency(userAdminC.getType(UserAdmin.class), cmsContext::setUserAdmin, null) //
+                               .addDependency(uuidFactoryC.getType(UuidFactory.class), cmsContext::setUuidFactory, null) //
+//                             .addDependency(contentRepositoryC.getType(ProvidedRepository.class), cmsContext::setContentRepository,
+//                                             null) //
+                               .build(register);
+               assert cmsContextC.get() == cmsContext;
+
+               addComponents(register);
+
+               register.activate();
+
+               postActivation(register);
+       }
+
+       protected void addComponents(ComponentRegister register) {
+
+       }
+
+       protected void postActivation(ComponentRegister register) {
+
+       }
+
+       public ComponentRegister getComponentRegister() {
+               return register;
+       }
+
+       public void stop() {
+               if (register.isActive()) {
+                       register.deactivate();
+               }
+               register.clear();
+               stopped.complete(null);
+       }
+
+       public void waitForStop() {
+               stopped.join();
+       }
+
+       public static void main(String[] args) {
+               if (args.length == 0) {
+                       System.err.println("Usage: <data path>");
+                       System.exit(1);
+               }
+               Path instancePath = Paths.get(args[0]);
+               System.setProperty("osgi.instance.area", instancePath.toUri().toString());
+
+               StaticCms staticCms = new StaticCms();
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> staticCms.stop(), "Static CMS Shutdown"));
+               staticCms.start();
+               staticCms.waitForStop();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java
deleted file mode 100644 (file)
index 08ac549..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-package org.argeo.cms.security;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.Reader;
-import java.io.Writer;
-import java.security.AccessController;
-import java.security.Provider;
-import java.security.Security;
-import java.util.Arrays;
-import java.util.Iterator;
-
-import javax.crypto.SecretKey;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsException;
-
-/** username / password based keyring. TODO internationalize */
-public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
-       // public final static String DEFAULT_KEYRING_LOGIN_CONTEXT = "KEYRING";
-
-       // private String loginContextName = DEFAULT_KEYRING_LOGIN_CONTEXT;
-       private CallbackHandler defaultCallbackHandler;
-
-       private String charset = "UTF-8";
-
-       /**
-        * Default provider is bouncy castle, in order to have consistent behaviour
-        * across implementations
-        */
-       private String securityProviderName = "BC";
-
-       /**
-        * Whether the keyring has already been created in the past with a master
-        * password
-        */
-       protected abstract Boolean isSetup();
-
-       /**
-        * Setup the keyring persistently, {@link #isSetup()} must return true
-        * afterwards
-        */
-       protected abstract void setup(char[] password);
-
-       /** Populates the key spec callback */
-       protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback);
-
-       protected abstract void encrypt(String path, InputStream unencrypted);
-
-       protected abstract InputStream decrypt(String path);
-
-       /** Triggers lazy initialization */
-       protected SecretKey getSecretKey(char[] password) {
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               // we assume only one secrete key is available
-               Iterator<SecretKey> iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
-               if (!iterator.hasNext() || password!=null) {// not initialized
-                       CallbackHandler callbackHandler = password == null ? new KeyringCallbackHandler()
-                                       : new PasswordProvidedCallBackHandler(password);
-                       ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
-                       Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
-                       try {
-                               LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject,
-                                               callbackHandler);
-                               loginContext.login();
-                               // FIXME will login even if password is wrong
-                               iterator = subject.getPrivateCredentials(SecretKey.class).iterator();
-                               return iterator.next();
-                       } catch (LoginException e) {
-                               throw new CmsException("Keyring login failed", e);
-                       } finally {
-                               Thread.currentThread().setContextClassLoader(currentContextClassLoader);
-                       }
-
-               } else {
-                       SecretKey secretKey = iterator.next();
-                       if (iterator.hasNext())
-                               throw new CmsException("More than one secret key in private credentials");
-                       return secretKey;
-               }
-       }
-
-       public InputStream getAsStream(String path) {
-               return decrypt(path);
-       }
-
-       public void set(String path, InputStream in) {
-               encrypt(path, in);
-       }
-
-       public char[] getAsChars(String path) {
-               // InputStream in = getAsStream(path);
-               // CharArrayWriter writer = null;
-               // Reader reader = null;
-               try (InputStream in = getAsStream(path);
-                               CharArrayWriter writer = new CharArrayWriter();
-                               Reader reader = new InputStreamReader(in, charset);) {
-                       IOUtils.copy(reader, writer);
-                       return writer.toCharArray();
-               } catch (IOException e) {
-                       throw new CmsException("Cannot decrypt to char array", e);
-               } finally {
-                       // IOUtils.closeQuietly(reader);
-                       // IOUtils.closeQuietly(in);
-                       // IOUtils.closeQuietly(writer);
-               }
-       }
-
-       public void set(String path, char[] arr) {
-               // ByteArrayOutputStream out = new ByteArrayOutputStream();
-               // ByteArrayInputStream in = null;
-               // Writer writer = null;
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream();
-                               Writer writer = new OutputStreamWriter(out, charset);) {
-                       // writer = new OutputStreamWriter(out, charset);
-                       writer.write(arr);
-                       writer.flush();
-                       // in = new ByteArrayInputStream(out.toByteArray());
-                       try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());) {
-                               set(path, in);
-                       }
-               } catch (IOException e) {
-                       throw new CmsException("Cannot encrypt to char array", e);
-               } finally {
-                       // IOUtils.closeQuietly(writer);
-                       // IOUtils.closeQuietly(out);
-                       // IOUtils.closeQuietly(in);
-               }
-       }
-
-       public void unlock(char[] password) {
-               if (!isSetup())
-                       setup(password);
-               SecretKey secretKey = getSecretKey(password);
-               if (secretKey == null)
-                       throw new CmsException("Could not unlock keyring");
-       }
-
-       protected Provider getSecurityProvider() {
-               return Security.getProvider(securityProviderName);
-       }
-
-       public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler) {
-               this.defaultCallbackHandler = defaultCallbackHandler;
-       }
-
-       public void setCharset(String charset) {
-               this.charset = charset;
-       }
-
-       public void setSecurityProviderName(String securityProviderName) {
-               this.securityProviderName = securityProviderName;
-       }
-
-       // @Deprecated
-       // protected static byte[] hash(char[] password, byte[] salt, Integer
-       // iterationCount) {
-       // ByteArrayOutputStream out = null;
-       // OutputStreamWriter writer = null;
-       // try {
-       // out = new ByteArrayOutputStream();
-       // writer = new OutputStreamWriter(out, "UTF-8");
-       // writer.write(password);
-       // MessageDigest pwDigest = MessageDigest.getInstance("SHA-256");
-       // pwDigest.reset();
-       // pwDigest.update(salt);
-       // byte[] btPass = pwDigest.digest(out.toByteArray());
-       // for (int i = 0; i < iterationCount; i++) {
-       // pwDigest.reset();
-       // btPass = pwDigest.digest(btPass);
-       // }
-       // return btPass;
-       // } catch (Exception e) {
-       // throw new CmsException("Cannot hash", e);
-       // } finally {
-       // IOUtils.closeQuietly(out);
-       // IOUtils.closeQuietly(writer);
-       // }
-       //
-       // }
-
-       /**
-        * Convenience method using the underlying callback to ask for a password
-        * (typically used when the password is not saved in the keyring)
-        */
-       protected char[] ask() {
-               PasswordCallback passwordCb = new PasswordCallback("Password", false);
-               Callback[] dialogCbs = new Callback[] { passwordCb };
-               try {
-                       defaultCallbackHandler.handle(dialogCbs);
-                       char[] password = passwordCb.getPassword();
-                       return password;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot ask for a password", e);
-               }
-
-       }
-
-       class KeyringCallbackHandler implements CallbackHandler {
-               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                       // checks
-                       if (callbacks.length != 2)
-                               throw new IllegalArgumentException(
-                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
-                       if (!(callbacks[0] instanceof PasswordCallback))
-                               throw new UnsupportedCallbackException(callbacks[0]);
-                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
-                               throw new UnsupportedCallbackException(callbacks[0]);
-
-                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
-                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
-
-                       if (isSetup()) {
-                               Callback[] dialogCbs = new Callback[] { passwordCb };
-                               defaultCallbackHandler.handle(dialogCbs);
-                       } else {// setup keyring
-                               TextOutputCallback textCb1 = new TextOutputCallback(TextOutputCallback.INFORMATION,
-                                               "Enter a master password which will protect your private data");
-                               TextOutputCallback textCb2 = new TextOutputCallback(TextOutputCallback.INFORMATION,
-                                               "(for example your credentials to third-party services)");
-                               TextOutputCallback textCb3 = new TextOutputCallback(TextOutputCallback.INFORMATION,
-                                               "Don't forget this password since the data cannot be read without it");
-                               PasswordCallback confirmPasswordCb = new PasswordCallback("Confirm password", false);
-                               // first try
-                               Callback[] dialogCbs = new Callback[] { textCb1, textCb2, textCb3, passwordCb, confirmPasswordCb };
-                               defaultCallbackHandler.handle(dialogCbs);
-
-                               // if passwords different, retry (except if cancelled)
-                               while (passwordCb.getPassword() != null
-                                               && !Arrays.equals(passwordCb.getPassword(), confirmPasswordCb.getPassword())) {
-                                       TextOutputCallback textCb = new TextOutputCallback(TextOutputCallback.ERROR,
-                                                       "The passwords do not match");
-                                       dialogCbs = new Callback[] { textCb, passwordCb, confirmPasswordCb };
-                                       defaultCallbackHandler.handle(dialogCbs);
-                               }
-
-                               if (passwordCb.getPassword() != null) {// not cancelled
-                                       setup(passwordCb.getPassword());
-                               }
-                       }
-
-                       if (passwordCb.getPassword() != null)
-                               handleKeySpecCallback(pbeCb);
-               }
-
-       }
-
-       class PasswordProvidedCallBackHandler implements CallbackHandler {
-               private final char[] password;
-
-               public PasswordProvidedCallBackHandler(char[] password) {
-                       this.password = password;
-               }
-
-               @Override
-               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                       // checks
-                       if (callbacks.length != 2)
-                               throw new IllegalArgumentException(
-                                               "Keyring requires 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
-                       if (!(callbacks[0] instanceof PasswordCallback))
-                               throw new UnsupportedCallbackException(callbacks[0]);
-                       if (!(callbacks[1] instanceof PBEKeySpecCallback))
-                               throw new UnsupportedCallbackException(callbacks[0]);
-
-                       PasswordCallback passwordCb = (PasswordCallback) callbacks[0];
-                       passwordCb.setPassword(password);
-                       PBEKeySpecCallback pbeCb = (PBEKeySpecCallback) callbacks[1];
-                       handleKeySpecCallback(pbeCb);
-               }
-
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java b/org.argeo.cms/src/org/argeo/cms/security/ChecksumFactory.java
deleted file mode 100644 (file)
index 69e8a08..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-package org.argeo.cms.security;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.security.MessageDigest;
-import java.util.Base64;
-import java.util.zip.Checksum;
-
-import org.argeo.cms.CmsException;
-
-/** Allows to fine tune how files are read. */
-public class ChecksumFactory {
-       private int regionSize = 10 * 1024 * 1024;
-
-       public byte[] digest(Path path, final String algo) {
-               try {
-                       final MessageDigest md = MessageDigest.getInstance(algo);
-                       if (Files.isDirectory(path)) {
-                               long begin = System.currentTimeMillis();
-                               Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
-
-                                       @Override
-                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                               if (!Files.isDirectory(file)) {
-                                                       byte[] digest = digest(file, algo);
-                                                       md.update(digest);
-                                               }
-                                               return FileVisitResult.CONTINUE;
-                                       }
-
-                               });
-                               byte[] digest = md.digest();
-                               long duration = System.currentTimeMillis() - begin;
-                               System.out.println(printBase64Binary(digest) + " " + path + " (" + duration / 1000 + "s)");
-                               return digest;
-                       } else {
-                               long begin = System.nanoTime();
-                               long length = -1;
-                               try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
-                                       length = channel.size();
-                                       long cursor = 0;
-                                       while (cursor < length) {
-                                               long effectiveSize = Math.min(regionSize, length - cursor);
-                                               MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
-                                               // md.update(mb);
-                                               byte[] buffer = new byte[1024];
-                                               while (mb.hasRemaining()) {
-                                                       mb.get(buffer);
-                                                       md.update(buffer);
-                                               }
-
-                                               // sub digest
-                                               // mb.flip();
-                                               // MessageDigest subMd =
-                                               // MessageDigest.getInstance(algo);
-                                               // subMd.update(mb);
-                                               // byte[] subDigest = subMd.digest();
-                                               // System.out.println(" -> " + cursor);
-                                               // System.out.println(IOUtils.encodeHexString(subDigest));
-                                               // System.out.println(new BigInteger(1,
-                                               // subDigest).toString(16));
-                                               // System.out.println(new BigInteger(1, subDigest)
-                                               // .toString(Character.MAX_RADIX));
-                                               // System.out.println(printBase64Binary(subDigest));
-
-                                               cursor = cursor + regionSize;
-                                       }
-                                       byte[] digest = md.digest();
-                                       long duration = System.nanoTime() - begin;
-                                       System.out.println(printBase64Binary(digest) + " " + path.getFileName() + " (" + duration / 1000000
-                                                       + "ms, " + (length / 1024) + "kB, " + (length / (duration / 1000000)) * 1000 / (1024 * 1024)
-                                                       + " MB/s)");
-                                       return digest;
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new CmsException("Cannot digest " + path, e);
-               }
-       }
-
-       /** Whether the file should be mapped. */
-       protected boolean mapFile(FileChannel fileChannel) throws IOException {
-               long size = fileChannel.size();
-               if (size > (regionSize / 10))
-                       return true;
-               return false;
-       }
-
-       public long checksum(Path path, Checksum crc) {
-               final int bufferSize = 2 * 1024 * 1024;
-               long begin = System.currentTimeMillis();
-               try (FileChannel channel = (FileChannel) Files.newByteChannel(path);) {
-                       byte[] bytes = new byte[bufferSize];
-                       long length = channel.size();
-                       long cursor = 0;
-                       while (cursor < length) {
-                               long effectiveSize = Math.min(regionSize, length - cursor);
-                               MappedByteBuffer mb = channel.map(FileChannel.MapMode.READ_ONLY, cursor, effectiveSize);
-                               int nGet;
-                               while (mb.hasRemaining()) {
-                                       nGet = Math.min(mb.remaining(), bufferSize);
-                                       mb.get(bytes, 0, nGet);
-                                       crc.update(bytes, 0, nGet);
-                               }
-                               cursor = cursor + regionSize;
-                       }
-                       return crc.getValue();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot checksum " + path, e);
-               } finally {
-                       long duration = System.currentTimeMillis() - begin;
-                       System.out.println(duration / 1000 + "s");
-               }
-       }
-
-       public static void main(String... args) {
-               ChecksumFactory cf = new ChecksumFactory();
-               // Path path =
-               // Paths.get("/home/mbaudier/apache-maven-3.2.3-bin.tar.gz");
-               Path path;
-               if (args.length > 0) {
-                       path = Paths.get(args[0]);
-               } else {
-                       path = Paths.get("/home/mbaudier/Downloads/torrents/CentOS-7-x86_64-DVD-1503-01/"
-                                       + "CentOS-7-x86_64-DVD-1503-01.iso");
-               }
-               // long adler = cf.checksum(path, new Adler32());
-               // System.out.format("Adler=%d%n", adler);
-               // long crc = cf.checksum(path, new CRC32());
-               // System.out.format("CRC=%d%n", crc);
-               String algo = "SHA1";
-               byte[] digest = cf.digest(path, algo);
-               System.out.println(algo + " " + printBase64Binary(digest));
-               System.out.println(algo + " " + new BigInteger(1, digest).toString(16));
-               // String sha1 = printBase64Binary(cf.digest(path, "SHA1"));
-               // System.out.format("SHA1=%s%n", sha1);
-       }
-
-       private static String printBase64Binary(byte[] arr) {
-               return Base64.getEncoder().encodeToString(arr);
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java
deleted file mode 100644 (file)
index df26c6b..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.cms.security;
-
-/**
- * Marker interface for an advanced keyring based on cryptography.
- */
-public interface CryptoKeyring extends Keyring {
-       public void changePassword(char[] oldPassword, char[] newPassword);
-
-       public void unlock(char[] password);
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/Keyring.java b/org.argeo.cms/src/org/argeo/cms/security/Keyring.java
deleted file mode 100644 (file)
index 53740c6..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.cms.security;
-
-import java.io.InputStream;
-
-/**
- * Access to private (typically encrypted) data. The keyring is responsible for
- * retrieving the necessary credentials. <b>Experimental. This API may
- * change.</b>
- */
-public interface Keyring {
-       /**
-        * Returns the confidential information as chars. Must ask for it if it is
-        * not stored.
-        */
-       public char[] getAsChars(String path);
-
-       /**
-        * Returns the confidential information as a stream. Must ask for it if it
-        * is not stored.
-        */
-       public InputStream getAsStream(String path);
-
-       public void set(String path, char[] arr);
-
-       public void set(String path, InputStream in);
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java b/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java
deleted file mode 100644 (file)
index fb53940..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.cms.security;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.cms.CmsConstants;
-
-public class NodeSecurityUtils {
-       public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME,
-                       ROLE_USER_ADMIN_NAME;
-       public final static List<LdapName> RESERVED_ROLES;
-       static {
-               try {
-                       ROLE_ADMIN_NAME = new LdapName(CmsConstants.ROLE_ADMIN);
-                       ROLE_DATA_ADMIN_NAME = new LdapName(CmsConstants.ROLE_DATA_ADMIN);
-                       ROLE_USER_NAME = new LdapName(CmsConstants.ROLE_USER);
-                       ROLE_USER_ADMIN_NAME = new LdapName(CmsConstants.ROLE_USER_ADMIN);
-                       ROLE_ANONYMOUS_NAME = new LdapName(CmsConstants.ROLE_ANONYMOUS);
-                       RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList(
-                                       new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME }));
-               } catch (InvalidNameException e) {
-                       throw new Error("Cannot initialize login module class", e);
-               }
-       }
-
-       public static void checkUserName(LdapName name) throws IllegalArgumentException {
-               if (RESERVED_ROLES.contains(name))
-                       throw new IllegalArgumentException(name + " is a reserved name");
-       }
-
-       public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException {
-//             if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName))
-//                     throw new IllegalArgumentException(roleName + " cannot be listed as role");
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java b/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java
deleted file mode 100644 (file)
index 13e8d75..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.argeo.cms.security;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.PasswordCallback;
-
-/**
- * All information required to set up a {@link PBEKeySpec} bar the password
- * itself (use a {@link PasswordCallback})
- */
-public class PBEKeySpecCallback implements Callback {
-       private String secretKeyFactory;
-       private byte[] salt;
-       private Integer iterationCount;
-       /** Can be null for some algorithms */
-       private Integer keyLength;
-       /** Can be null, will trigger secret key encryption if not */
-       private String secretKeyEncryption;
-
-       private String encryptedPasswordHashCipher;
-       private byte[] encryptedPasswordHash;
-
-       public void set(String secretKeyFactory, byte[] salt,
-                       Integer iterationCount, Integer keyLength,
-                       String secretKeyEncryption) {
-               this.secretKeyFactory = secretKeyFactory;
-               this.salt = salt;
-               this.iterationCount = iterationCount;
-               this.keyLength = keyLength;
-               this.secretKeyEncryption = secretKeyEncryption;
-//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
-//             this.encryptedPasswordHash = encryptedPasswordHash;
-       }
-
-       public String getSecretKeyFactory() {
-               return secretKeyFactory;
-       }
-
-       public byte[] getSalt() {
-               return salt;
-       }
-
-       public Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       public Integer getKeyLength() {
-               return keyLength;
-       }
-
-       public String getSecretKeyEncryption() {
-               return secretKeyEncryption;
-       }
-
-       public String getEncryptedPasswordHashCipher() {
-               return encryptedPasswordHashCipher;
-       }
-
-       public byte[] getEncryptedPasswordHash() {
-               return encryptedPasswordHash;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/package-info.java b/org.argeo.cms/src/org/argeo/cms/security/package-info.java
deleted file mode 100644 (file)
index e994054..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS reusable security components. */
-package org.argeo.cms.security;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java
deleted file mode 100644 (file)
index cfd4827..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms.tabular;
-
-import java.util.List;
-
-/** Minimal tabular row wrapping an {@link Object} array */
-public class ArrayTabularRow implements TabularRow {
-       private final Object[] arr;
-
-       public ArrayTabularRow(List<?> objs) {
-               this.arr = objs.toArray();
-       }
-
-       public Object get(Integer col) {
-               return arr[col];
-       }
-
-       public int size() {
-               return arr.length;
-       }
-
-       public Object[] toArray() {
-               return arr;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java
deleted file mode 100644 (file)
index 7f7ac1e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.cms.tabular;
-
-/** The column in a tabular content */
-public class TabularColumn {
-       private String name;
-       /**
-        * JCR types, see
-        * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
-        * ?javax/jcr/PropertyType.html
-        */
-       private Integer type;
-
-       /** column with default type */
-       public TabularColumn(String name) {
-               super();
-               this.name = name;
-       }
-
-       public TabularColumn(String name, Integer type) {
-               super();
-               this.name = name;
-               this.type = type;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setName(String name) {
-               this.name = name;
-       }
-
-       public Integer getType() {
-               return type;
-       }
-
-       public void setType(Integer type) {
-               this.type = type;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java
deleted file mode 100644 (file)
index c6d2ab8..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.cms.tabular;
-
-import java.util.List;
-
-/**
- * Content organized as a table, possibly with headers. Only JCR types are
- * supported even though there is not direct dependency on JCR.
- */
-public interface TabularContent {
-       /** The headers of this table or <code>null</code> is none available. */
-       public List<TabularColumn> getColumns();
-
-       public TabularRowIterator read();
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java
deleted file mode 100644 (file)
index 69b9732..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.argeo.cms.tabular;
-
-/** A row of tabular data */
-public interface TabularRow {
-       /** The value at this column index */
-       public Object get(Integer col);
-
-       /** The raw objects (direct references) */
-       public Object[] toArray();
-
-       /** Number of columns */
-       public int size();
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java
deleted file mode 100644 (file)
index 7ad8719..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.argeo.cms.tabular;
-
-import java.util.Iterator;
-
-/** Navigation of rows */
-public interface TabularRowIterator extends Iterator<TabularRow> {
-       /**
-        * Current row number, has to be incremented by each call to next() ; starts at 0, will
-        * therefore be 1 for the first row returned.
-        */
-       public Long getCurrentRowNumber();
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java
deleted file mode 100644 (file)
index 34fc85b..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.cms.tabular;
-
-
-/** Write to a tabular content */
-public interface TabularWriter {
-       /** Append a new row of data */
-       public void appendRow(Object[] row);
-
-       /** Finish persisting data and release resources */
-       public void close();
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/package-info.java b/org.argeo.cms/src/org/argeo/cms/tabular/package-info.java
deleted file mode 100644 (file)
index 6cb48d0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Tabular format API. */
-package org.argeo.cms.tabular;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CompositeString.java b/org.argeo.cms/src/org/argeo/cms/util/CompositeString.java
new file mode 100644 (file)
index 0000000..8ea16f7
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.cms.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/** A name that can be expressed with various conventions. */
+public class CompositeString {
+       public final static Character UNDERSCORE = Character.valueOf('_');
+       public final static Character SPACE = Character.valueOf(' ');
+       public final static Character DASH = Character.valueOf('-');
+
+       private final String[] parts;
+
+       // optimisation
+       private final int hashCode;
+
+       public CompositeString(String str) {
+               Objects.requireNonNull(str, "String cannot be null");
+               if ("".equals(str.trim()))
+                       throw new IllegalArgumentException("String cannot be empty");
+               if (!str.equals(str.trim()))
+                       throw new IllegalArgumentException("String must be trimmed");
+               this.parts = toParts(str);
+               hashCode = hashCode(this.parts);
+       }
+
+       public String toString(char separator, boolean upperCase) {
+               StringBuilder sb = null;
+               for (String part : parts) {
+                       if (sb == null) {
+                               sb = new StringBuilder();
+                       } else {
+                               sb.append(separator);
+                       }
+                       sb.append(upperCase ? part.toUpperCase() : part);
+               }
+               return sb.toString();
+       }
+
+       public String toStringCaml(boolean firstCharUpperCase) {
+               StringBuilder sb = null;
+               for (String part : parts) {
+                       if (sb == null) {// first
+                               sb = new StringBuilder();
+                               sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
+                       } else {
+                               sb.append(Character.toUpperCase(part.charAt(0)));
+                       }
+
+                       if (part.length() > 1)
+                               sb.append(part.substring(1));
+               }
+               return sb.toString();
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj == null || !(obj instanceof CompositeString))
+                       return false;
+
+               CompositeString other = (CompositeString) obj;
+               return Arrays.equals(parts, other.parts);
+       }
+
+       @Override
+       public String toString() {
+               return toString(DASH, false);
+       }
+
+       public static String[] toParts(String str) {
+               Character separator = null;
+               if (str.indexOf(UNDERSCORE) >= 0) {
+                       checkNo(str, SPACE);
+                       checkNo(str, DASH);
+                       separator = UNDERSCORE;
+               } else if (str.indexOf(DASH) >= 0) {
+                       checkNo(str, SPACE);
+                       checkNo(str, UNDERSCORE);
+                       separator = DASH;
+               } else if (str.indexOf(SPACE) >= 0) {
+                       checkNo(str, DASH);
+                       checkNo(str, UNDERSCORE);
+                       separator = SPACE;
+               }
+
+               List<String> res = new ArrayList<>();
+               if (separator != null) {
+                       StringTokenizer st = new StringTokenizer(str, separator.toString());
+                       while (st.hasMoreTokens()) {
+                               res.add(st.nextToken().toLowerCase());
+                       }
+               } else {
+                       // single
+                       String strLowerCase = str.toLowerCase();
+                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
+                               return new String[] { strLowerCase };
+
+                       // CAML
+                       StringBuilder current = null;
+                       for (char c : str.toCharArray()) {
+                               if (Character.isUpperCase(c)) {
+                                       if (current != null)
+                                               res.add(current.toString());
+                                       current = new StringBuilder();
+                               }
+                               if (current == null)// first char is lower case
+                                       current = new StringBuilder();
+                               current.append(Character.toLowerCase(c));
+                       }
+                       res.add(current.toString());
+               }
+               return res.toArray(new String[res.size()]);
+       }
+
+       private static void checkNo(String str, Character c) {
+               if (str.indexOf(c) >= 0) {
+                       throw new IllegalArgumentException("Only one kind of sperator is allowed");
+               }
+       }
+
+       private static int hashCode(String[] parts) {
+               int hashCode = 0;
+               for (String part : parts) {
+                       hashCode = hashCode + part.hashCode();
+               }
+               return hashCode;
+       }
+
+       static boolean smokeTests() {
+               CompositeString plainName = new CompositeString("NAME");
+               assert "name".equals(plainName.toString());
+               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
+               assert "name".equals(plainName.toString(UNDERSCORE, false));
+               assert "name".equals(plainName.toStringCaml(false));
+               assert "Name".equals(plainName.toStringCaml(true));
+
+               CompositeString camlName = new CompositeString("myComplexName");
+
+               assert new CompositeString("my-complex-name").equals(camlName);
+               assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
+               assert new CompositeString("My complex Name").equals(camlName);
+               assert new CompositeString("MyComplexName").equals(camlName);
+
+               assert "my-complex-name".equals(camlName.toString());
+               assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
+               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
+               assert "myComplexName".equals(camlName.toStringCaml(false));
+               assert "MyComplexName".equals(camlName.toStringCaml(true));
+
+               return CompositeString.class.desiredAssertionStatus();
+       }
+
+       public static void main(String[] args) {
+               System.out.println(smokeTests());
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvParser.java b/org.argeo.cms/src/org/argeo/cms/util/CsvParser.java
new file mode 100644 (file)
index 0000000..f22a1e4
--- /dev/null
@@ -0,0 +1,242 @@
+package org.argeo.cms.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses a CSV file interpreting the first line as a header. The
+ * {@link #parse(InputStream)} method and the setters are synchronized so that
+ * the object cannot be modified when parsing.
+ */
+public abstract class CsvParser {
+       private char separator = ',';
+       private char quote = '\"';
+
+       private Boolean noHeader = false;
+       private Boolean strictLineAsLongAsHeader = true;
+
+       /**
+        * Actually process a parsed line. If
+        * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
+        * and the tokens are guaranteed to have the same size.
+        * 
+        * @param lineNumber the current line number, starts at 1 (the header, if header
+        *                   processing is enabled, the first line otherwise)
+        * @param header     the read-only header or null if
+        *                   {@link #setNoHeader(Boolean)} is true (default is false)
+        * @param tokens     the parsed tokens
+        */
+       protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in the stream to parse
+        * 
+        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+        */
+       @Deprecated
+       public synchronized void parse(InputStream in) {
+               parse(in, (Charset) null);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in       the stream to parse
+        * @param encoding the encoding to use.
+        * 
+        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+        */
+       @Deprecated
+       public synchronized void parse(InputStream in, String encoding) {
+               Reader reader;
+               if (encoding == null)
+                       reader = new InputStreamReader(in);
+               else
+                       try {
+                               reader = new InputStreamReader(in, encoding);
+                       } catch (UnsupportedEncodingException e) {
+                               throw new IllegalArgumentException(e);
+                       }
+               parse(reader);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in      the stream to parse
+        * @param charset the charset to use
+        */
+       public synchronized void parse(InputStream in, Charset charset) {
+               Reader reader;
+               if (charset == null)
+                       reader = new InputStreamReader(in);
+               else
+                       reader = new InputStreamReader(in, charset);
+               parse(reader);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param reader the reader to use (it will be buffered)
+        */
+       public synchronized void parse(Reader reader) {
+               Integer lineCount = 0;
+               try (BufferedReader bufferedReader = new BufferedReader(reader)) {
+                       List<String> header = null;
+                       if (!noHeader) {
+                               String headerStr = bufferedReader.readLine();
+                               if (headerStr == null)// empty file
+                                       return;
+                               lineCount++;
+                               header = new ArrayList<String>();
+                               StringBuffer currStr = new StringBuffer("");
+                               Boolean wasInquote = false;
+                               while (parseLine(headerStr, header, currStr, wasInquote)) {
+                                       headerStr = bufferedReader.readLine();
+                                       if (headerStr == null)
+                                               break;
+                                       wasInquote = true;
+                               }
+                               header = Collections.unmodifiableList(header);
+                       }
+
+                       String line = null;
+                       lines: while ((line = bufferedReader.readLine()) != null) {
+                               line = preProcessLine(line);
+                               if (line == null) {
+                                       // skip line
+                                       continue lines;
+                               }
+                               lineCount++;
+                               List<String> tokens = new ArrayList<String>();
+                               StringBuffer currStr = new StringBuffer("");
+                               Boolean wasInquote = false;
+                               sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
+                                       line = bufferedReader.readLine();
+                                       if (line == null)
+                                               break sublines;
+                                       wasInquote = true;
+                               }
+                               if (!noHeader && strictLineAsLongAsHeader) {
+                                       int headerSize = header.size();
+                                       int tokenSize = tokens.size();
+                                       if (tokenSize == 1 && line.trim().equals(""))
+                                               continue lines;// empty line
+                                       if (headerSize != tokenSize) {
+                                               throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
+                                                               + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
+                                                               + ", tokens: " + tokens);
+                                       }
+                               }
+                               processLine(lineCount, header, tokens);
+                       }
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
+               }
+       }
+
+       /**
+        * Called before each (logical) line is processed, giving a change to modify it
+        * (typically for cleaning dirty files). To be overridden, return the line
+        * unchanged by default. Skip the line if 'null' is returned.
+        */
+       protected String preProcessLine(String line) {
+               return line;
+       }
+
+       /**
+        * Parses a line character by character for performance purpose
+        * 
+        * @return whether to continue parsing this line
+        */
+       protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
+               if (wasInquote)
+                       currStr.append('\n');
+
+               char[] arr = str.toCharArray();
+               boolean inQuote = wasInquote;
+               for (int i = 0; i < arr.length; i++) {
+                       char c = arr[i];
+                       if (c == separator) {
+                               if (!inQuote) {
+                                       tokens.add(currStr.toString());
+//                                     currStr.delete(0, currStr.length());
+                                       currStr.setLength(0);
+                                       currStr.trimToSize();
+                               } else {
+                                       // we don't remove separator that are in a quoted substring
+                                       // System.out
+                                       // .println("IN QUOTE, got a separator: [" + c + "]");
+                                       currStr.append(c);
+                               }
+                       } else if (c == quote) {
+                               if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
+                                       // case of double quote
+                                       currStr.append(quote);
+                                       i++;
+                               } else {// standard
+                                       inQuote = inQuote ? false : true;
+                               }
+                       } else {
+                               currStr.append(c);
+                       }
+               }
+
+               if (!inQuote) {
+                       tokens.add(currStr.toString());
+                       // System.out.println("# TOKEN: " + currStr);
+               }
+               // if (inQuote)
+               // throw new ArgeoException("Missing quote at the end of the line "
+               // + str + " (parsed: " + tokens + ")");
+               if (inQuote)
+                       return true;
+               else
+                       return false;
+               // return tokens;
+       }
+
+       public char getSeparator() {
+               return separator;
+       }
+
+       public synchronized void setSeparator(char separator) {
+               this.separator = separator;
+       }
+
+       public char getQuote() {
+               return quote;
+       }
+
+       public synchronized void setQuote(char quote) {
+               this.quote = quote;
+       }
+
+       public Boolean getNoHeader() {
+               return noHeader;
+       }
+
+       public synchronized void setNoHeader(Boolean noHeader) {
+               this.noHeader = noHeader;
+       }
+
+       public Boolean getStrictLineAsLongAsHeader() {
+               return strictLineAsLongAsHeader;
+       }
+
+       public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
+               this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java b/org.argeo.cms/src/org/argeo/cms/util/CsvParserWithLinesAsMap.java
new file mode 100644 (file)
index 0000000..0a0382c
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.cms.util;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CSV parser allowing to process lines as maps whose keys are the header
+ * fields.
+ */
+public abstract class CsvParserWithLinesAsMap extends CsvParser {
+
+       /**
+        * Actually processes a line.
+        * 
+        * @param lineNumber the current line number, starts at 1 (the header, if header
+        *                   processing is enabled, the first lien otherwise)
+        * @param line       the parsed tokens as a map whose keys are the header fields
+        */
+       protected abstract void processLine(Integer lineNumber, Map<String, String> line);
+
+       protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
+               if (header == null)
+                       throw new IllegalArgumentException("Only CSV with header is supported");
+               Map<String, String> line = new HashMap<String, String>();
+               for (int i = 0; i < header.size(); i++) {
+                       String key = header.get(i);
+                       String value = null;
+                       if (i < tokens.size())
+                               value = tokens.get(i);
+                       line.put(key, value);
+               }
+               processLine(lineNumber, line);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java b/org.argeo.cms/src/org/argeo/cms/util/CsvWriter.java
new file mode 100644 (file)
index 0000000..902e6bb
--- /dev/null
@@ -0,0 +1,156 @@
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.List;
+
+/** Write in CSV format. */
+public class CsvWriter {
+       private final Writer out;
+
+       private char separator = ',';
+       private char quote = '\"';
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out the stream to write to. Caller is responsible for closing it.
+        * 
+        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+        * 
+        */
+       @Deprecated
+       public CsvWriter(OutputStream out) {
+               this.out = new OutputStreamWriter(out);
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out      the stream to write to. Caller is responsible for closing it.
+        * @param encoding the encoding to use.
+        * 
+        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+        */
+       @Deprecated
+       public CsvWriter(OutputStream out, String encoding) {
+               try {
+                       this.out = new OutputStreamWriter(out, encoding);
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException(e);
+               }
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out     the stream to write to. Caller is responsible for closing it.
+        * @param charset the charset to use
+        */
+       public CsvWriter(OutputStream out, Charset charset) {
+               this.out = new OutputStreamWriter(out, charset);
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out the stream to write to. Caller is responsible for closing it.
+        */
+       public CsvWriter(Writer writer) {
+               this.out = writer;
+       }
+
+       /**
+        * Write a CSV line. Also used to write a header if needed (this is transparent
+        * for the CSV writer): simply call it first, before writing the lines.
+        */
+       public void writeLine(List<?> tokens) {
+               try {
+                       Iterator<?> it = tokens.iterator();
+                       while (it.hasNext()) {
+                               Object obj = it.next();
+                               writeToken(obj != null ? obj.toString() : null);
+                               if (it.hasNext())
+                                       out.write(separator);
+                       }
+                       out.write('\n');
+                       out.flush();
+               } catch (IOException e) {
+                       throw new RuntimeException("Could not write " + tokens, e);
+               }
+       }
+
+       /**
+        * Write a CSV line. Also used to write a header if needed (this is transparent
+        * for the CSV writer): simply call it first, before writing the lines.
+        */
+       public void writeLine(Object... tokens) {
+               try {
+                       for (int i = 0; i < tokens.length; i++) {
+                               if (tokens[i] == null) {
+                                       writeToken(null);
+                               } else {
+                                       writeToken(tokens[i].toString());
+                               }
+                               if (i != (tokens.length - 1))
+                                       out.write(separator);
+                       }
+                       out.write('\n');
+                       out.flush();
+               } catch (IOException e) {
+                       throw new RuntimeException("Could not write " + tokens, e);
+               }
+       }
+
+       protected void writeToken(String token) throws IOException {
+               if (token == null) {
+                       // TODO configure how to deal with null
+                       out.write("");
+                       return;
+               }
+               // +2 for possible quotes, another +2 assuming there would be an already
+               // quoted string where quotes needs to be duplicated
+               // another +2 for safety
+               // we don't want to increase buffer size while writing
+               StringBuffer buf = new StringBuffer(token.length() + 6);
+               char[] arr = token.toCharArray();
+               boolean shouldQuote = false;
+               for (char c : arr) {
+                       if (!shouldQuote) {
+                               if (c == separator)
+                                       shouldQuote = true;
+                               if (c == '\n')
+                                       shouldQuote = true;
+                       }
+
+                       if (c == quote) {
+                               shouldQuote = true;
+                               // duplicate quote
+                               buf.append(quote);
+                       }
+
+                       // generic case
+                       buf.append(c);
+               }
+
+               if (shouldQuote == true)
+                       out.write(quote);
+               out.write(buf.toString());
+               if (shouldQuote == true)
+                       out.write(quote);
+       }
+
+       public void setSeparator(char separator) {
+               this.separator = separator;
+       }
+
+       public void setQuote(char quote) {
+               this.quote = quote;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java b/org.argeo.cms/src/org/argeo/cms/util/CurrentSubject.java
new file mode 100644 (file)
index 0000000..6a3dcbc
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionException;
+
+import javax.security.auth.Subject;
+
+/**
+ * Prepare evolution of Java APIs introduced in JDK 18, as these static methods
+ * will be added to {@link Subject}.
+ */
+@SuppressWarnings("removal")
+public class CurrentSubject {
+
+       private final static boolean useThreadLocal = Boolean
+                       .parseBoolean(System.getProperty("jdk.security.auth.subject.useTL"));
+
+       private final static InheritableThreadLocal<Subject> current = new InheritableThreadLocal<>();
+
+       public static Subject current() {
+               if (useThreadLocal) {
+                       return current.get();
+               } else {// legacy
+                       Subject subject = Subject.getSubject(AccessController.getContext());
+                       return subject;
+               }
+       }
+
+       public static <T> T callAs(Subject subject, Callable<T> action) {
+               if (useThreadLocal) {
+                       Subject previous = current();
+                       current.set(subject);
+                       try {
+                               return action.call();
+                       } catch (Exception e) {
+                               throw new CompletionException("Failed to execute action for " + subject, e);
+                       } finally {
+                               current.set(previous);
+                       }
+               } else {// legacy
+                       try {
+                               return Subject.doAs(subject, new PrivilegedExceptionAction<T>() {
+
+                                       @Override
+                                       public T run() throws Exception {
+                                               return action.call();
+                                       }
+
+                               });
+                       } catch (PrivilegedActionException e) {
+                               throw new CompletionException("Failed to execute action for " + subject, e.getCause());
+                       } catch (Exception e) {
+                               throw new CompletionException("Failed to execute action for " + subject, e);
+                       }
+               }
+       }
+
+       /** Singleton. */
+       private CurrentSubject() {
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java b/org.argeo.cms/src/org/argeo/cms/util/DictionaryKeys.java
new file mode 100644 (file)
index 0000000..a9f6a31
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.util;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
+ * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
+ * for-each loops.
+ */
+class DictionaryKeys implements Iterable<String> {
+       private final Dictionary<String, ?> dictionary;
+
+       public DictionaryKeys(Dictionary<String, ?> dictionary) {
+               this.dictionary = dictionary;
+       }
+
+       @Override
+       public Iterator<String> iterator() {
+               return new KeyIterator(dictionary.keys());
+       }
+
+       private static class KeyIterator implements Iterator<String> {
+               private final Enumeration<String> keys;
+
+               KeyIterator(Enumeration<String> keys) {
+                       this.keys = keys;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return keys.hasMoreElements();
+               }
+
+               @Override
+               public String next() {
+                       return keys.nextElement();
+               }
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java b/org.argeo.cms/src/org/argeo/cms/util/DigestUtils.java
new file mode 100644 (file)
index 0000000..047749f
--- /dev/null
@@ -0,0 +1,202 @@
+package org.argeo.cms.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** Utilities around cryptographic digests */
+public class DigestUtils {
+       public final static String MD5 = "MD5";
+       public final static String SHA1 = "SHA1";
+       public final static String SHA256 = "SHA-256";
+       public final static String SHA512 = "SHA-512";
+
+       private static Boolean debug = false;
+       // TODO: make it configurable
+       private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
+
+       public static byte[] sha1(byte[]... bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(SHA1);
+                       for (byte[] arr : bytes)
+                               digest.update(arr);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new UnsupportedOperationException("SHA1 is not avalaible", e);
+               }
+       }
+
+       public static byte[] digestAsBytes(String algorithm, byte[]... bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       for (byte[] arr : bytes)
+                               digest.update(arr);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e);
+               }
+       }
+
+       public static String digest(String algorithm, byte[]... bytes) {
+               return toHexString(digestAsBytes(algorithm, bytes));
+       }
+
+       public static String digest(String algorithm, InputStream in) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       // ReadableByteChannel channel = Channels.newChannel(in);
+                       // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
+                       // while (channel.read(bb) > 0)
+                       // digest.update(bb);
+                       byte[] buffer = new byte[byteBufferCapacity];
+                       int read = 0;
+                       while ((read = in.read(buffer)) > 0) {
+                               digest.update(buffer, 0, read);
+                       }
+
+                       byte[] checksum = digest.digest();
+                       String res = toHexString(checksum);
+                       return res;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(in);
+               }
+       }
+
+       public static String digest(String algorithm, File file) {
+               FileInputStream fis = null;
+               FileChannel fc = null;
+               try {
+                       fis = new FileInputStream(file);
+                       fc = fis.getChannel();
+
+                       // Get the file's size and then map it into memory
+                       int sz = (int) fc.size();
+                       ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
+                       return digest(algorithm, bb);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
+               } finally {
+                       StreamUtils.closeQuietly(fis);
+                       if (fc.isOpen())
+                               try {
+                                       fc.close();
+                               } catch (IOException e) {
+                                       // silent
+                               }
+               }
+       }
+
+       protected static String digest(String algorithm, ByteBuffer bb) {
+               long begin = System.currentTimeMillis();
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       digest.update(bb);
+                       byte[] checksum = digest.digest();
+                       String res = toHexString(checksum);
+                       long end = System.currentTimeMillis();
+                       if (debug)
+                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+                       return res;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+               }
+       }
+
+       public static String sha1hex(Path path) {
+               return digest(SHA1, path, byteBufferCapacity);
+       }
+
+       public static String digest(String algorithm, Path path, long bufferSize) {
+               byte[] digest = digestAsBytes(algorithm, path, bufferSize);
+               return toHexString(digest);
+       }
+
+       public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) {
+               long begin = System.currentTimeMillis();
+               try {
+                       MessageDigest md = MessageDigest.getInstance(algorithm);
+                       FileChannel fc = FileChannel.open(file);
+                       long fileSize = Files.size(file);
+                       if (fileSize <= bufferSize) {
+                               ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
+                               md.update(bb);
+                       } else {
+                               long lastCycle = (fileSize / bufferSize) - 1;
+                               long position = 0;
+                               for (int i = 0; i <= lastCycle; i++) {
+                                       ByteBuffer bb;
+                                       if (i != lastCycle) {
+                                               bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
+                                               position = position + bufferSize;
+                                       } else {
+                                               bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
+                                               position = fileSize;
+                                       }
+                                       md.update(bb);
+                               }
+                       }
+                       long end = System.currentTimeMillis();
+                       if (debug)
+                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+                       return md.digest();
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest " + file + "  with algorithm " + algorithm, e);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot digest " + file + "  with algorithm " + algorithm, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               File file;
+               if (args.length > 0)
+                       file = new File(args[0]);
+               else {
+                       System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
+                                       + "docs/guide/security/CryptoSpec.html#AppA)");
+                       return;
+               }
+
+               if (args.length > 1) {
+                       String algorithm = args[1];
+                       System.out.println(digest(algorithm, file));
+               } else {
+                       String algorithm = "MD5";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       algorithm = "SHA";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       System.out.println(algorithm + ": " + sha1hex(file.toPath()));
+                       algorithm = "SHA-256";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       algorithm = "SHA-512";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+               }
+       }
+
+       final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+       /** Converts a byte array to an hex String. */
+       public static String toHexString(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 2];
+               for (int j = 0; j < bytes.length; j++) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 2] = hexArray[v >>> 4];
+                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+               }
+               return new String(hexChars);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/DirH.java b/org.argeo.cms/src/org/argeo/cms/util/DirH.java
new file mode 100644 (file)
index 0000000..2596c61
--- /dev/null
@@ -0,0 +1,116 @@
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Hashes the hashes of the files in a directory. */
+public class DirH {
+
+       private final static Charset charset = Charset.forName("UTF-16");
+       private final static long bufferSize = 200 * 1024 * 1024;
+       private final static String algorithm = "SHA";
+
+       private final static byte EOL = (byte) '\n';
+       private final static byte SPACE = (byte) ' ';
+
+       private final int hashSize;
+
+       private final byte[][] hashes;
+       private final byte[][] fileNames;
+       private final byte[] digest;
+       private final byte[] dirName;
+
+       /**
+        * @param dirName can be null or empty
+        */
+       private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
+               if (hashes.length != fileNames.length)
+                       throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
+               this.hashes = hashes;
+               this.fileNames = fileNames;
+               this.dirName = dirName == null ? new byte[0] : dirName;
+               if (hashes.length == 0) {// empty dir
+                       hashSize = 20;
+                       // FIXME what is the digest of an empty dir?
+                       digest = new byte[hashSize];
+                       Arrays.fill(digest, SPACE);
+                       return;
+               }
+               hashSize = hashes[0].length;
+               for (int i = 0; i < hashes.length; i++) {
+                       if (hashes[i].length != hashSize)
+                               throw new IllegalArgumentException(
+                                               "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
+               }
+
+               try {
+                       MessageDigest md = MessageDigest.getInstance(algorithm);
+                       for (int i = 0; i < hashes.length; i++) {
+                               md.update(this.hashes[i]);
+                               md.update(SPACE);
+                               md.update(this.fileNames[i]);
+                               md.update(EOL);
+                       }
+                       digest = md.digest();
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest", e);
+               }
+       }
+
+       public void print(PrintStream out) {
+               out.print(DigestUtils.toHexString(digest));
+               if (dirName.length > 0) {
+                       out.print(' ');
+                       out.print(new String(dirName, charset));
+               }
+               out.print('\n');
+               for (int i = 0; i < hashes.length; i++) {
+                       out.print(DigestUtils.toHexString(hashes[i]));
+                       out.print(' ');
+                       out.print(new String(fileNames[i], charset));
+                       out.print('\n');
+               }
+       }
+
+       public static DirH digest(Path dir) {
+               try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
+                       List<byte[]> hs = new ArrayList<byte[]>();
+                       List<String> fNames = new ArrayList<>();
+                       for (Path file : files) {
+                               if (!Files.isDirectory(file)) {
+                                       byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize);
+                                       hs.add(digest);
+                                       fNames.add(file.getFileName().toString());
+                               }
+                       }
+
+                       byte[][] fileNames = new byte[fNames.size()][];
+                       for (int i = 0; i < fNames.size(); i++) {
+                               fileNames[i] = fNames.get(i).getBytes(charset);
+                       }
+                       byte[][] hashes = hs.toArray(new byte[hs.size()][]);
+                       return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot digest " + dir, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               try {
+                       DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
+                       dirH.print(System.out);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java b/org.argeo.cms/src/org/argeo/cms/util/ExceptionsChain.java
new file mode 100644 (file)
index 0000000..e71cfb3
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.cms.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Serialisable wrapper of a {@link Throwable}. typically to be written as XML
+ * or JSON in a server error response.
+ */
+public class ExceptionsChain {
+       private List<SystemException> exceptions = new ArrayList<>();
+
+       public ExceptionsChain() {
+       }
+
+       public ExceptionsChain(Throwable exception) {
+               writeException(exception);
+       }
+
+       /** recursive */
+       protected void writeException(Throwable exception) {
+               SystemException systemException = new SystemException(exception);
+               exceptions.add(systemException);
+               Throwable cause = exception.getCause();
+               if (cause != null)
+                       writeException(cause);
+       }
+
+       public List<SystemException> getExceptions() {
+               return exceptions;
+       }
+
+       public void setExceptions(List<SystemException> exceptions) {
+               this.exceptions = exceptions;
+       }
+
+       /** An exception in the chain. */
+       public static class SystemException {
+               private String type;
+               private String message;
+               private List<String> stackTrace;
+
+               public SystemException() {
+               }
+
+               public SystemException(Throwable exception) {
+                       this.type = exception.getClass().getName();
+                       this.message = exception.getMessage();
+                       this.stackTrace = new ArrayList<>();
+                       StackTraceElement[] elems = exception.getStackTrace();
+                       for (int i = 0; i < elems.length; i++)
+                               stackTrace.add("at " + elems[i].toString());
+               }
+
+               public String getType() {
+                       return type;
+               }
+
+               public void setType(String type) {
+                       this.type = type;
+               }
+
+               public String getMessage() {
+                       return message;
+               }
+
+               public void setMessage(String message) {
+                       this.message = message;
+               }
+
+               public List<String> getStackTrace() {
+                       return stackTrace;
+               }
+
+               public void setStackTrace(List<String> stackTrace) {
+                       this.stackTrace = stackTrace;
+               }
+
+               @Override
+               public String toString() {
+                       return "System exception: " + type + ", " + message + ", " + stackTrace;
+               }
+
+       }
+
+       @Override
+       public String toString() {
+               return exceptions.toString();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/FsUtils.java b/org.argeo.cms/src/org/argeo/cms/util/FsUtils.java
new file mode 100644 (file)
index 0000000..26c05b6
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** Utilities around the standard Java file abstractions. */
+public class FsUtils {
+
+       /** Deletes this path, recursively if needed. */
+       public static void copyDirectory(Path source, Path target) {
+               if (!Files.exists(source) || !Files.isDirectory(source))
+                       throw new IllegalArgumentException(source + " is not a directory");
+               if (Files.exists(target) && !Files.isDirectory(target))
+                       throw new IllegalArgumentException(target + " is not a directory");
+               try {
+                       Files.createDirectories(target);
+                       Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
+
+                               @Override
+                               public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException {
+                                       Path relativePath = source.relativize(directory);
+                                       Path targetDirectory = target.resolve(relativePath);
+                                       if (!Files.exists(targetDirectory))
+                                               Files.createDirectory(targetDirectory);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Path relativePath = source.relativize(file);
+                                       Path targetFile = target.resolve(relativePath);
+                                       Files.copy(file, targetFile);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot copy " + source + " to " + target, e);
+               }
+
+       }
+
+       /**
+        * Deletes this path, recursively if needed. Does nothing if the path does not
+        * exist.
+        */
+       public static void delete(Path path) {
+               try {
+                       if (!Files.exists(path))
+                               return;
+                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                               @Override
+                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+                                       if (e != null)
+                                               throw e;
+                                       Files.delete(directory);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Files.delete(file);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot delete " + path, e);
+               }
+       }
+
+       /** Singleton. */
+       private FsUtils() {
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/LangUtils.java b/org.argeo.cms/src/org/argeo/cms/util/LangUtils.java
new file mode 100644 (file)
index 0000000..0e21427
--- /dev/null
@@ -0,0 +1,331 @@
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Utilities around Java basic features. */
+public class LangUtils {
+       /*
+        * NON-API OSGi
+        */
+       /**
+        * Returns an array with the names of the provided classes. Useful when
+        * registering services with multiple interfaces in OSGi.
+        */
+       public static String[] names(Class<?>... clzz) {
+               String[] res = new String[clzz.length];
+               for (int i = 0; i < clzz.length; i++)
+                       res[i] = clzz[i].getName();
+               return res;
+       }
+
+//     /*
+//      * MAP
+//      */
+//     /**
+//      * Creates a new {@link Map} with one key-value pair. Key should not be null,
+//      * but if the value is null, it returns an empty {@link Map}.
+//      * 
+//      * @deprecated Use {@link Collections#singletonMap(Object, Object)} instead.
+//      */
+//     @Deprecated
+//     public static Map<String, Object> map(String key, Object value) {
+//             assert key != null;
+//             HashMap<String, Object> props = new HashMap<>();
+//             if (value != null)
+//                     props.put(key, value);
+//             return props;
+//     }
+
+       /*
+        * DICTIONARY
+        */
+
+       /**
+        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+        * null, but if the value is null, it returns an empty {@link Dictionary}.
+        */
+       public static Dictionary<String, Object> dict(String key, Object value) {
+               assert key != null;
+               Hashtable<String, Object> props = new Hashtable<>();
+               if (value != null)
+                       props.put(key, value);
+               return props;
+       }
+
+       /** @deprecated Use {@link #dict(String, Object)} instead. */
+       @Deprecated
+       public static Dictionary<String, Object> dico(String key, Object value) {
+               return dict(key, value);
+       }
+
+       /** Converts a {@link Dictionary} to a {@link Map} of strings. */
+       public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
+               if (properties == null) {
+                       return null;
+               }
+               Map<String, String> res = new HashMap<>(properties.size());
+               Enumeration<String> keys = properties.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       res.put(key, properties.get(key).toString());
+               }
+               return res;
+       }
+
+       /** Converts a {@link Dictionary} to a {@link Map}. */
+       public static Map<String, Object> dictToMap(Dictionary<String, ?> properties) {
+               if (properties == null) {
+                       return null;
+               }
+               Map<String, Object> res = new HashMap<>(properties.size());
+               Enumeration<String> keys = properties.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       res.put(key, properties.get(key));
+               }
+               return res;
+       }
+
+       /**
+        * Get a string property from this map, expecting to find it, or
+        * <code>null</code> if not found.
+        */
+       public static String get(Map<String, ?> map, String key) {
+               Object res = map.get(key);
+               if (res == null)
+                       return null;
+               return res.toString();
+       }
+
+       /**
+        * Get a string property from this map, expecting to find it.
+        * 
+        * @throws IllegalArgumentException if the key was not found
+        */
+       public static String getNotNull(Map<String, ?> map, String key) {
+               Object res = map.get(key);
+               if (res == null)
+                       throw new IllegalArgumentException("Map " + map + " should contain key " + key);
+               return res.toString();
+       }
+
+       /**
+        * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
+        */
+       public static Iterable<String> keys(Dictionary<String, ?> props) {
+               assert props != null;
+               return new DictionaryKeys(props);
+       }
+
+       static String toJson(Dictionary<String, ?> props) {
+               return toJson(props, false);
+       }
+
+       static String toJson(Dictionary<String, ?> props, boolean pretty) {
+               StringBuilder sb = new StringBuilder();
+               sb.append('{');
+               if (pretty)
+                       sb.append('\n');
+               Enumeration<String> keys = props.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       if (pretty)
+                               sb.append(' ');
+                       sb.append('\"').append(key).append('\"');
+                       if (pretty)
+                               sb.append(" : ");
+                       else
+                               sb.append(':');
+                       sb.append('\"').append(props.get(key)).append('\"');
+                       if (keys.hasMoreElements())
+                               sb.append(", ");
+                       if (pretty)
+                               sb.append('\n');
+               }
+               sb.append('}');
+               return sb.toString();
+       }
+
+       static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
+               if (props == null)
+                       throw new IllegalArgumentException("Props cannot be null");
+               Properties toStore = new Properties();
+               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                       String key = keys.nextElement();
+                       toStore.setProperty(key, props.get(key).toString());
+               }
+               try (OutputStream out = Files.newOutputStream(path)) {
+                       toStore.store(out, null);
+               }
+       }
+
+       static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
+                       throws IOException {
+               if (props == null)
+                       throw new IllegalArgumentException("Props cannot be null");
+               Object dnValue = props.get(dnKey);
+               String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
+               LdapName dn;
+               try {
+                       dn = new LdapName(dnStr);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
+               }
+               if (dnValue == null)
+                       throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
+               try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+                       writer.append("\ndn: ");
+                       writer.append(dn.toString());
+                       writer.append('\n');
+                       for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                               String key = keys.nextElement();
+                               Object value = props.get(key);
+                               writer.append(key);
+                               writer.append(": ");
+                               // FIXME deal with binary and multiple values
+                               writer.append(value.toString());
+                               writer.append('\n');
+                       }
+               }
+       }
+
+       static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
+               Properties toLoad = new Properties();
+               try (InputStream in = Files.newInputStream(path)) {
+                       toLoad.load(in);
+               }
+               Dictionary<String, Object> res = new Hashtable<String, Object>();
+               for (Object key : toLoad.keySet())
+                       res.put(key.toString(), toLoad.get(key));
+               return res;
+       }
+
+       /*
+        * COLLECTIONS
+        */
+       /**
+        * Convert a comma-separated separated {@link String} or a {@link String} array
+        * to a {@link List} of {@link String}, trimming them. Useful to quickly
+        * interpret OSGi services properties.
+        * 
+        * @return a {@link List} containing the trimmed {@link String}s, or an empty
+        *         {@link List} if the argument was <code>null</code>.
+        */
+       public static List<String> toStringList(Object value) {
+               List<String> values = new ArrayList<>();
+               if (value == null)
+                       return values;
+               String[] arr;
+               if (value instanceof String) {
+                       arr = ((String) value).split(",");
+               } else if (value instanceof String[]) {
+                       arr = (String[]) value;
+               } else {
+                       throw new IllegalArgumentException("Unsupported value type " + value.getClass());
+               }
+               for (String str : arr) {
+                       values.add(str.trim());
+               }
+               return values;
+       }
+
+       /** Size of an {@link Iterable}, optimised if it is a {@link Collection}. */
+       public static int size(Iterable<?> iterable) {
+               if (iterable instanceof Collection)
+                       return ((Collection<?>) iterable).size();
+
+               int size = 0;
+               for (Iterator<?> it = iterable.iterator(); it.hasNext(); size++)
+                       it.next();
+               return size;
+       }
+
+       public static <T> T getAt(Iterable<T> iterable, int index) {
+               if (iterable instanceof List) {
+                       List<T> lst = ((List<T>) iterable);
+                       if (index >= lst.size())
+                               throw new IllegalArgumentException("Index " + index + " is not available (size is " + lst.size() + ")");
+                       return lst.get(index);
+               }
+               int i = 0;
+               for (Iterator<T> it = iterable.iterator(); it.hasNext(); i++) {
+                       if (i == index)
+                               return it.next();
+                       else
+                               it.next();
+               }
+               throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
+       }
+
+       /*
+        * EXCEPTIONS
+        */
+       /**
+        * Chain the messages of all causes (one per line, <b>starts with a line
+        * return</b>) without all the stack
+        */
+       public static String chainCausesMessages(Throwable t) {
+               StringBuffer buf = new StringBuffer();
+               chainCauseMessage(buf, t);
+               return buf.toString();
+       }
+
+       /** Recursive chaining of messages */
+       private static void chainCauseMessage(StringBuffer buf, Throwable t) {
+               buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
+               if (t.getCause() != null)
+                       chainCauseMessage(buf, t.getCause());
+       }
+
+       /*
+        * TIME
+        */
+       /** Formats time elapsed since start. */
+       public static String since(ZonedDateTime start) {
+               ZonedDateTime now = ZonedDateTime.now();
+               return duration(start, now);
+       }
+
+       /** Formats a duration. */
+       public static String duration(Temporal start, Temporal end) {
+               long count = ChronoUnit.DAYS.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " days" : count + " day";
+               count = ChronoUnit.HOURS.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " hours" : count + " hours";
+               count = ChronoUnit.MINUTES.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " minutes" : count + " minute";
+               count = ChronoUnit.SECONDS.between(start, end);
+               return count > 1 ? count + " seconds" : count + " second";
+       }
+
+       /** Singleton constructor. */
+       private LangUtils() {
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/OS.java b/org.argeo.cms/src/org/argeo/cms/util/OS.java
new file mode 100644 (file)
index 0000000..c63d7a1
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.util;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/** When OS specific informations are needed. */
+public class OS {
+       public final static OS LOCAL = new OS();
+
+       private final String arch, name, version;
+
+       /** The OS of the running JVM */
+       protected OS() {
+               arch = System.getProperty("os.arch");
+               name = System.getProperty("os.name");
+               version = System.getProperty("os.version");
+       }
+
+       public String getArch() {
+               return arch;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public String getVersion() {
+               return version;
+       }
+
+       public boolean isMSWindows() {
+               // only MS Windows would use such an horrendous separator...
+               return File.separatorChar == '\\';
+       }
+
+       public String[] getDefaultShellCommand() {
+               if (!isMSWindows())
+                       return new String[] { "/bin/bash", "-l", "-i" };
+               else
+                       return new String[] { "cmd.exe", "/C" };
+       }
+
+       public static long getJvmPid() {
+               return ProcessHandle.current().pid();
+//             String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
+//             return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
+       }
+
+       /**
+        * Get the runtime directory. It will be the environment variable
+        * XDG_RUNTIME_DIR if it is set, or ~/.cache/argeo if not.
+        */
+       public static Path getRunDir() {
+               Path runDir;
+               String xdgRunDir = System.getenv("XDG_RUNTIME_DIR");
+               if (xdgRunDir != null) {
+                       // TODO support multiple names
+                       runDir = Paths.get(xdgRunDir);
+               } else {
+                       runDir = Paths.get(System.getProperty("user.home"), ".cache/argeo");
+               }
+               return runDir;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java b/org.argeo.cms/src/org/argeo/cms/util/PasswordEncryption.java
new file mode 100644 (file)
index 0000000..c50f415
--- /dev/null
@@ -0,0 +1,216 @@
+package org.argeo.cms.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PasswordEncryption {
+       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+       /** Stronger with 256, but causes problem with Oracle JVM */
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
+       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+//     public final static String DEFAULT_CHARSET = "UTF-8";
+       public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
+       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+       private String cipherName = DEFAULT_CIPHER_NAME;
+
+       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+
+       private Key key;
+       private Cipher ecipher;
+       private Cipher dcipher;
+
+       private String securityProviderName = null;
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copy of nor
+        * reference to the passed array is kept
+        */
+       public PasswordEncryption(char[] password) {
+               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
+       }
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copies of nor
+        * references to the passed arrays are kept
+        */
+       public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
+               try {
+                       initKeyAndCiphers(password, passwordSalt, initializationVector);
+               } catch (InvalidKeyException e) {
+                       Integer previousSecreteKeyLength = secreteKeyLength;
+                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
+                       System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
+                                       + " secrete key length instead of " + previousSecreteKeyLength);
+                       try {
+                               initKeyAndCiphers(password, passwordSalt, initializationVector);
+                       } catch (GeneralSecurityException e1) {
+                               throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
+                       }
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalStateException("Cannot get secret key", e);
+               }
+       }
+
+       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
+                       throws GeneralSecurityException {
+               byte[] salt = new byte[8];
+               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
+               // for (int i = 0; i < password.length && i < salt.length; i++)
+               // salt[i] = (byte) password[i];
+               byte[] iv = new byte[16];
+               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
+
+               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
+               PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
+               String secKeyEncryption = getSecretKeyEncryption();
+               if (secKeyEncryption != null) {
+                       SecretKey tmp = keyFac.generateSecret(keySpec);
+                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
+               } else {
+                       key = keyFac.generateSecret(keySpec);
+               }
+               if (securityProviderName != null)
+                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
+               else
+                       ecipher = Cipher.getInstance(getCipherName());
+               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+               dcipher = Cipher.getInstance(getCipherName());
+               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+       }
+
+       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
+               try {
+                       CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
+                       StreamUtils.copy(decryptedIn, out);
+                       StreamUtils.closeQuietly(out);
+               } catch (IOException e) {
+                       throw e;
+               } finally {
+                       StreamUtils.closeQuietly(decryptedIn);
+               }
+       }
+
+       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
+               try {
+                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
+                       StreamUtils.copy(decryptedIn, decryptedOut);
+               } catch (IOException e) {
+                       throw e;
+               } finally {
+                       StreamUtils.closeQuietly(encryptedIn);
+               }
+       }
+
+       public byte[] encryptString(String str) {
+               ByteArrayOutputStream out = null;
+               ByteArrayInputStream in = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
+                       encrypt(in, out);
+                       return out.toByteArray();
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       /** Closes the input stream */
+       public String decryptAsString(InputStream in) {
+               ByteArrayOutputStream out = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       decrypt(in, out);
+                       return new String(out.toByteArray(), DEFAULT_CHARSET);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       protected Key getKey() {
+               return key;
+       }
+
+       protected Cipher getEcipher() {
+               return ecipher;
+       }
+
+       protected Cipher getDcipher() {
+               return dcipher;
+       }
+
+       protected Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       protected Integer getKeyLength() {
+               return secreteKeyLength;
+       }
+
+       protected String getSecretKeyFactoryName() {
+               return secreteKeyFactoryName;
+       }
+
+       protected String getSecretKeyEncryption() {
+               return secreteKeyEncryption;
+       }
+
+       protected String getCipherName() {
+               return cipherName;
+       }
+
+       public void setIterationCount(Integer iterationCount) {
+               this.iterationCount = iterationCount;
+       }
+
+       public void setSecreteKeyLength(Integer keyLength) {
+               this.secreteKeyLength = keyLength;
+       }
+
+       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
+               this.secreteKeyFactoryName = secreteKeyFactoryName;
+       }
+
+       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
+               this.secreteKeyEncryption = secreteKeyEncryption;
+       }
+
+       public void setCipherName(String cipherName) {
+               this.cipherName = cipherName;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java b/org.argeo.cms/src/org/argeo/cms/util/ServiceChannel.java
new file mode 100644 (file)
index 0000000..8cdbcad
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.CompletionHandler;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
+public class ServiceChannel implements AsynchronousByteChannel {
+       private final ReadableByteChannel in;
+       private final WritableByteChannel out;
+
+       private boolean open = true;
+
+       private ExecutorService executor;
+
+       public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
+               this.in = in;
+               this.out = out;
+               this.executor = executor;
+       }
+
+       @Override
+       public Future<Integer> read(ByteBuffer dst) {
+               return executor.submit(() -> in.read(dst));
+       }
+
+       @Override
+       public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
+               try {
+                       Future<Integer> res = read(dst);
+                       handler.completed(res.get(), attachment);
+               } catch (Exception e) {
+                       handler.failed(e, attachment);
+               }
+       }
+
+       @Override
+       public Future<Integer> write(ByteBuffer src) {
+               return executor.submit(() -> out.write(src));
+       }
+
+       @Override
+       public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
+               try {
+                       Future<Integer> res = write(src);
+                       handler.completed(res.get(), attachment);
+               } catch (Exception e) {
+                       handler.failed(e, attachment);
+               }
+       }
+
+       @Override
+       public synchronized void close() throws IOException {
+               try {
+                       in.close();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               try {
+                       out.close();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               open = false;
+               notifyAll();
+       }
+
+       @Override
+       public synchronized boolean isOpen() {
+               return open;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java b/org.argeo.cms/src/org/argeo/cms/util/StreamUtils.java
new file mode 100644 (file)
index 0000000..a589e73
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.StringJoiner;
+
+/** Stream utilities to be used when Apache Commons IO is not available. */
+public class StreamUtils {
+       private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+       /*
+        * APACHE COMMONS IO (inspired)
+        */
+
+       /** @return the number of bytes */
+       public static Long copy(InputStream in, OutputStream out) throws IOException {
+               Long count = 0l;
+               byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+               while (true) {
+                       int length = in.read(buf);
+                       if (length < 0)
+                               break;
+                       out.write(buf, 0, length);
+                       count = count + length;
+               }
+               return count;
+       }
+
+       /** @return the number of chars */
+       public static Long copy(Reader in, Writer out) throws IOException {
+               Long count = 0l;
+               char[] buf = new char[DEFAULT_BUFFER_SIZE];
+               while (true) {
+                       int length = in.read(buf);
+                       if (length < 0)
+                               break;
+                       out.write(buf, 0, length);
+                       count = count + length;
+               }
+               return count;
+       }
+
+       public static byte[] toByteArray(InputStream in) throws IOException {
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                       copy(in, out);
+                       return out.toByteArray();
+               }
+       }
+
+       public static void closeQuietly(InputStream in) {
+               if (in != null)
+                       try {
+                               in.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(OutputStream out) {
+               if (out != null)
+                       try {
+                               out.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(Reader in) {
+               if (in != null)
+                       try {
+                               in.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(Writer out) {
+               if (out != null)
+                       try {
+                               out.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static String toString(BufferedReader reader) throws IOException {
+               StringJoiner sn = new StringJoiner("\n");
+               String line = null;
+               while ((line = reader.readLine()) != null)
+                       sn.add(line);
+               return sn.toString();
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/Tester.java b/org.argeo.cms/src/org/argeo/cms/util/Tester.java
new file mode 100644 (file)
index 0000000..fa62cd7
--- /dev/null
@@ -0,0 +1,126 @@
+package org.argeo.cms.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/** A generic tester based on Java assertions and functional programming. */
+public class Tester {
+       private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private ClassLoader classLoader;
+
+       /** Use {@link Thread#getContextClassLoader()} by default. */
+       public Tester() {
+               this(Thread.currentThread().getContextClassLoader());
+       }
+
+       public Tester(ClassLoader classLoader) {
+               this.classLoader = classLoader;
+       }
+
+       public void execute(String className) {
+               Class<?> clss;
+               try {
+                       clss = classLoader.loadClass(className);
+                       boolean assertionsEnabled = clss.desiredAssertionStatus();
+                       if (!assertionsEnabled)
+                               throw new IllegalStateException("Test runner " + getClass().getName()
+                                               + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
+               } catch (Exception e1) {
+                       throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
+
+               }
+               List<Method> methods = findMethods(clss);
+               if (methods.size() == 0)
+                       throw new IllegalArgumentException("No test method found in " + clss);
+               // TODO make order more predictable?
+               for (Method method : methods) {
+                       String uid = method.getDeclaringClass().getName() + "#" + method.getName();
+                       TesterStatus testStatus = new TesterStatus(uid);
+                       Object obj = null;
+                       try {
+                               beforeTest(uid, method);
+                               obj = clss.getDeclaredConstructor().newInstance();
+                               method.invoke(obj);
+                               testStatus.setPassed();
+                               afterTestPassed(uid, method, obj);
+                       } catch (Exception e) {
+                               testStatus.setFailed(e);
+                               afterTestFailed(uid, method, obj, e);
+                       } finally {
+                               results.put(uid, testStatus);
+                       }
+               }
+       }
+
+       protected void beforeTest(String uid, Method method) {
+               // System.out.println(uid + ": STARTING");
+       }
+
+       protected void afterTestPassed(String uid, Method method, Object obj) {
+               System.out.println(uid + ": PASSED");
+       }
+
+       protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
+               System.out.println(uid + ": FAILED");
+               e.printStackTrace();
+       }
+
+       protected List<Method> findMethods(Class<?> clss) {
+               List<Method> methods = new ArrayList<Method>();
+//             Method call = getMethod(clss, "call");
+//             if (call != null)
+//                     methods.add(call);
+//
+               for (Method method : clss.getMethods()) {
+                       if (method.getName().startsWith("test")) {
+                               methods.add(method);
+                       }
+               }
+               return methods;
+       }
+
+       protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
+               try {
+                       return clss.getMethod(name, parameterTypes);
+               } catch (NoSuchMethodException e) {
+                       return null;
+               } catch (SecurityException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public static void main(String[] args) {
+               // deal with arguments
+               String className;
+               if (args.length < 1) {
+                       System.err.println(usage());
+                       System.exit(1);
+                       throw new IllegalArgumentException();
+               } else {
+                       className = args[0];
+               }
+
+               Tester test = new Tester();
+               try {
+                       test.execute(className);
+               } catch (Throwable e) {
+                       e.printStackTrace();
+               }
+
+               Map<String, TesterStatus> r = test.results;
+               for (String uid : r.keySet()) {
+                       TesterStatus testStatus = r.get(uid);
+                       System.out.println(testStatus);
+               }
+       }
+
+       public static String usage() {
+               return "java " + Tester.class.getName() + " [test class name]";
+
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java b/org.argeo.cms/src/org/argeo/cms/util/TesterStatus.java
new file mode 100644 (file)
index 0000000..09ab432
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.util;
+
+import java.io.Serializable;
+
+/** The status of a test. */
+public class TesterStatus implements Serializable {
+       private static final long serialVersionUID = 6272975746885487000L;
+
+       private Boolean passed = null;
+       private final String uid;
+       private Throwable throwable = null;
+
+       public TesterStatus(String uid) {
+               this.uid = uid;
+       }
+
+       /** For cloning. */
+       public TesterStatus(String uid, Boolean passed, Throwable throwable) {
+               this(uid);
+               this.passed = passed;
+               this.throwable = throwable;
+       }
+
+       public synchronized Boolean isRunning() {
+               return passed == null;
+       }
+
+       public synchronized Boolean isPassed() {
+               assert passed != null;
+               return passed;
+       }
+
+       public synchronized Boolean isFailed() {
+               assert passed != null;
+               return !passed;
+       }
+
+       public synchronized void setPassed() {
+               setStatus(true);
+       }
+
+       public synchronized void setFailed() {
+               setStatus(false);
+       }
+
+       public synchronized void setFailed(Throwable throwable) {
+               setStatus(false);
+               setThrowable(throwable);
+       }
+
+       protected void setStatus(Boolean passed) {
+               if (this.passed != null)
+                       throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
+               this.passed = passed;
+       }
+
+       protected void setThrowable(Throwable throwable) {
+               if (this.throwable != null)
+                       throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
+               this.throwable = throwable;
+       }
+
+       public String getUid() {
+               return uid;
+       }
+
+       public Throwable getThrowable() {
+               return throwable;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               // TODO Auto-generated method stub
+               return super.clone();
+       }
+
+       @Override
+       public boolean equals(Object o) {
+               if (o instanceof TesterStatus) {
+                       TesterStatus other = (TesterStatus) o;
+                       // we don't check consistency for performance purposes
+                       // this equals() is supposed to be used in collections or for transfer
+                       return other.uid.equals(uid);
+               }
+               return false;
+       }
+
+       @Override
+       public int hashCode() {
+               return uid.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return uid + "\t" + (passed ? "passed" : "failed");
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/Throughput.java b/org.argeo.cms/src/org/argeo/cms/util/Throughput.java
new file mode 100644 (file)
index 0000000..4fc15f9
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.util;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+/** A throughput, that is, a value per unit of time. */
+public class Throughput {
+       private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
+
+       public enum Unit {
+               s, m, h, d
+       }
+
+       private final Double value;
+       private final Unit unit;
+
+       public Throughput(Double value, Unit unit) {
+               this.value = value;
+               this.unit = unit;
+       }
+
+       public Throughput(Long periodMs, Long count, Unit unit) {
+               if (unit.equals(Unit.s))
+                       value = ((double) count * 1000d) / periodMs;
+               else if (unit.equals(Unit.m))
+                       value = ((double) count * 60d * 1000d) / periodMs;
+               else if (unit.equals(Unit.h))
+                       value = ((double) count * 60d * 60d * 1000d) / periodMs;
+               else if (unit.equals(Unit.d))
+                       value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
+               else
+                       throw new IllegalArgumentException("Unsupported unit " + unit);
+               this.unit = unit;
+       }
+
+       public Throughput(Double value, String unitStr) {
+               this(value, Unit.valueOf(unitStr));
+       }
+
+       public Throughput(String def) {
+               int index = def.indexOf('/');
+               if (def.length() < 3 || index <= 0 || index != def.length() - 2)
+                       throw new IllegalArgumentException(
+                                       def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
+               String valueStr = def.substring(0, index);
+               String unitStr = def.substring(index + 1);
+               try {
+                       this.value = usNumberFormat.parse(valueStr).doubleValue();
+               } catch (ParseException e) {
+                       throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
+               }
+               this.unit = Unit.valueOf(unitStr);
+       }
+
+       public Long asMsPeriod() {
+               if (unit.equals(Unit.s))
+                       return Math.round(1000d / value);
+               else if (unit.equals(Unit.m))
+                       return Math.round((60d * 1000d) / value);
+               else if (unit.equals(Unit.h))
+                       return Math.round((60d * 60d * 1000d) / value);
+               else if (unit.equals(Unit.d))
+                       return Math.round((24d * 60d * 60d * 1000d) / value);
+               else
+                       throw new IllegalArgumentException("Unsupported unit " + unit);
+       }
+
+       @Override
+       public String toString() {
+               return usNumberFormat.format(value) + '/' + unit;
+       }
+
+       public Double getValue() {
+               return value;
+       }
+
+       public Unit getUnit() {
+               return unit;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/util/package-info.java b/org.argeo.cms/src/org/argeo/cms/util/package-info.java
new file mode 100644 (file)
index 0000000..5efc68a
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic Java utilities. */
+package org.argeo.cms.util;
\ No newline at end of file
index 71eb16789f03cca076c9f305536a1df162cc3a50..4199cd3a3eae6bdf431f85b94a75445cdf7475c9 100644 (file)
@@ -2,7 +2,7 @@
 <classpath>
        <classpathentry kind="src" path="src"/>
        <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
                <attributes>
                        <attribute name="module" value="true"/>
                </attributes>
index 9dcea49d2d6199cad72b9696191de85ecbd69f53..cab85d02ff3e8eefa19317916ebf0de3f286af30 100644 (file)
@@ -1,31 +1,34 @@
 package org.argeo.init;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.lang.System.Logger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
 
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.argeo.init.osgi.OsgiBoot;
 import org.argeo.init.osgi.OsgiRuntimeContext;
 
 /** Configure and launch an Argeo service. */
-public class Service implements Runnable, AutoCloseable {
-       private final static Logger log = System.getLogger(Service.class.getName());
+public class Service {
+       private final static Logger logger = System.getLogger(Service.class.getName());
+
+       public final static String PROP_ARGEO_INIT_MAIN = "argeo.init.main";
 
        private static RuntimeContext runtimeContext = null;
 
        protected Service(String[] args) {
        }
 
-       @Override
-       public void run() {
-       }
-
-       @Override
-       public void close() throws Exception {
-       }
-
        public static void main(String[] args) {
-               long pid = ProcessHandle.current().pid();
-               log.log(Logger.Level.DEBUG, "Argeo Init starting with PID " + pid);
+               final long pid = ProcessHandle.current().pid();
+               logger.log(Logger.Level.DEBUG, () -> "Argeo Init starting with PID " + pid);
 
                // shutdown on exit
                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@@ -37,19 +40,56 @@ public class Service implements Runnable, AutoCloseable {
                                }
                        } catch (Exception e) {
                                e.printStackTrace();
-                               System.exit(1);
+                               Runtime.getRuntime().halt(1);
                        }
                }, "Runtime shutdown"));
 
+               // TODO use args as well
+               String dataArea = System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
+               String stateArea = System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA);
+               String configArea = System.getProperty(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA);
+
+               if (configArea != null) {
+                       Path configAreaPath = Paths.get(configArea);
+                       Path additionalSystemPropertiesPath = configAreaPath.resolve("system.properties");
+                       if (Files.exists(additionalSystemPropertiesPath)) {
+                               Properties properties = new Properties();
+                               try (InputStream in = Files.newInputStream(additionalSystemPropertiesPath)) {
+                                       properties.load(in);
+                               } catch (IOException e) {
+                                       logger.log(Logger.Level.ERROR,
+                                                       "Cannot load additional system properties " + additionalSystemPropertiesPath, e);
+                               }
+
+                               for (Object key : properties.keySet()) {
+                                       String currentValue = System.getProperty(key.toString());
+                                       String value = properties.getProperty(key.toString());
+                                       if (currentValue != null) {
+                                               if (!Objects.equals(value, currentValue))
+                                                       logger.log(Logger.Level.WARNING, "System property " + key + " already set with value "
+                                                                       + currentValue + " instead of " + value + ". Ignoring new value.");
+                                       } else {
+                                               System.setProperty(key.toString(), value);
+                                       }
+                               }
+                               ThinLoggerFinder.reloadConfiguration();
+                       }
+               }
+
                Map<String, String> config = new HashMap<>();
-               config.put("osgi.framework.useSystemProperties", "true");
-//             for (Object key : System.getProperties().keySet()) {
-//                     config.put(key.toString(), System.getProperty(key.toString()));
-//                     log.log(Logger.Level.DEBUG, key + "=" + System.getProperty(key.toString()));
-//             }
+               config.put(PROP_ARGEO_INIT_MAIN, "true");
+
                try {
                        try {
-                               OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext((Map<String, String>) config);
+                               if (stateArea != null)
+                                       config.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, stateArea);
+                               if (configArea != null)
+                                       config.put(OsgiBoot.PROP_OSGI_SHARED_CONFIGURATION_AREA, configArea);
+                               if (dataArea != null)
+                                       config.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, dataArea);
+                               // config.put(OsgiBoot.PROP_OSGI_USE_SYSTEM_PROPERTIES, "true");
+
+                               OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext(config);
                                osgiRuntimeContext.run();
                                Service.runtimeContext = osgiRuntimeContext;
                                Service.runtimeContext.waitForStop(0);
@@ -63,10 +103,9 @@ public class Service implements Runnable, AutoCloseable {
                        e.printStackTrace();
                        System.exit(1);
                }
-               log.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
+               logger.log(Logger.Level.DEBUG, "Argeo Init stopped with PID " + pid);
        }
 
-       
        public static RuntimeContext getRuntimeContext() {
                return runtimeContext;
        }
index 9713d01d3ef6f0014d538789efed63df435b9494..cd8d8954142f4dfc1a1942b652e2a6932e165b9d 100644 (file)
@@ -24,6 +24,10 @@ public class A2Branch implements Comparable<A2Branch> {
                component.branches.put(id, this);
        }
 
+       public Iterable<A2Module> listModules(Object filter) {
+               return modules.values();
+       }
+
        A2Module getOrAddModule(Version version, Object locator) {
                if (modules.containsKey(version)) {
                        A2Module res = modules.get(version);
@@ -35,19 +39,19 @@ public class A2Branch implements Comparable<A2Branch> {
                        return new A2Module(this, version, locator);
        }
 
-       A2Module last() {
+       public A2Module last() {
                return modules.get(modules.lastKey());
        }
 
-       A2Module first() {
+       public A2Module first() {
                return modules.get(modules.firstKey());
        }
 
-       A2Component getComponent() {
+       public A2Component getComponent() {
                return component;
        }
 
-       String getId() {
+       public String getId() {
                return id;
        }
 
index 2b6814f6b839bb9dab2c0421069f0e98fe4bd9e0..cc2f564716858b5dee3389404bf7fcf8a431dd1e 100644 (file)
@@ -23,6 +23,10 @@ public class A2Component implements Comparable<A2Component> {
                contribution.components.put(id, this);
        }
 
+       public Iterable<A2Branch> listBranches(Object filter) {
+               return branches.values();
+       }
+
        A2Branch getOrAddBranch(String branchId) {
                if (branches.containsKey(branchId))
                        return branches.get(branchId);
@@ -36,15 +40,15 @@ public class A2Component implements Comparable<A2Component> {
                return module;
        }
 
-       A2Branch last() {
+       public A2Branch last() {
                return branches.get(branches.lastKey());
        }
 
-       A2Contribution getContribution() {
+       public A2Contribution getContribution() {
                return contribution;
        }
 
-       String getId() {
+       public String getId() {
                return id;
        }
 
index 3d33b55e281deb1b48dbc0db97b6e5cc65c3b9b2..a4b720056fda21036a2946f449add9b7e7622985 100644 (file)
@@ -13,6 +13,8 @@ public class A2Contribution implements Comparable<A2Contribution> {
        final static String RUNTIME = "runtime";
        final static String CLASSPATH = "classpath";
 
+       final static String DEFAULT = "default";
+
        private final ProvisioningSource source;
        private final String id;
 
@@ -30,6 +32,10 @@ public class A2Contribution implements Comparable<A2Contribution> {
 //                     context.contributions.put(id, this);
        }
 
+       public Iterable<A2Component> listComponents(Object filter) {
+               return components.values();
+       }
+
        A2Component getOrAddComponent(String componentId) {
                if (components.containsKey(componentId))
                        return components.get(componentId);
index b862c5435a2909dbf127e26528e1a5dcc9bb4ab8..0b6d3a91c5470033bfe8112b073188143964e0bd 100644 (file)
@@ -7,7 +7,7 @@ import org.osgi.framework.Version;
  * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
  * equivalent of the full coordinates of a Maven artifact version.
  */
-class A2Module implements Comparable<A2Module> {
+public class A2Module implements Comparable<A2Module> {
        private final A2Branch branch;
        private final Version version;
        private final Object locator;
@@ -19,11 +19,11 @@ class A2Module implements Comparable<A2Module> {
                branch.modules.put(version, this);
        }
 
-       A2Branch getBranch() {
+       public A2Branch getBranch() {
                return branch;
        }
 
-       Version getVersion() {
+       public Version getVersion() {
                return version;
        }
 
index 388a85012061d8e685166e2f7973fa7f9cd7bce9..5c8329c85e8616329a2154047273ac4b4ed943dd 100644 (file)
@@ -1,7 +1,18 @@
 package org.argeo.init.a2;
 
+import java.net.URI;
+
 /** A provisioning source in A2 format. */
 public interface A2Source extends ProvisioningSource {
+       /** Use standard a2 protocol, installing from source URL. */
        final static String SCHEME_A2 = "a2";
+       /**
+        * Use equinox-specific reference: installation, which does not copy the bundle
+        * content.
+        */
+       final static String SCHEME_A2_REFERENCE = "a2+reference";
        final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
+       final static String DEFAULT_A2_REFERENCE_URI = SCHEME_A2_REFERENCE + ":///";
+
+       URI getUri();
 }
index f43a6162a26b3a303b66792d9d980a32551ef1da..617e7887806f451a9eb89cd874610ccf8bab0344 100644 (file)
@@ -28,6 +28,12 @@ import org.osgi.framework.Version;
 public abstract class AbstractProvisioningSource implements ProvisioningSource {
        protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
 
+       private final boolean usingReference;
+
+       public AbstractProvisioningSource(boolean usingReference) {
+               this.usingReference = usingReference;
+       }
+
        public Iterable<A2Contribution> listContributions(Object filter) {
                return contributions.values();
        }
@@ -35,16 +41,25 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource {
        @Override
        public Bundle install(BundleContext bc, A2Module module) {
                try {
-                       Path tempJar = null;
-                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
-                               tempJar = toTempJar((Path) module.getLocator());
-                       Bundle bundle;
-                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
-                               bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+                       Object locator = module.getLocator();
+                       if (usingReference && locator instanceof Path locatorPath) {
+                               String referenceUrl = "reference:file:" + locatorPath.toString();
+                               Bundle bundle = bc.installBundle(referenceUrl);
+                               return bundle;
+                       } else {
+
+                               Path tempJar = null;
+                               if (locator instanceof Path && Files.isDirectory((Path) locator))
+                                       tempJar = toTempJar((Path) locator);
+                               Bundle bundle;
+                               try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
+                                       bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+                               }
+
+                               if (tempJar != null)
+                                       Files.deleteIfExists(tempJar);
+                               return bundle;
                        }
-                       if (tempJar != null)
-                               Files.deleteIfExists(tempJar);
-                       return bundle;
                } catch (BundleException | IOException e) {
                        throw new A2Exception("Cannot install module " + module, e);
                }
@@ -53,14 +68,21 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource {
        @Override
        public void update(Bundle bundle, A2Module module) {
                try {
-                       Path tempJar = null;
-                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
-                               tempJar = toTempJar((Path) module.getLocator());
-                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
-                               bundle.update(in);
+                       Object locator = module.getLocator();
+                       if (usingReference && locator instanceof Path) {
+                               try (InputStream in = newInputStream(locator)) {
+                                       bundle.update(in);
+                               }
+                       } else {
+                               Path tempJar = null;
+                               if (locator instanceof Path && Files.isDirectory((Path) locator))
+                                       tempJar = toTempJar((Path) locator);
+                               try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
+                                       bundle.update(in);
+                               }
+                               if (tempJar != null)
+                                       Files.deleteIfExists(tempJar);
                        }
-                       if (tempJar != null)
-                               Files.deleteIfExists(tempJar);
                } catch (BundleException | IOException e) {
                        throw new A2Exception("Cannot update module " + module, e);
                }
@@ -122,6 +144,25 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource {
 
        }
 
+       protected String[] readNameVersionFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+               int semiColIndex = symbolicName.indexOf(';');
+               if (semiColIndex >= 0)
+                       symbolicName = symbolicName.substring(0, semiColIndex);
+               return new String[] { symbolicName, versionStr };
+       }
+
        protected String readVersionFromModule(Path modulePath) {
                Manifest manifest;
                if (Files.isDirectory(modulePath)) {
@@ -155,6 +196,20 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource {
                return symbolicName;
        }
 
+       protected boolean isUsingReference() {
+               return usingReference;
+       }
+
+       private InputStream newInputStream(Object locator) throws IOException {
+               if (locator instanceof Path) {
+                       return Files.newInputStream((Path) locator);
+               } else if (locator instanceof URL) {
+                       return ((URL) locator).openStream();
+               } else {
+                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
+               }
+       }
+
        private static Manifest findManifest(Path currentPath) {
                Path metaInfPath = currentPath.resolve("META-INF");
                if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
@@ -200,13 +255,4 @@ public abstract class AbstractProvisioningSource implements ProvisioningSource {
 
        }
 
-       private InputStream newInputStream(Object locator) throws IOException {
-               if (locator instanceof Path) {
-                       return Files.newInputStream((Path) locator);
-               } else if (locator instanceof URL) {
-                       return ((URL) locator).openStream();
-               } else {
-                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
-               }
-       }
 }
index 8a9e5e67f1f323bb2755a16e5f11a0726c64fc1b..12de4228ba9a57e55eb3c43a180bb1810db349e4 100644 (file)
@@ -11,10 +11,15 @@ import org.argeo.init.osgi.OsgiBootUtils;
 import org.osgi.framework.Version;
 
 /**
- * A provisioning source based on the linear classpath with which the JCM has
+ * A provisioning source based on the linear classpath with which the JVM has
  * been started.
  */
 public class ClasspathSource extends AbstractProvisioningSource {
+       
+       public ClasspathSource() {
+               super(true);
+       }
+
        void load() throws IOException {
                A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH);
                List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
index 067537d5911d24bddd7fe8fa2bec73688bcc85de..e0e2e437f58307c50b60e53500634b3111da2f21 100644 (file)
@@ -1,12 +1,16 @@
 package org.argeo.init.a2;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.SortedSet;
-import java.util.TreeSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.StringJoiner;
+import java.util.TreeMap;
 
 import org.argeo.init.osgi.OsgiBootUtils;
 import org.osgi.framework.Version;
@@ -14,27 +18,73 @@ import org.osgi.framework.Version;
 /** A file system {@link AbstractProvisioningSource} in A2 format. */
 public class FsA2Source extends AbstractProvisioningSource implements A2Source {
        private final Path base;
+       private final Map<String, String> variantsXOr;
 
-       public FsA2Source(Path base) {
-               super();
+//     public FsA2Source(Path base) {
+//             this(base, new HashMap<>());
+//     }
+
+       public FsA2Source(Path base, Map<String, String> variantsXOr, boolean usingReference) {
+               super(usingReference);
                this.base = base;
+               this.variantsXOr = new HashMap<>(variantsXOr);
        }
 
        void load() throws IOException {
+               SortedMap<Path, A2Contribution> contributions = new TreeMap<>();
+
                DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
-               SortedSet<A2Contribution> contributions = new TreeSet<>();
                contributions: for (Path contributionPath : contributionPaths) {
                        if (Files.isDirectory(contributionPath)) {
                                String contributionId = contributionPath.getFileName().toString();
                                if (A2Contribution.BOOT.equals(contributionId))// skip boot
                                        continue contributions;
-                               A2Contribution contribution = getOrAddContribution(contributionId);
-                               contributions.add(contribution);
+                               if (contributionId.contains(".")) {
+                                       A2Contribution contribution = getOrAddContribution(contributionId);
+                                       contributions.put(contributionPath, contribution);
+                               } else {// variants
+                                       Path variantPath = null;
+                                       // is it an explicit variant?
+                                       String variant = variantsXOr.get(contributionPath.getFileName().toString());
+                                       if (variant != null) {
+                                               variantPath = contributionPath.resolve(variant);
+                                       }
+
+                                       // is there a default variant?
+                                       if (variantPath == null) {
+                                               Path defaultPath = contributionPath.resolve(A2Contribution.DEFAULT);
+                                               if (Files.exists(defaultPath)) {
+                                                       variantPath = defaultPath;
+                                               }
+                                       }
+
+                                       if (variantPath == null)
+                                               continue contributions;
+
+                                       // a variant was found, let's collect its contributions (also common ones in its
+                                       // parent)
+                                       for (Path variantContributionPath : Files.newDirectoryStream(variantPath.getParent())) {
+                                               String variantContributionId = variantContributionPath.getFileName().toString();
+                                               if (variantContributionId.contains(".")) {
+                                                       A2Contribution contribution = getOrAddContribution(variantContributionId);
+                                                       contributions.put(variantContributionPath, contribution);
+                                               }
+                                       }
+                                       for (Path variantContributionPath : Files.newDirectoryStream(variantPath)) {
+                                               String variantContributionId = variantContributionPath.getFileName().toString();
+                                               if (variantContributionId.contains(".")) {
+                                                       A2Contribution contribution = getOrAddContribution(variantContributionId);
+                                                       contributions.put(variantContributionPath, contribution);
+                                               }
+                                       }
+                               }
                        }
                }
 
-               for (A2Contribution contribution : contributions) {
-                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId()));
+               for (Path contributionPath : contributions.keySet()) {
+                       String contributionId = contributionPath.getFileName().toString();
+                       A2Contribution contribution = getOrAddContribution(contributionId);
+                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(contributionPath);
                        modules: for (Path modulePath : modulePaths) {
                                if (!Files.isDirectory(modulePath)) {
                                        // OsgiBootUtils.debug("Registering " + modulePath);
@@ -43,21 +93,11 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source {
                                        String ext = moduleFileName.substring(lastDot + 1);
                                        if (!"jar".equals(ext))
                                                continue modules;
-//                                     String moduleName = moduleFileName.substring(0, lastDot);
-//                                     if (moduleName.endsWith("-SNAPSHOT"))
-//                                             moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length());
-//                                     int lastDash = moduleName.lastIndexOf('-');
-//                                     String versionStr = moduleName.substring(lastDash + 1);
-//                                     String componentName = moduleName.substring(0, lastDash);
-                                       // if(versionStr.endsWith("-SNAPSHOT")) {
-                                       // versionStr = readVersionFromModule(modulePath);
-                                       // }
                                        Version version;
-//                                     try {
-//                                             version = new Version(versionStr);
-//                                     } catch (Exception e) {
-                                       String versionStr = readVersionFromModule(modulePath);
-                                       String componentName = readSymbolicNameFromModule(modulePath);
+                                       // TODO optimise? check attributes?
+                                       String[] nameVersion = readNameVersionFromModule(modulePath);
+                                       String componentName = nameVersion[0];
+                                       String versionStr = nameVersion[1];
                                        if (versionStr != null) {
                                                version = new Version(versionStr);
                                        } else {
@@ -75,15 +115,46 @@ public class FsA2Source extends AbstractProvisioningSource implements A2Source {
 
        }
 
-       public static void main(String[] args) {
+       @Override
+       public URI getUri() {
+               URI baseUri = base.toUri();
                try {
-                       FsA2Source context = new FsA2Source(Paths.get(
-                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi"));
-                       context.load();
-                       context.asTree();
-               } catch (Exception e) {
-                       e.printStackTrace();
+                       if (baseUri.getScheme().equals("file")) {
+                               String queryPart = "";
+                               if (!getVariantsXOr().isEmpty()) {
+                                       StringJoiner sj = new StringJoiner("&");
+                                       for (String key : getVariantsXOr().keySet()) {
+                                               sj.add(key + "=" + getVariantsXOr().get(key));
+                                       }
+                                       queryPart = sj.toString();
+                               }
+                               return new URI(isUsingReference() ? SCHEME_A2_REFERENCE : SCHEME_A2, null, base.toString(), queryPart,
+                                               null);
+                       } else {
+                               throw new UnsupportedOperationException("Unsupported scheme " + baseUri.getScheme());
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot build URI from " + baseUri, e);
                }
        }
 
+       protected Map<String, String> getVariantsXOr() {
+               return variantsXOr;
+       }
+
+//     public static void main(String[] args) {
+//             if (args.length == 0)
+//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
+//             try {
+//                     Map<String, String> xOr = new HashMap<>();
+//                     xOr.put("osgi", "equinox");
+//                     xOr.put("swt", "rap");
+//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+//                     context.load();
+//                     context.asTree();
+//             } catch (Exception e) {
+//                     e.printStackTrace();
+//             }
+//     }
+
 }
index 1657fad57e9835784f9a39890ea9b8558e732821..0313d20f367ed32d2fa44a24f213f3decb76daa9 100644 (file)
@@ -17,7 +17,7 @@ public class FsM2Source extends AbstractProvisioningSource {
        private final Path base;
 
        public FsM2Source(Path base) {
-               super();
+               super(false);
                this.base = base;
        }
 
index 35fbee3568a4b3ea39d0709423ab4d33d31c1499..0064ab9eddbb1c4d3c8c37148eb9efaaa68f61aa 100644 (file)
@@ -11,11 +11,12 @@ class OsgiContext extends AbstractProvisioningSource {
        private final BundleContext bc;
 
        public OsgiContext(BundleContext bc) {
-               super();
+               super(false);
                this.bc = bc;
        }
 
        public OsgiContext() {
+               super(false);
                Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
                if (bundle == null)
                        throw new IllegalArgumentException(
index 6012c34ee6d20afc9fc954cbe5a4005890c912c4..cbb296f4c1b780240ddd610ac3257fd8d8218476 100644 (file)
@@ -1,7 +1,13 @@
 package org.argeo.init.a2;
 
+import static org.argeo.init.a2.A2Source.SCHEME_A2;
+import static org.argeo.init.a2.A2Source.SCHEME_A2_REFERENCE;
+
 import java.io.File;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -10,6 +16,8 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -19,7 +27,6 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
-import org.osgi.framework.launch.Framework;
 import org.osgi.framework.wiring.FrameworkWiring;
 
 /** Loads provisioning sources into an OSGi context. */
@@ -48,14 +55,25 @@ public class ProvisioningManager {
                                        updatedBundles.add(bundle);
                        }
                }
-               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
-               frameworkWiring.refreshBundles(updatedBundles);
+//             FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+//             frameworkWiring.refreshBundles(updatedBundles);
        }
 
        public void registerSource(String uri) {
                try {
                        URI u = new URI(uri);
-                       if (A2Source.SCHEME_A2.equals(u.getScheme())) {
+
+                       // XOR
+                       Map<String, List<String>> properties = queryToMap(u);
+                       Map<String, String> xOr = new HashMap<>();
+                       for (String key : properties.keySet()) {
+                               List<String> lst = properties.get(key);
+                               if (lst.size() != 1)
+                                       throw new IllegalArgumentException("Invalid XOR definitions in " + uri);
+                               xOr.put(key, lst.get(0));
+                       }
+
+                       if (SCHEME_A2.equals(u.getScheme()) || SCHEME_A2_REFERENCE.equals(u.getScheme())) {
                                if (u.getHost() == null || "".equals(u.getHost())) {
                                        String baseStr = u.getPath();
                                        if (File.separatorChar == '\\') {// MS Windows
@@ -63,12 +81,19 @@ public class ProvisioningManager {
                                        }
                                        Path base = Paths.get(baseStr);
                                        if (Files.exists(base)) {
-                                               FsA2Source source = new FsA2Source(base);
+                                               FsA2Source source = new FsA2Source(base, xOr, SCHEME_A2_REFERENCE.equals(u.getScheme()));
                                                source.load();
                                                addSource(source);
                                                OsgiBootUtils.info("Registered " + uri + " as source");
+                                       } else {
+                                               OsgiBootUtils.debug("Source " + base + " does not exist, ignoring.");
                                        }
+                               } else {
+                                       throw new UnsupportedOperationException(
+                                                       "Remote installation is not yet supported, cannot add source " + u);
                                }
+                       } else {
+                               throw new IllegalArgumentException("Unkown scheme: for source " + u);
                        }
                } catch (Exception e) {
                        throw new A2Exception("Cannot add source " + uri, e);
@@ -172,29 +197,74 @@ public class ProvisioningManager {
                return updatedBundles;
        }
 
-       public static void main(String[] args) {
-               Map<String, String> configuration = new HashMap<>();
-               configuration.put("osgi.console", "2323");
-               Framework framework = OsgiBootUtils.launch(configuration);
+       private static Map<String, List<String>> queryToMap(URI uri) {
+               return queryToMap(uri.getQuery());
+       }
+
+       private static Map<String, List<String>> queryToMap(String queryPart) {
                try {
-                       ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
-                       FsA2Source context = new FsA2Source(Paths.get(
-                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi"));
-                       context.load();
-                       if (framework.getBundleContext().getBundles().length == 1) {// initial
-                               pm.install(null);
-                       } else {
-                               pm.update();
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-               } finally {
-                       try {
-                               // framework.stop();
-                       } catch (Exception e) {
-                               e.printStackTrace();
+                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+                       if (queryPart == null)
+                               return query_pairs;
+                       final String[] pairs = queryPart.split("&");
+                       for (String pair : pairs) {
+                               final int idx = pair.indexOf("=");
+                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+                                               : pair;
+                               if (!query_pairs.containsKey(key)) {
+                                       query_pairs.put(key, new LinkedList<String>());
+                               }
+                               final String value = idx > 0 && pair.length() > idx + 1
+                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+                                               : null;
+                               query_pairs.get(key).add(value);
                        }
+                       return query_pairs;
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
                }
        }
 
+//     public static void main(String[] args) {
+//             if (args.length == 0)
+//                     throw new IllegalArgumentException("Usage: <path to A2 base>");
+//             Map<String, String> configuration = new HashMap<>();
+//             configuration.put("osgi.console", "2323");
+//             configuration.put("org.osgi.framework.bootdelegation",
+//                             "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp,sun.nio.cs");
+//             Framework framework = OsgiBootUtils.launch(configuration);
+//             try {
+//                     ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
+//                     Map<String, String> xOr = new HashMap<>();
+//                     xOr.put("osgi", "equinox");
+//                     xOr.put("swt", "rap");
+//                     FsA2Source context = new FsA2Source(Paths.get(args[0]), xOr);
+//                     context.load();
+//                     pm.addSource(context);
+//                     if (framework.getBundleContext().getBundles().length == 1) {// initial
+//                             pm.install(null);
+//                     } else {
+//                             pm.update();
+//                     }
+//
+//                     Thread.sleep(2000);
+//
+//                     Bundle[] bundles = framework.getBundleContext().getBundles();
+//                     Arrays.sort(bundles, (b1, b2) -> b1.getSymbolicName().compareTo(b2.getSymbolicName()));
+//                     for (Bundle b : bundles)
+//                             if (b.getState() == Bundle.RESOLVED || b.getState() == Bundle.STARTING || b.getState() == Bundle.ACTIVE)
+//                                     System.out.println(b.getSymbolicName() + " " + b.getVersion());
+//                             else
+//                                     System.err.println(b.getSymbolicName() + " " + b.getVersion() + " (" + b.getState() + ")");
+//             } catch (Exception e) {
+//                     e.printStackTrace();
+//             } finally {
+//                     try {
+//                             framework.stop();
+//                     } catch (Exception e) {
+//                             e.printStackTrace();
+//                     }
+//             }
+//     }
+
 }
index 1bfab8e92cd9f8d1573ea084309aa6f8bf79a12d..3ff5265e5e41509ef435130a19272df62ef877be 100644 (file)
@@ -91,8 +91,13 @@ class ThinJavaUtilLogging {
                @Override
                public void publish(LogRecord record) {
                        java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName());
-                       systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
-                                       record.getThrown());
+                       if (record.getParameters() != null && record.getParameters().length > 0) {
+                               systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
+                                               record.getParameters());
+                       } else {
+                               systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
+                                               record.getThrown());
+                       }
                }
 
                @Override
index f443b2e669d28aa9599716e126adb24397db6f66..e60d22fba1061238e20da6b0071afdfdcf03ca7a 100644 (file)
@@ -21,17 +21,24 @@ public class ThinLoggerFinder extends LoggerFinder {
        public ThinLoggerFinder() {
                if (logging != null)
                        throw new IllegalStateException("Only one logging can be initialised.");
-               init();
+//             init();
        }
 
        @Override
        public Logger getLogger(String name, Module module) {
+               lazyInit();
                return logging.getLogger(name, module);
        }
 
        private static void init() {
                logging = new ThinLogging();
+               reloadConfiguration();
+       }
 
+       /** Reload configuration form system properties */
+       public static void reloadConfiguration() {
+               if (logging == null)
+                       return;
                Map<String, Object> configuration = new HashMap<>();
                for (Object key : System.getProperties().keySet()) {
                        Objects.requireNonNull(key);
index e21899394dda63abdef24514b9bad69516974472..5b2c93924505112c9d4f4bade1ac7b6fcf88152f 100644 (file)
@@ -16,11 +16,9 @@ import java.util.ResourceBundle;
 import java.util.SortedMap;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 import java.util.concurrent.Flow;
 import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.SubmissionPublisher;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
@@ -39,8 +37,24 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        final static String DEFAULT_LEVEL_PROPERTY = "log";
        final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + ".";
 
-       final static String JOURNALD_PROPERTY = "argeo.logging.journald";
-       final static String CALL_LOCATION_PROPERTY = "argeo.logging.callLocation";
+       /**
+        * Whether logged event are only immediately printed to the standard output.
+        * Required for native images.
+        */
+       final static String PROP_ARGEO_LOGGING_SYNCHRONOUS = "argeo.logging.synchronous";
+       /**
+        * Whether to enable jounrald compatible output, either: auto (default), true,
+        * or false.
+        */
+       final static String PROP_ARGEO_LOGGING_JOURNALD = "argeo.logging.journald";
+       /**
+        * The level from which call location (that is, line number in Java code) will
+        * be searched (default is WARNING)
+        */
+       final static String PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL = "argeo.logging.callLocationLevel";
+
+       final static String ENV_INVOCATION_ID = "INVOCATION_ID";
+       final static String ENV_GIO_LAUNCHED_DESKTOP_FILE_PID = "GIO_LAUNCHED_DESKTOP_FILE_PID";
 
        private final static AtomicLong nextEntry = new AtomicLong(0l);
 
@@ -51,43 +65,37 @@ class ThinLogging implements Consumer<Map<String, Object>> {
        private NavigableMap<String, Level> levels = new TreeMap<>();
        private volatile boolean updatingConfiguration = false;
 
-       private final ExecutorService executor;
        private final LogEntryPublisher publisher;
+       private PrintStreamSubscriber synchronousSubscriber;
 
        private final boolean journald;
        private final Level callLocationLevel;
 
-       ThinLogging() {
-               executor = Executors.newCachedThreadPool((r) -> {
-                       Thread t = new Thread(r);
-                       t.setDaemon(true);
-                       return t;
-               });
-               publisher = new LogEntryPublisher(executor, Flow.defaultBufferSize());
-
-               PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
-               publisher.subscribe(subscriber);
+       private final boolean synchronous;
 
+       ThinLogging() {
+               publisher = new LogEntryPublisher();
+               synchronous = Boolean.parseBoolean(System.getProperty(PROP_ARGEO_LOGGING_SYNCHRONOUS, "false"));
+               if (synchronous) {
+                       synchronousSubscriber = new PrintStreamSubscriber();
+               } else {
+                       PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
+                       publisher.subscribe(subscriber);
+               }
                Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
 
                // initial default level
-               levels.put("", Level.WARNING);
+               levels.put(DEFAULT_LEVEL_NAME, Level.WARNING);
 
                // Logging system config
                // journald
-
-//             Map<String, String> env = new TreeMap<>(System.getenv());
-//             for (String key : env.keySet()) {
-//                     System.out.println(key + "=" + env.get(key));
-//             }
-
-               String journaldStr = System.getProperty(JOURNALD_PROPERTY, "auto");
+               String journaldStr = System.getProperty(PROP_ARGEO_LOGGING_JOURNALD, "auto");
                switch (journaldStr) {
                case "auto":
-                       String systemdInvocationId = System.getenv("INVOCATION_ID");
+                       String systemdInvocationId = System.getenv(ENV_INVOCATION_ID);
                        if (systemdInvocationId != null) {// in systemd
-                               // check whether we are indirectly in a desktop app (e.g. eclipse)
-                               String desktopFilePid = System.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID");
+                               // check whether we are indirectly in a desktop app (typically an IDE)
+                               String desktopFilePid = System.getenv(ENV_GIO_LAUNCHED_DESKTOP_FILE_PID);
                                if (desktopFilePid != null) {
                                        Long javaPid = ProcessHandle.current().pid();
                                        if (!javaPid.toString().equals(desktopFilePid)) {
@@ -110,10 +118,10 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        break;
                default:
                        throw new IllegalArgumentException(
-                                       "Unsupported value '" + journaldStr + "' for property " + JOURNALD_PROPERTY);
+                                       "Unsupported value '" + journaldStr + "' for property " + PROP_ARGEO_LOGGING_JOURNALD);
                }
 
-               String callLocationStr = System.getProperty(CALL_LOCATION_PROPERTY, Level.WARNING.getName());
+               String callLocationStr = System.getProperty(PROP_ARGEO_LOGGING_CALL_LOCATION_LEVEL, Level.WARNING.getName());
                callLocationLevel = Level.valueOf(callLocationStr);
        }
 
@@ -127,41 +135,29 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        }
                }
 
-               publisher.close();
-               try {
-                       // we ait a bit in order to make sure all messages are flushed
-                       // TODO synchronize more efficiently
-                       executor.awaitTermination(300, TimeUnit.MILLISECONDS);
-               } catch (InterruptedException e) {
-                       // silent
+               if (!synchronous) {
+                       publisher.close();
+                       try {
+                               // we wait a bit in order to make sure all messages are flushed
+                               // TODO synchronize more efficiently
+                               // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+                               ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
                }
        }
 
        private Level computeApplicableLevel(String name) {
                Map.Entry<String, Level> entry = levels.floorEntry(name);
                assert entry != null;
-               return entry.getValue();
+               if (name.startsWith(entry.getKey()))
+                       return entry.getValue();
+               else
+                       return levels.get(DEFAULT_LEVEL_NAME);// default
 
        }
 
-//     private boolean isLoggable(String name, Level level) {
-//             Objects.requireNonNull(name);
-//             Objects.requireNonNull(level);
-//
-//             if (updatingConfiguration) {
-//                     synchronized (levels) {
-//                             try {
-//                                     levels.wait();
-//                                     // TODO make exit more robust
-//                             } catch (InterruptedException e) {
-//                                     throw new IllegalStateException(e);
-//                             }
-//                     }
-//             }
-//
-//             return level.getSeverity() >= computeApplicableLevel(name).getSeverity();
-//     }
-
        public Logger getLogger(String name, Module module) {
                if (!loggers.containsKey(name)) {
                        ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
@@ -214,7 +210,6 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        updatingConfiguration = false;
                        levels.notifyAll();
                }
-
        }
 
        Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
@@ -286,8 +281,19 @@ class ThinLogging implements Consumer<Map<String, Object>> {
 
                        // NOTE: this is the method called when logging a plain message without
                        // exception, so it should be considered as a format only when args are not null
-                       if (format.contains("{}"))// workaround for weird Jetty formatting
-                               params = null;
+//                     if (format.contains("{}"))// workaround for weird Jetty formatting
+//                             params = null;
+                       // TODO move this to slf4j wrapper?
+                       if (format.contains("{}")) {
+                               StringBuilder sb = new StringBuilder();
+                               String[] segments = format.split("\\{\\}");
+                               for (int i = 0; i < segments.length; i++) {
+                                       sb.append(segments[i]);
+                                       if (i != (segments.length - 1))
+                                               sb.append("{" + i + "}");
+                               }
+                               format = sb.toString();
+                       }
                        String msg = params == null ? format : MessageFormat.format(format, params);
                        publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread));
                }
@@ -315,6 +321,7 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                        case "org.osgi.service.log.Logger":
                                        case "org.eclipse.osgi.internal.log.LoggerImpl":
                                        case "org.argeo.api.cms.CmsLog":
+                                       case "org.argeo.init.osgi.OsgiBootUtils":
                                        case "org.slf4j.impl.ArgeoLogger":
                                        case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
                                        case "org.eclipse.jetty.util.log.Slf4jLog":
@@ -342,8 +349,8 @@ class ThinLogging implements Consumer<Map<String, Object>> {
 
        private class LogEntryPublisher extends SubmissionPublisher<Map<String, Serializable>> {
 
-               private LogEntryPublisher(Executor executor, int maxBufferCapacity) {
-                       super(executor, maxBufferCapacity);
+               private LogEntryPublisher() {
+                       super();
                }
 
                private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
@@ -372,8 +379,13 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                        logEntry.put(KEY_THREAD, thread.getName());
 
                        // should be unmodifiable for security reasons
-                       if (!isClosed())
-                               submit(Collections.unmodifiableMap(logEntry));
+                       if (synchronous) {
+                               assert synchronousSubscriber != null;
+                               synchronousSubscriber.onNext(logEntry);
+                       } else {
+                               if (!isClosed())
+                                       submit(Collections.unmodifiableMap(logEntry));
+                       }
                }
 
        }
@@ -420,6 +432,8 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                private PrintStream err;
                private int writeToErrLevel = Level.WARNING.getSeverity();
 
+               private Subscription subscription;
+
                protected PrintStreamSubscriber() {
                        this(System.out, System.err);
                }
@@ -435,7 +449,8 @@ class ThinLogging implements Consumer<Map<String, Object>> {
 
                @Override
                public void onSubscribe(Subscription subscription) {
-                       subscription.request(Long.MAX_VALUE);
+                       this.subscription = subscription;
+                       this.subscription.request(1);
                }
 
                @Override
@@ -446,6 +461,8 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                                out.print(toPrint(item));
                        }
                        // TODO flush for journald?
+                       if (this.subscription != null)
+                               this.subscription.request(1);
                }
 
                @Override
@@ -507,10 +524,9 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                protected String toPrint(Map<String, Serializable> logEntry) {
                        StringBuilder sb = new StringBuilder();
                        StringTokenizer st = new StringTokenizer((String) logEntry.get(KEY_MSG), "\r\n");
-                       assert st.hasMoreTokens();
 
                        // first line
-                       String firstLine = st.nextToken();
+                       String firstLine = st.hasMoreTokens() ? st.nextToken() : "";
                        sb.append(firstLinePrefix(logEntry));
                        sb.append(firstLine);
                        sb.append(firstLineSuffix(logEntry));
@@ -559,6 +575,16 @@ class ThinLogging implements Consumer<Map<String, Object>> {
                logger.log(Logger.Level.INFO, "Log info");
                logger.log(Logger.Level.WARNING, "Log warning");
                logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
+
+               try {
+                       // we ait a bit in order to make sure all messages are flushed
+                       // TODO synchronize more efficiently
+                       // executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+                       ForkJoinPool.commonPool().awaitTermination(300, TimeUnit.MILLISECONDS);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+
        }
 
 }
index a4f5a371d9d4c8ab708762b6be70156208510741..b85b248b9e17c94f01be20070cb86e69f3a79dd4 100644 (file)
@@ -4,6 +4,7 @@ import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
 import java.util.Objects;
 
+import org.argeo.init.Service;
 import org.argeo.init.logging.ThinLoggerFinder;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -18,34 +19,45 @@ public class Activator implements BundleActivator {
                // must be called first
                ThinLoggerFinder.lazyInit();
        }
-       private Logger logger = System.getLogger(Activator.class.getName());
+       private final static Logger logger = System.getLogger(Activator.class.getName());
 
        private Long checkpoint = null;
+
+       private boolean argeoInit = false;
+       /** Not null if we created it ourselves. */
        private OsgiRuntimeContext runtimeContext;
 
        public void start(final BundleContext bundleContext) throws Exception {
-               if (runtimeContext == null) {
-                       runtimeContext = new OsgiRuntimeContext(bundleContext);
-               }
-               logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
-
-               // admin thread
+               // The OSGi runtime was created by us, and therefore already initialized
+               argeoInit = Boolean.parseBoolean(bundleContext.getProperty(Service.PROP_ARGEO_INIT_MAIN));
+               if (!argeoInit) {
+                       if (runtimeContext == null) {
+                               runtimeContext = new OsgiRuntimeContext(bundleContext);
+                               logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
+                       }
+
+                       // admin thread
 //             Thread adminThread = new AdminThread(bundleContext);
 //             adminThread.start();
 
-               // bootstrap
+                       // bootstrap
 //             OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-               if (checkpoint == null) {
+                       if (checkpoint == null) {
 //                     osgiBoot.bootstrap();
-                       checkpoint = System.currentTimeMillis();
-               } else {
-                       runtimeContext.update();
-                       checkpoint = System.currentTimeMillis();
+                               checkpoint = System.currentTimeMillis();
+                       } else {
+                               runtimeContext.update();
+                               checkpoint = System.currentTimeMillis();
+                       }
                }
        }
 
        public void stop(BundleContext context) throws Exception {
-               Objects.requireNonNull(runtimeContext);
-               runtimeContext.stop(context);
+               if (!argeoInit) {
+                       Objects.nonNull(runtimeContext);
+                       runtimeContext.stop(context);
+                       runtimeContext = null;
+               }
        }
+
 }
index 73ab9afa7706d303aee96e874021aafb1a4f15d2..863ee00841f335957baa25d8fbac2448eed2d977 100644 (file)
@@ -25,7 +25,7 @@ class BundlesSet {
                                dirPath = dirPath.substring("file:".length());
 
                        dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath();
-                       if (OsgiBootUtils.debug)
+                       if (OsgiBootUtils.isDebug())
                                OsgiBootUtils.debug("Base dir: " + dir);
                } catch (IOException e) {
                        throw new RuntimeException("Cannot convert to absolute path", e);
index 8fbe4b2386f8577b37629a4d3020be185beb9a2f..884461b12b5b0e5bb322ddd96705c619af6a0f1f 100644 (file)
@@ -38,52 +38,45 @@ import org.osgi.framework.wiring.FrameworkWiring;
  */
 public class OsgiBoot implements OsgiBootConstants {
        public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
+       public final static String PROP_ARGEO_OSGI_MAX_START_LEVEL = "argeo.osgi.maxStartLevel";
        public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
 
-       public final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
-       public final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
-       public final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
-       public final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
+       @Deprecated
+       final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
+       final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
+       final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
+       final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
 
        // booleans
-       public final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
-       // public final static String PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN =
-       // "argeo.osgi.boot.excludeSvn";
+       @Deprecated
+       final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
 
-       public final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
-       public final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
-       public final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
+       final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
+       final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
+       final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
 
+       @Deprecated
        public final static String DEFAULT_BASE_URL = "reference:file:";
-       // public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**";
+       final static String DEFAULT_MAX_START_LEVEL = "32";
 
-       // OSGi system properties
+       // OSGi standard properties
        final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
        final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
-       final static String INSTANCE_AREA_PROP = "osgi.instance.area";
-       final static String CONFIGURATION_AREA_PROP = "osgi.configuration.area";
+       public final static String PROP_OSGI_INSTANCE_AREA = "osgi.instance.area";
+       public final static String PROP_OSGI_CONFIGURATION_AREA = "osgi.configuration.area";
+       public final static String PROP_OSGI_SHARED_CONFIGURATION_AREA = "osgi.sharedConfiguration.area";
+       public final static String PROP_OSGI_USE_SYSTEM_PROPERTIES = "osgi.framework.useSystemProperties";
 
        // Symbolic names
-       public final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.osgi.boot";
-       public final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
-
-       /** Exclude svn metadata implicitely(a bit costly) */
-       // private boolean excludeSvn =
-       // Boolean.valueOf(System.getProperty(PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN,
-       // "false"))
-       // .booleanValue();
-
-       /** Default is 10s */
-       @Deprecated
-       private long defaultTimeout = 10000l;
+       final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.init";
+       final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
 
        private final BundleContext bundleContext;
        private final String localCache;
-
        private final ProvisioningManager provisioningManager;
 
        /*
-        * INITIALIZATION
+        * INITIALISATION
         */
        /** Constructor */
        public OsgiBoot(BundleContext bundleContext) {
@@ -97,15 +90,29 @@ public class OsgiBoot implements OsgiBootConstants {
                if (sources == null) {
                        provisioningManager.registerDefaultSource();
                } else {
+//                     OsgiBootUtils.debug("Found sources " + sources);
                        for (String source : sources.split(",")) {
+                               int qmIndex = source.lastIndexOf('?');
+                               String queryPart = "";
+                               if (qmIndex >= 0) {
+                                       queryPart = source.substring(qmIndex);
+                                       source = source.substring(0, qmIndex);
+                               }
                                if (source.trim().equals(A2Source.DEFAULT_A2_URI)) {
                                        if (Files.exists(homePath))
                                                provisioningManager.registerSource(
-                                                               A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2");
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2");
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2");
+                                                               A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2" + queryPart);
+                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2" + queryPart);
+                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2" + queryPart);
+                               } else if (source.trim().equals(A2Source.DEFAULT_A2_REFERENCE_URI)) {
+                                       if (Files.exists(homePath))
+                                               provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + "://" + homePath.toString()
+                                                               + "/.local/share/a2" + queryPart);
+                                       provisioningManager
+                                                       .registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/local/share/a2" + queryPart);
+                                       provisioningManager.registerSource(A2Source.SCHEME_A2_REFERENCE + ":///usr/share/a2" + queryPart);
                                } else {
-                                       provisioningManager.registerSource(source);
+                                       provisioningManager.registerSource(source + queryPart);
                                }
                        }
                }
@@ -118,18 +125,42 @@ public class OsgiBoot implements OsgiBootConstants {
        /*
         * HIGH-LEVEL METHODS
         */
-       /** Bootstraps the OSGi runtime */
-       public void bootstrap() {
+       /**
+        * Bootstraps the OSGi runtime using these properties, which MUST be consistent
+        * with {@link BundleContext#getProperty(String)}. If these properties are
+        * <code>null</code>, system properties are used instead.
+        */
+       public void bootstrap(Map<String, String> properties) {
                try {
                        long begin = System.currentTimeMillis();
-                       System.out.println();
-                       String osgiInstancePath = bundleContext.getProperty(INSTANCE_AREA_PROP);
-                       OsgiBootUtils
-                                       .info("OSGi bootstrap starting" + (osgiInstancePath != null ? " (" + osgiInstancePath + ")" : ""));
+
+                       // notify start
+                       String osgiInstancePath = getProperty(PROP_OSGI_INSTANCE_AREA);
+                       String osgiConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
+                       String osgiSharedConfigurationPath = getProperty(PROP_OSGI_CONFIGURATION_AREA);
+                       OsgiBootUtils.info("OSGi bootstrap starting" //
+                                       + (osgiInstancePath != null ? " data: " + osgiInstancePath + "" : "") //
+                                       + (osgiConfigurationPath != null ? " state: " + osgiConfigurationPath + "" : "") //
+                                       + (osgiSharedConfigurationPath != null ? " config: " + osgiSharedConfigurationPath + "" : "") //
+                       );
+
+                       // legacy install bundles
                        installUrls(getBundlesUrls());
                        installUrls(getDistributionUrls());
+
+                       // A2 install bundles
                        provisioningManager.install(null);
-                       startBundles();
+
+                       // Make sure fragments are properly considered by refreshing
+                       refreshFramework();
+
+                       // start bundles
+//                     if (properties != null && !Boolean.parseBoolean(properties.get(PROP_OSGI_USE_SYSTEM_PROPERTIES)))
+                       startBundles(properties);
+//                     else
+//                             startBundles();
+
+                       // complete
                        long duration = System.currentTimeMillis() - begin;
                        OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
                                        + duration + "ms), " + bundleContext.getBundles().length + " bundles");
@@ -139,7 +170,7 @@ public class OsgiBoot implements OsgiBootConstants {
                }
 
                // diagnostics
-               if (OsgiBootUtils.debug) {
+               if (OsgiBootUtils.isDebug()) {
                        OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
                        diagnostics.checkUnresolved();
                        Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
@@ -159,6 +190,16 @@ public class OsgiBoot implements OsgiBootConstants {
                System.out.println();
        }
 
+       /**
+        * Calls {@link #bootstrap(Map)} with <code>null</code>.
+        * 
+        * @see #bootstrap(Map)
+        */
+       @Deprecated
+       public void bootstrap() {
+               bootstrap(null);
+       }
+
        public void update() {
                provisioningManager.update();
        }
@@ -181,7 +222,7 @@ public class OsgiBoot implements OsgiBootConstants {
                        String url = (String) urls.get(i);
                        installUrl(url, installedBundles);
                }
-               refreshFramework();
+//             refreshFramework();
        }
 
        /** Actually install the provided URL */
@@ -189,11 +230,11 @@ public class OsgiBoot implements OsgiBootConstants {
                try {
                        if (installedBundles.containsKey(url)) {
                                Bundle bundle = (Bundle) installedBundles.get(url);
-                               if (OsgiBootUtils.debug)
+                               if (OsgiBootUtils.isDebug())
                                        debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url);
                        } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/")
                                        || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) {
-                               if (OsgiBootUtils.debug)
+                               if (OsgiBootUtils.isDebug())
                                        warn("Skip " + url);
                                return;
                        } else {
@@ -201,7 +242,7 @@ public class OsgiBoot implements OsgiBootConstants {
                                if (url.startsWith("http"))
                                        OsgiBootUtils
                                                        .info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
-                               else if (OsgiBootUtils.debug)
+                               else if (OsgiBootUtils.isDebug())
                                        OsgiBootUtils.debug(
                                                        "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
                                assert bundle.getSymbolicName() != null;
@@ -250,7 +291,7 @@ public class OsgiBoot implements OsgiBootConstants {
                                } else
                                        OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message);
                        }
-                       if (OsgiBootUtils.debug && !message.contains(ALREADY_INSTALLED))
+                       if (OsgiBootUtils.isDebug() && !message.contains(ALREADY_INSTALLED))
                                e.printStackTrace();
                }
        }
@@ -258,16 +299,73 @@ public class OsgiBoot implements OsgiBootConstants {
        /*
         * START
         */
-       public void startBundles() {
-               startBundles(System.getProperties());
+
+       /**
+        * Start bundles based on these properties.
+        * 
+        * @see OsgiBoot#doStartBundles(Map)
+        */
+       public void startBundles(Map<String, String> properties) {
+               Map<String, String> map = new TreeMap<>();
+               // first use properties
+               if (properties != null) {
+                       for (String key : properties.keySet()) {
+                               String property = key;
+                               if (property.startsWith(PROP_ARGEO_OSGI_START)) {
+                                       map.put(property, properties.get(property));
+                               }
+                       }
+               }
+               // then try all start level until a maximum
+               int maxStartLevel = Integer.parseInt(getProperty(PROP_ARGEO_OSGI_MAX_START_LEVEL, DEFAULT_MAX_START_LEVEL));
+               for (int i = 1; i <= maxStartLevel; i++) {
+                       String key = PROP_ARGEO_OSGI_START + "." + i;
+                       String value = getProperty(key);
+                       if (value != null)
+                               map.put(key, value);
+
+               }
+               // finally, override with system properties
+               for (Object key : System.getProperties().keySet()) {
+                       if (key.toString().startsWith(PROP_ARGEO_OSGI_START)) {
+                               map.put(key.toString(), System.getProperty(key.toString()));
+                       }
+               }
+               // start
+               doStartBundles(map);
        }
 
+       @Deprecated
        public void startBundles(Properties properties) {
+               Map<String, String> map = new TreeMap<>();
+               // first use properties
+               if (properties != null) {
+                       for (Object key : properties.keySet()) {
+                               String property = key.toString();
+                               if (property.startsWith(PROP_ARGEO_OSGI_START)) {
+                                       map.put(property, properties.get(property).toString());
+                               }
+                       }
+               }
+               startBundles(map);
+       }
+
+       /** Start bundle based on keys starting with {@link #PROP_ARGEO_OSGI_START}. */
+       protected void doStartBundles(Map<String, String> properties) {
                FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
 
                // default and active start levels from System properties
-               Integer defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
-               Integer activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
+               int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
+               int defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
+               int activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
+               if (OsgiBootUtils.isDebug()) {
+                       OsgiBootUtils.debug("OSGi default start level: "
+                                       + getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "<not set>") + ", using " + defaultStartLevel);
+                       OsgiBootUtils.debug("OSGi active start level: " + getProperty(PROP_OSGI_STARTLEVEL, "<not set>")
+                                       + ", using " + activeStartLevel);
+                       OsgiBootUtils.debug("Framework start level: " + frameworkStartLevel.getStartLevel() + " (initial: "
+                                       + initialStartLevel + ")");
+               }
 
                SortedMap<Integer, List<String>> startLevels = new TreeMap<Integer, List<String>>();
                computeStartLevels(startLevels, properties, defaultStartLevel);
@@ -289,31 +387,58 @@ public class OsgiBoot implements OsgiBootConstants {
                                        } catch (BundleException e) {
                                                OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
                                        }
-                                       if (getDebug())
+                                       if (OsgiBootUtils.isDebug())
                                                OsgiBootUtils.debug(bsn + " starts at level " + level);
                                }
                        }
                }
+
+               if (OsgiBootUtils.isDebug())
+                       OsgiBootUtils.debug("About to set framework start level to " + activeStartLevel + " ...");
+
                frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
-                       if (getDebug())
-                               OsgiBootUtils.debug("Framework event: " + event);
-                       int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
-                       int startLevel = frameworkStartLevel.getStartLevel();
-                       OsgiBootUtils.debug("Framework start level: " + startLevel + " (initial: " + initialStartLevel + ")");
+                       if (event.getType() == FrameworkEvent.ERROR) {
+                               OsgiBootUtils.error("Start sequence failed", event.getThrowable());
+                       } else {
+                               if (OsgiBootUtils.isDebug())
+                                       OsgiBootUtils.debug("Framework started at level " + frameworkStartLevel.getStartLevel());
+                       }
                });
+
+//             // Start the framework level after level
+//             int currentStartLevel = frameworkStartLevel.getStartLevel();
+//             stages: for (int stage = currentStartLevel + 1; stage <= activeStartLevel; stage++) {
+//                     if (OsgiBootUtils.isDebug())
+//                             OsgiBootUtils.debug("Starting stage " + stage + "...");
+//                     final int nextStage = stage;
+//                     final CompletableFuture<FrameworkEvent> stageCompleted = new CompletableFuture<>();
+//                     frameworkStartLevel.setStartLevel(nextStage, (FrameworkEvent event) -> {
+//                             stageCompleted.complete(event);
+//                     });
+//                     FrameworkEvent event;
+//                     try {
+//                             event = stageCompleted.get();
+//                     } catch (InterruptedException | ExecutionException e) {
+//                             throw new IllegalStateException("Cannot continue start", e);
+//                     }
+//                     if (event.getThrowable() != null) {
+//                             OsgiBootUtils.error("Stage " + nextStage + " failed, aborting start.", event.getThrowable());
+//                             break stages;
+//                     }
+//             }
        }
 
-       private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Properties properties,
+       private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Map<String, String> properties,
                        Integer defaultStartLevel) {
 
                // default (and previously, only behaviour)
-               appendToStartLevels(startLevels, defaultStartLevel, properties.getProperty(PROP_ARGEO_OSGI_START, ""));
+               appendToStartLevels(startLevels, defaultStartLevel, properties.getOrDefault(PROP_ARGEO_OSGI_START, ""));
 
                // list argeo.osgi.start.* system properties
-               Iterator<Object> keys = properties.keySet().iterator();
+               Iterator<String> keys = properties.keySet().iterator();
                final String prefix = PROP_ARGEO_OSGI_START + ".";
                while (keys.hasNext()) {
-                       String key = keys.next().toString();
+                       String key = keys.next();
                        if (key.startsWith(prefix)) {
                                Integer startLevel;
                                String suffix = key.substring(prefix.length());
@@ -329,7 +454,7 @@ public class OsgiBoot implements OsgiBootConstants {
                                        startLevel = defaultStartLevel;
 
                                // append bundle names
-                               String bundleNames = properties.getProperty(key);
+                               String bundleNames = properties.get(key);
                                appendToStartLevels(startLevels, startLevel, bundleNames);
                        }
                }
@@ -350,109 +475,6 @@ public class OsgiBoot implements OsgiBootConstants {
                }
        }
 
-       /**
-        * Start the provided list of bundles
-        *
-        * @return whether all bundles are now in active state
-        * @deprecated
-        */
-       @Deprecated
-       public boolean startBundles(List<String> bundlesToStart) {
-               if (bundlesToStart.size() == 0)
-                       return true;
-
-               // used to monitor ACTIVE states
-               List<Bundle> startedBundles = new ArrayList<Bundle>();
-               // used to log the bundles not found
-               List<String> notFoundBundles = new ArrayList<String>(bundlesToStart);
-
-               Bundle[] bundles = bundleContext.getBundles();
-               long startBegin = System.currentTimeMillis();
-               for (int i = 0; i < bundles.length; i++) {
-                       Bundle bundle = bundles[i];
-                       String symbolicName = bundle.getSymbolicName();
-                       if (bundlesToStart.contains(symbolicName))
-                               try {
-                                       try {
-                                               bundle.start();
-                                               if (OsgiBootUtils.debug)
-                                                       debug("Bundle " + symbolicName + " started");
-                                       } catch (Exception e) {
-                                               OsgiBootUtils.warn("Start of bundle " + symbolicName + " failed because of " + e
-                                                               + ", maybe bundle is not yet resolved," + " waiting and trying again.");
-                                               waitForBundleResolvedOrActive(startBegin, bundle);
-                                               bundle.start();
-                                               startedBundles.add(bundle);
-                                       }
-                                       notFoundBundles.remove(symbolicName);
-                               } catch (Exception e) {
-                                       OsgiBootUtils.warn("Bundle " + symbolicName + " cannot be started: " + e.getMessage());
-                                       if (OsgiBootUtils.debug)
-                                               e.printStackTrace();
-                                       // was found even if start failed
-                                       notFoundBundles.remove(symbolicName);
-                               }
-               }
-
-               for (int i = 0; i < notFoundBundles.size(); i++)
-                       OsgiBootUtils.warn("Bundle '" + notFoundBundles.get(i) + "' not started because it was not found.");
-
-               // monitors that all bundles are started
-               long beginMonitor = System.currentTimeMillis();
-               boolean allStarted = !(startedBundles.size() > 0);
-               List<String> notStarted = new ArrayList<String>();
-               while (!allStarted && (System.currentTimeMillis() - beginMonitor) < defaultTimeout) {
-                       notStarted = new ArrayList<String>();
-                       allStarted = true;
-                       for (int i = 0; i < startedBundles.size(); i++) {
-                               Bundle bundle = (Bundle) startedBundles.get(i);
-                               // TODO check behaviour of lazs bundles
-                               if (bundle.getState() != Bundle.ACTIVE) {
-                                       allStarted = false;
-                                       notStarted.add(bundle.getSymbolicName());
-                               }
-                       }
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-               }
-               long duration = System.currentTimeMillis() - beginMonitor;
-
-               if (!allStarted)
-                       for (int i = 0; i < notStarted.size(); i++)
-                               OsgiBootUtils.warn("Bundle '" + notStarted.get(i) + "' not ACTIVE after " + (duration / 1000) + "s");
-
-               return allStarted;
-       }
-
-       /** Waits for a bundle to become active or resolved */
-       @Deprecated
-       private void waitForBundleResolvedOrActive(long startBegin, Bundle bundle) throws Exception {
-               int originalState = bundle.getState();
-               if ((originalState == Bundle.RESOLVED) || (originalState == Bundle.ACTIVE))
-                       return;
-
-               String originalStateStr = OsgiBootUtils.stateAsString(originalState);
-
-               int currentState = bundle.getState();
-               while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) {
-                       long now = System.currentTimeMillis();
-                       if ((now - startBegin) > defaultTimeout * 10)
-                               throw new Exception("Bundle " + bundle.getSymbolicName() + " was not RESOLVED or ACTIVE after "
-                                               + (now - startBegin) + "ms (originalState=" + originalStateStr + ", currentState="
-                                               + OsgiBootUtils.stateAsString(currentState) + ")");
-
-                       try {
-                               Thread.sleep(100l);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       currentState = bundle.getState();
-               }
-       }
-
        /*
         * BUNDLE PATTERNS INSTALLATION
         */
@@ -478,13 +500,14 @@ public class OsgiBoot implements OsgiBootConstants {
        }
 
        /** Implements the path matching logic */
+       @Deprecated
        public List<String> getBundlesUrls(String baseUrl, String bundlePatterns) {
                List<String> urls = new ArrayList<String>();
                if (bundlePatterns == null)
                        return urls;
 
 //             bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
-               if (OsgiBootUtils.debug)
+               if (OsgiBootUtils.isDebug())
                        debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
 
                StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
@@ -572,16 +595,6 @@ public class OsgiBoot implements OsgiBootConstants {
 
                        distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache);
                }
-               // if (baseUrl != null && !(distributionUrl.startsWith("http") ||
-               // distributionUrl.startsWith("file"))) {
-               // // relative url
-               // distributionBundle = new DistributionBundle(baseUrl, distributionUrl,
-               // localCache);
-               // } else {
-               // distributionBundle = new DistributionBundle(distributionUrl);
-               // if (baseUrl != null)
-               // distributionBundle.setBaseUrl(baseUrl);
-               // }
                distributionBundle.processUrl();
                return distributionBundle.listUrls();
        }
@@ -597,7 +610,7 @@ public class OsgiBoot implements OsgiBootConstants {
                        File[] files = baseDir.listFiles();
 
                        if (files == null) {
-                               if (OsgiBootUtils.debug)
+                               if (OsgiBootUtils.isDebug())
                                        OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
                                                        + ", isDirectory=" + baseDir.isDirectory());
                                return;
@@ -630,13 +643,13 @@ public class OsgiBoot implements OsgiBootConstants {
                                                        // FIXME recurse only if start matches ?
                                                        match(matched, base, newCurrentPath, pattern);
 //                                                     } else {
-//                                                             if (OsgiBootUtils.debug)
+//                                                             if (OsgiBootUtils.isDebug())
 //                                                                     debug(newCurrentPath + " does not start match with " + pattern);
 //
 //                                                     }
                                                } else {
                                                        boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath));
-                                                       if (OsgiBootUtils.debug)
+                                                       if (OsgiBootUtils.isDebug())
                                                                debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
                                                        if (nonDirectoryOk)
                                                                matched.add(relativeToFullPath(base, newCurrentPath));
@@ -647,10 +660,6 @@ public class OsgiBoot implements OsgiBootConstants {
                }
        }
 
-       protected void matchFile() {
-
-       }
-
        /*
         * LOW LEVEL UTILITIES
         */
@@ -693,6 +702,7 @@ public class OsgiBoot implements OsgiBootConstants {
        private void refreshFramework() {
                Bundle systemBundle = bundleContext.getBundle(0);
                FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
+               // TODO deal with refresh breaking native loading (e.g SWT)
                frameworkWiring.refreshBundles(null);
        }
 
@@ -717,36 +727,8 @@ public class OsgiBoot implements OsgiBootConstants {
         * BEAN METHODS
         */
 
-       public boolean getDebug() {
-               return OsgiBootUtils.debug;
-       }
-
-       // public void setDebug(boolean debug) {
-       // this.debug = debug;
-       // }
-
        public BundleContext getBundleContext() {
                return bundleContext;
        }
 
-       public String getLocalCache() {
-               return localCache;
-       }
-
-       // public void setDefaultTimeout(long defaultTimeout) {
-       // this.defaultTimeout = defaultTimeout;
-       // }
-
-       // public boolean isExcludeSvn() {
-       // return excludeSvn;
-       // }
-       //
-       // public void setExcludeSvn(boolean excludeSvn) {
-       // this.excludeSvn = excludeSvn;
-       // }
-
-       /*
-        * INTERNAL CLASSES
-        */
-
 }
index d8efe83435bb8b745a51ab977dde354b4b4b058d..a782ac37b078bf7835489df5cb69144d834c89e4 100644 (file)
@@ -1,12 +1,12 @@
 package org.argeo.init.osgi;
 
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
 import java.util.ArrayList;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.ServiceLoader;
 import java.util.StringTokenizer;
@@ -18,33 +18,26 @@ import org.osgi.framework.launch.FrameworkFactory;
 
 /** Utilities, mostly related to logging. */
 public class OsgiBootUtils {
-       /** ISO8601 (as per log4j) and difference to UTC */
-       private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z");
-
-       static boolean debug = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG) == null ? false
-                       : !System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG).trim().equals("false");
+       private final static Logger logger = System.getLogger(OsgiBootUtils.class.getName());
 
        public static void info(Object obj) {
-               System.out.println("# OSGiBOOT      # " + dateFormat.format(new Date()) + " # " + obj);
+               logger.log(Level.INFO, () -> Objects.toString(obj));
        }
 
        public static void debug(Object obj) {
-               if (debug)
-                       System.out.println("# OSGiBOOT DBG  # " + dateFormat.format(new Date()) + " # " + obj);
+               logger.log(Level.TRACE, () -> Objects.toString(obj));
        }
 
        public static void warn(Object obj) {
-               System.out.println("# OSGiBOOT WARN # " + dateFormat.format(new Date()) + " # " + obj);
+               logger.log(Level.WARNING, () -> Objects.toString(obj));
        }
 
        public static void error(Object obj, Throwable e) {
-               System.err.println("# OSGiBOOT ERR  # " + dateFormat.format(new Date()) + " # " + obj);
-               if (e != null)
-                       e.printStackTrace();
+               logger.log(Level.ERROR, () -> Objects.toString(obj), e);
        }
 
        public static boolean isDebug() {
-               return debug;
+               return logger.isLoggable(Level.TRACE);
        }
 
        public static String stateAsString(int state) {
@@ -137,6 +130,7 @@ public class OsgiBootUtils {
                return framework;
        }
 
+       @Deprecated
        public static Map<String, String> equinoxArgsToConfiguration(String[] args) {
                // FIXME implement it
                return new HashMap<>();
index 39c42cbf3c5c127595dfe29e313ef0d9b5453e10..cd2c80acb556792558ba6f316362932e3898b08b 100644 (file)
@@ -38,8 +38,8 @@ public class OsgiBuilder {
 
        public OsgiBuilder() {
                // configuration.put("osgi.clean", "true");
-               configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
-               configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
+               configuration.put(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA, System.getProperty(OsgiBoot.PROP_OSGI_CONFIGURATION_AREA));
+               configuration.put(OsgiBoot.PROP_OSGI_INSTANCE_AREA, System.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA));
                configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
        }
 
@@ -48,7 +48,7 @@ public class OsgiBuilder {
                framework = OsgiBootUtils.launch(configuration);
 
                BundleContext bc = framework.getBundleContext();
-               String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
+               String osgiData = bc.getProperty(OsgiBoot.PROP_OSGI_INSTANCE_AREA);
                // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
                String osgiConf = framework.getDataFile("").getAbsolutePath();
                if (OsgiBootUtils.isDebug())
index 373e3d6719115030884a2ed770b7dbf6667724d9..186577b4dba40dda3ee7169b0d2bb4c863489c10 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.init.osgi;
 
+import java.lang.System.LoggerFinder;
 import java.util.Collections;
 import java.util.Hashtable;
 import java.util.Map;
@@ -15,26 +16,28 @@ import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
 import org.osgi.framework.launch.Framework;
 import org.osgi.framework.launch.FrameworkFactory;
 
 /** An OSGi runtime context. */
-public class OsgiRuntimeContext implements RuntimeContext {
+public class OsgiRuntimeContext implements RuntimeContext, AutoCloseable {
        private Map<String, String> config;
        private Framework framework;
        private OsgiBoot osgiBoot;
 
-       @SuppressWarnings("rawtypes")
-       private ServiceRegistration<Consumer> loggingConfigurationSr;
-       @SuppressWarnings("rawtypes")
-       private ServiceRegistration<Flow.Publisher> logEntryPublisherSr;
-
+       /**
+        * Constructor to use when the runtime context will create the OSGi
+        * {@link Framework}.
+        */
        public OsgiRuntimeContext(Map<String, String> config) {
                this.config = config;
        }
 
-       public OsgiRuntimeContext(BundleContext bundleContext) {
+       /**
+        * Constructor to use when the OSGi {@link Framework} has been created by other
+        * means.
+        */
+       OsgiRuntimeContext(BundleContext bundleContext) {
                start(bundleContext);
        }
 
@@ -55,16 +58,21 @@ public class OsgiRuntimeContext implements RuntimeContext {
        }
 
        public void start(BundleContext bundleContext) {
+               // preferences
+//             SystemRootPreferences systemRootPreferences = ThinPreferencesFactory.getInstance().getSystemRootPreferences();
+//             bundleContext.registerService(AbstractPreferences.class, systemRootPreferences, new Hashtable<>());
+
+               // Make sure LoggerFinder has been searched for, since it is lazily loaded
+               LoggerFinder.getLoggerFinder();
+
                // logging
-               loggingConfigurationSr = bundleContext.registerService(Consumer.class,
-                               ThinLoggerFinder.getConfigurationConsumer(),
+               bundleContext.registerService(Consumer.class, ThinLoggerFinder.getConfigurationConsumer(),
                                new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
-               logEntryPublisherSr = bundleContext.registerService(Flow.Publisher.class,
-                               ThinLoggerFinder.getLogEntryPublisher(),
+               bundleContext.registerService(Flow.Publisher.class, ThinLoggerFinder.getLogEntryPublisher(),
                                new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
 
                osgiBoot = new OsgiBoot(bundleContext);
-               osgiBoot.bootstrap();
+               osgiBoot.bootstrap(config);
 
        }
 
@@ -113,4 +121,8 @@ public class OsgiRuntimeContext implements RuntimeContext {
 
        }
 
+       public Framework getFramework() {
+               return framework;
+       }
+
 }
diff --git a/org.argeo.init/src/org/argeo/init/osgi/log4j.properties b/org.argeo.init/src/org/argeo/init/osgi/log4j.properties
deleted file mode 100644 (file)
index 1fcf25e..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-log4j.rootLogger=WARN, console
-
-log4j.logger.org.argeo=INFO
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
-
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
diff --git a/org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java b/org.argeo.init/src/org/argeo/init/prefs/SystemRootPreferences.java
new file mode 100644 (file)
index 0000000..202100a
--- /dev/null
@@ -0,0 +1,83 @@
+package org.argeo.init.prefs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+
+public class SystemRootPreferences extends AbstractPreferences implements Consumer<AbstractPreferences> {
+       private CompletableFuture<AbstractPreferences> singleChild;
+
+       protected SystemRootPreferences() {
+               super(null, "");
+       }
+
+       @Override
+       public void accept(AbstractPreferences t) {
+               this.singleChild.complete(t);
+       }
+
+       /*
+        * ABSTRACT PREFERENCES
+        */
+
+       @Override
+       protected void putSpi(String key, String value) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected String getSpi(String key) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected void removeSpi(String key) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected void removeNodeSpi() throws BackingStoreException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected String[] keysSpi() throws BackingStoreException {
+               return new String[0];
+       }
+
+       /** Will block. */
+       @Override
+       protected String[] childrenNamesSpi() throws BackingStoreException {
+               String childName;
+               try {
+                       childName = singleChild.get().name();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot get child preferences name", e);
+               }
+               return new String[] { childName };
+       }
+
+       @Override
+       protected AbstractPreferences childSpi(String name) {
+               String childName;
+               try {
+                       childName = singleChild.get().name();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot get child preferences name", e);
+               }
+               if (!childName.equals(name))
+                       throw new IllegalArgumentException("Child name is " + childName + ", not " + name);
+               return null;
+       }
+
+       @Override
+       protected void syncSpi() throws BackingStoreException {
+       }
+
+       @Override
+       protected void flushSpi() throws BackingStoreException {
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java b/org.argeo.init/src/org/argeo/init/prefs/ThinPreferencesFactory.java
new file mode 100644 (file)
index 0000000..8b3356c
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.init.prefs;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.prefs.Preferences;
+import java.util.prefs.PreferencesFactory;
+
+public class ThinPreferencesFactory implements PreferencesFactory {
+       private static CompletableFuture<ThinPreferencesFactory> INSTANCE = new CompletableFuture<>();
+
+       private SystemRootPreferences systemRootPreferences;
+
+       public ThinPreferencesFactory() {
+               systemRootPreferences = new SystemRootPreferences();
+               if (INSTANCE.isDone())
+                       throw new IllegalStateException(
+                                       "There is already a " + ThinPreferencesFactory.class.getName() + " instance.");
+               INSTANCE.complete(this);
+       }
+
+       @Override
+       public Preferences systemRoot() {
+               return systemRootPreferences;
+       }
+
+       @Override
+       public Preferences userRoot() {
+               throw new UnsupportedOperationException();
+       }
+
+       public SystemRootPreferences getSystemRootPreferences() {
+               return systemRootPreferences;
+       }
+
+       public static ThinPreferencesFactory getInstance() {
+               try {
+                       return INSTANCE.get();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot get " + ThinPreferencesFactory.class.getName() + " instance.", e);
+               }
+       }
+}
diff --git a/org.argeo.util/.classpath b/org.argeo.util/.classpath
deleted file mode 100644 (file)
index 71eb167..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
-               <attributes>
-                       <attribute name="module" value="true"/>
-               </attributes>
-       </classpathentry>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.util/.project b/org.argeo.util/.project
deleted file mode 100644 (file)
index 171ff88..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.util</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-               <nature>org.eclipse.pde.PluginNature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.util/META-INF/.gitignore b/org.argeo.util/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.util/bnd.bnd b/org.argeo.util/bnd.bnd
deleted file mode 100644 (file)
index 5f42f77..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-Activator: org.argeo.osgi.internal.EnterpriseActivator
-Bundle-ActivationPolicy: lazy
-
-Import-Package:        org.osgi.*;version=0.0.0,\
-!org.apache.commons.logging,\
-*                              
diff --git a/org.argeo.util/build.properties b/org.argeo.util/build.properties
deleted file mode 100644 (file)
index ae2abc5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-source.. = src/
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java b/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java
deleted file mode 100644 (file)
index bb495dd..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.osgi.internal;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-/**
- * Called to gather information about the OSGi runtime. Should not activate
- * anything else that canonical monitoring services (not creating implicit
- * APIs), which is the responsibility of higher levels.
- */
-public class EnterpriseActivator implements BundleActivator {
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java
deleted file mode 100644 (file)
index 0fc4f32..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.osgi.metatype;
-
-import org.argeo.util.naming.SpecifiedName;
-import org.osgi.service.metatype.AttributeDefinition;
-
-public interface EnumAD extends SpecifiedName, AttributeDefinition {
-       String name();
-
-       default Object getDefault() {
-               return null;
-       }
-
-       @Override
-       default String getName() {
-               return name();
-       }
-
-       @Override
-       default String getID() {
-               return getClass().getName() + "." + name();
-       }
-
-       @Override
-       default String getDescription() {
-               return null;
-       }
-
-       @Override
-       default int getCardinality() {
-               return 0;
-       }
-
-       @Override
-       default int getType() {
-               return STRING;
-       }
-
-       @Override
-       default String[] getOptionValues() {
-               return null;
-       }
-
-       @Override
-       default String[] getOptionLabels() {
-               return null;
-       }
-
-       @Override
-       default String validate(String value) {
-               return null;
-       }
-
-       @Override
-       default String[] getDefaultValue() {
-               Object value = getDefault();
-               if (value == null)
-                       return null;
-               return new String[] { value.toString() };
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java
deleted file mode 100644 (file)
index 97c7d56..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.osgi.metatype;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.osgi.service.metatype.AttributeDefinition;
-import org.osgi.service.metatype.ObjectClassDefinition;
-
-public class EnumOCD<T extends Enum<T>> implements ObjectClassDefinition {
-       private final Class<T> enumClass;
-       private String locale;
-
-       public EnumOCD(Class<T> clazz, String locale) {
-               this.enumClass = clazz;
-               this.locale = locale;
-       }
-
-       @Override
-       public String getName() {
-               return null;
-       }
-
-       public String getLocale() {
-               return locale;
-       }
-
-       @Override
-       public String getID() {
-               return enumClass.getName();
-       }
-
-       @Override
-       public String getDescription() {
-               return null;
-       }
-
-       @Override
-       public AttributeDefinition[] getAttributeDefinitions(int filter) {
-               EnumSet<T> set = EnumSet.allOf(enumClass);
-               List<AttributeDefinition> attrs = new ArrayList<>();
-               for (T key : set)
-                       attrs.add((AttributeDefinition) key);
-               return attrs.toArray(new AttributeDefinition[attrs.size()]);
-       }
-
-       @Override
-       public InputStream getIcon(int size) throws IOException {
-               return null;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java b/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java
deleted file mode 100644 (file)
index bca5d1f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** OSGi metatype support. */
-package org.argeo.osgi.metatype;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java b/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java
deleted file mode 100644 (file)
index c0ec290..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.argeo.osgi.provisioning;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.zip.ZipInputStream;
-
-import org.osgi.service.provisioning.ProvisioningService;
-
-public class SimpleProvisioningService implements ProvisioningService {
-       private Map<String, Object> map = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public SimpleProvisioningService() {
-               // update count
-               map.put(PROVISIONING_UPDATE_COUNT, 0);
-       }
-
-       @Override
-       public Dictionary<String, Object> getInformation() {
-               return new Information();
-       }
-
-       @Override
-       public synchronized void setInformation(Dictionary<String, ?> info) {
-               map.clear();
-               addInformation(info);
-       }
-
-       @Override
-       public synchronized void addInformation(Dictionary<String, ?> info) {
-               Enumeration<String> e = info.keys();
-               while (e.hasMoreElements()) {
-                       String key = e.nextElement();
-                       map.put(key, info.get(key));
-               }
-               incrementProvisioningUpdateCount();
-       }
-
-       protected synchronized void incrementProvisioningUpdateCount() {
-               Integer current = (Integer) map.get(PROVISIONING_UPDATE_COUNT);
-               Integer newValue = current + 1;
-               map.put(PROVISIONING_UPDATE_COUNT, newValue);
-       }
-
-       @Override
-       public synchronized void addInformation(ZipInputStream zis) throws IOException {
-               throw new UnsupportedOperationException();
-       }
-
-       class Information extends Dictionary<String, Object> {
-
-               @Override
-               public int size() {
-                       return map.size();
-               }
-
-               @Override
-               public boolean isEmpty() {
-                       return map.isEmpty();
-               }
-
-               @Override
-               public Enumeration<String> keys() {
-                       Iterator<String> it = map.keySet().iterator();
-                       return new Enumeration<String>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public String nextElement() {
-                                       return it.next();
-                               }
-
-                       };
-               }
-
-               @Override
-               public Enumeration<Object> elements() {
-                       Iterator<Object> it = map.values().iterator();
-                       return new Enumeration<Object>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public Object nextElement() {
-                                       return it.next();
-                               }
-
-                       };
-               }
-
-               @Override
-               public Object get(Object key) {
-                       return map.get(key);
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public Object remove(Object key) {
-                       throw new UnsupportedOperationException();
-               }
-
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java b/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java
deleted file mode 100644 (file)
index 1859887..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** OSGi provisioning support. */
-package org.argeo.osgi.provisioning;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java
deleted file mode 100644 (file)
index 2dd94c6..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.osgi.transaction;
-
-/** JTA transaction status. */
-public class JtaStatusAdapter implements TransactionStatusAdapter<Integer> {
-       private static final Integer STATUS_ACTIVE = 0;
-       private static final Integer STATUS_COMMITTED = 3;
-       private static final Integer STATUS_COMMITTING = 8;
-       private static final Integer STATUS_MARKED_ROLLBACK = 1;
-       private static final Integer STATUS_NO_TRANSACTION = 6;
-       private static final Integer STATUS_PREPARED = 2;
-       private static final Integer STATUS_PREPARING = 7;
-       private static final Integer STATUS_ROLLEDBACK = 4;
-       private static final Integer STATUS_ROLLING_BACK = 9;
-//     private static final Integer STATUS_UNKNOWN = 5;
-
-       @Override
-       public Integer getActiveStatus() {
-               return STATUS_ACTIVE;
-       }
-
-       @Override
-       public Integer getPreparingStatus() {
-               return STATUS_PREPARING;
-       }
-
-       @Override
-       public Integer getMarkedRollbackStatus() {
-               return STATUS_MARKED_ROLLBACK;
-       }
-
-       @Override
-       public Integer getPreparedStatus() {
-               return STATUS_PREPARED;
-       }
-
-       @Override
-       public Integer getCommittingStatus() {
-               return STATUS_COMMITTING;
-       }
-
-       @Override
-       public Integer getCommittedStatus() {
-               return STATUS_COMMITTED;
-       }
-
-       @Override
-       public Integer getRollingBackStatus() {
-               return STATUS_ROLLING_BACK;
-       }
-
-       @Override
-       public Integer getRolledBackStatus() {
-               return STATUS_ROLLEDBACK;
-       }
-
-       @Override
-       public Integer getNoTransactionStatus() {
-               return STATUS_NO_TRANSACTION;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java
deleted file mode 100644 (file)
index cf8a80b..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.transaction;
-
-/** Internal unchecked rollback exception. */
-class SimpleRollbackException extends RuntimeException {
-       private static final long serialVersionUID = 8055601819719780566L;
-
-       public SimpleRollbackException() {
-               super();
-       }
-
-       public SimpleRollbackException(Throwable cause) {
-               super(cause);
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java
deleted file mode 100644 (file)
index e668b31..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.argeo.osgi.transaction;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** Simple implementation of an XA transaction. */
-class SimpleTransaction<T>
-//implements Transaction, Status 
-{
-       private final Xid xid;
-       private T status;
-       private final List<XAResource> xaResources = new ArrayList<XAResource>();
-
-       private final SimpleTransactionManager transactionManager;
-       private TransactionStatusAdapter<T> tsa;
-
-       public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter<T> tsa) {
-               this.tsa = tsa;
-               this.status = tsa.getActiveStatus();
-               this.xid = new UuidXid();
-               this.transactionManager = transactionManager;
-       }
-
-       public synchronized void commit()
-//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-//                     SecurityException, IllegalStateException, SystemException 
-       {
-               status = tsa.getPreparingStatus();
-               for (XAResource xaRes : xaResources) {
-                       if (status.equals(tsa.getMarkedRollbackStatus()))
-                               break;
-                       try {
-                               xaRes.prepare(xid);
-                       } catch (XAException e) {
-                               status = tsa.getMarkedRollbackStatus();
-                               error("Cannot prepare " + xaRes + " for " + xid, e);
-                       }
-               }
-               if (status.equals(tsa.getMarkedRollbackStatus())) {
-                       rollback();
-                       throw new SimpleRollbackException();
-               }
-               status = tsa.getPreparedStatus();
-
-               status = tsa.getCommittingStatus();
-               for (XAResource xaRes : xaResources) {
-                       if (status.equals(tsa.getMarkedRollbackStatus()))
-                               break;
-                       try {
-                               xaRes.commit(xid, false);
-                       } catch (XAException e) {
-                               status = tsa.getMarkedRollbackStatus();
-                               error("Cannot prepare " + xaRes + " for " + xid, e);
-                       }
-               }
-               if (status.equals(tsa.getMarkedRollbackStatus())) {
-                       rollback();
-                       throw new SimpleRollbackException();
-               }
-
-               // complete
-               status = tsa.getCommittedStatus();
-               clearResources(XAResource.TMSUCCESS);
-               transactionManager.unregister(xid);
-       }
-
-       public synchronized void rollback()
-//                     throws IllegalStateException, SystemException 
-       {
-               status = tsa.getRollingBackStatus();
-               for (XAResource xaRes : xaResources) {
-                       try {
-                               xaRes.rollback(xid);
-                       } catch (XAException e) {
-                               error("Cannot rollback " + xaRes + " for " + xid, e);
-                       }
-               }
-
-               // complete
-               status = tsa.getRolledBackStatus();
-               clearResources(XAResource.TMFAIL);
-               transactionManager.unregister(xid);
-       }
-
-       public synchronized boolean enlistResource(XAResource xaRes)
-//                     throws RollbackException, IllegalStateException, SystemException 
-       {
-               if (xaResources.add(xaRes)) {
-                       try {
-                               xaRes.start(getXid(), XAResource.TMNOFLAGS);
-                               return true;
-                       } catch (XAException e) {
-                               error("Cannot enlist " + xaRes, e);
-                               return false;
-                       }
-               } else
-                       return false;
-       }
-
-       public synchronized boolean delistResource(XAResource xaRes, int flag)
-//                     throws IllegalStateException, SystemException 
-       {
-               if (xaResources.remove(xaRes)) {
-                       try {
-                               xaRes.end(getXid(), flag);
-                       } catch (XAException e) {
-                               error("Cannot delist " + xaRes, e);
-                               return false;
-                       }
-                       return true;
-               } else
-                       return false;
-       }
-
-       protected void clearResources(int flag) {
-               for (XAResource xaRes : xaResources)
-                       try {
-                               xaRes.end(getXid(), flag);
-                       } catch (XAException e) {
-                               error("Cannot end " + xaRes, e);
-                       }
-               xaResources.clear();
-       }
-
-       protected void error(Object obj, Exception e) {
-               System.err.println(obj);
-               e.printStackTrace();
-       }
-
-       public synchronized T getStatus()
-//                     throws SystemException 
-       {
-               return status;
-       }
-
-//     public void registerSynchronization(Synchronization sync)
-//                     throws RollbackException, IllegalStateException, SystemException {
-//             throw new UnsupportedOperationException();
-//     }
-
-       public void setRollbackOnly()
-//                     throws IllegalStateException, SystemException 
-       {
-               status = tsa.getMarkedRollbackStatus();
-       }
-
-       @Override
-       public int hashCode() {
-               return xid.hashCode();
-       }
-
-       Xid getXid() {
-               return xid;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java
deleted file mode 100644 (file)
index 3d4edfd..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-package org.argeo.osgi.transaction;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Callable;
-
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/**
- * Simple implementation of an XA transaction manager.
- */
-public class SimpleTransactionManager
-// implements TransactionManager, UserTransaction 
-               implements WorkControl, WorkTransaction {
-       private ThreadLocal<SimpleTransaction<Integer>> current = new ThreadLocal<SimpleTransaction<Integer>>();
-
-       private Map<Xid, SimpleTransaction<Integer>> knownTransactions = Collections
-                       .synchronizedMap(new HashMap<Xid, SimpleTransaction<Integer>>());
-       private TransactionStatusAdapter<Integer> tsa = new JtaStatusAdapter();
-//     private SyncRegistry syncRegistry = new SyncRegistry();
-
-       /*
-        * WORK IMPLEMENTATION
-        */
-       @Override
-       public <T> T required(Callable<T> work) {
-               T res;
-               begin();
-               try {
-                       res = work.call();
-                       commit();
-               } catch (Exception e) {
-                       rollback();
-                       throw new SimpleRollbackException(e);
-               }
-               return res;
-       }
-
-       @Override
-       public WorkContext getWorkContext() {
-               return new WorkContext() {
-
-                       @Override
-                       public void registerXAResource(XAResource resource, String recoveryId) {
-                               getTransaction().enlistResource(resource);
-                       }
-               };
-       }
-
-       /*
-        * WORK TRANSACTION IMPLEMENTATION
-        */
-
-       @Override
-       public boolean isNoTransactionStatus() {
-               return tsa.getNoTransactionStatus().equals(getStatus());
-       }
-
-       /*
-        * JTA IMPLEMENTATION
-        */
-
-       public void begin()
-//                     throws NotSupportedException, SystemException 
-       {
-               if (getCurrent() != null)
-                       throw new UnsupportedOperationException("Nested transactions are not supported");
-               SimpleTransaction<Integer> transaction = new SimpleTransaction<Integer>(this, tsa);
-               knownTransactions.put(transaction.getXid(), transaction);
-               current.set(transaction);
-       }
-
-       public void commit()
-//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-//                     SecurityException, IllegalStateException, SystemException 
-       {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().commit();
-       }
-
-       public int getStatus()
-//                     throws SystemException
-       {
-               if (getCurrent() == null)
-                       return tsa.getNoTransactionStatus();
-               return getTransaction().getStatus();
-       }
-
-       public SimpleTransaction<Integer> getTransaction()
-//                     throws SystemException 
-       {
-               return getCurrent();
-       }
-
-       protected SimpleTransaction<Integer> getCurrent()
-//                     throws SystemException 
-       {
-               SimpleTransaction<Integer> transaction = current.get();
-               if (transaction == null)
-                       return null;
-               Integer status = transaction.getStatus();
-               if (status.equals(tsa.getCommittedStatus()) || status.equals(tsa.getRolledBackStatus())) {
-                       current.remove();
-                       return null;
-               }
-               return transaction;
-       }
-
-       void unregister(Xid xid) {
-               knownTransactions.remove(xid);
-       }
-
-       public void resume(SimpleTransaction<Integer> tobj)
-//                     throws InvalidTransactionException, IllegalStateException, SystemException 
-       {
-               if (getCurrent() != null)
-                       throw new IllegalStateException("Transaction " + current.get() + " already registered");
-               current.set(tobj);
-       }
-
-       public void rollback()
-//                     throws IllegalStateException, SecurityException, SystemException 
-       {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().rollback();
-       }
-
-       public void setRollbackOnly()
-//                     throws IllegalStateException, SystemException 
-       {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().setRollbackOnly();
-       }
-
-       public void setTransactionTimeout(int seconds)
-//                     throws SystemException
-       {
-               throw new UnsupportedOperationException();
-       }
-
-       public SimpleTransaction<Integer> suspend()
-//                     throws SystemException
-       {
-               SimpleTransaction<Integer> transaction = getCurrent();
-               current.remove();
-               return transaction;
-       }
-
-//     public TransactionSynchronizationRegistry getTsr() {
-//             return syncRegistry;
-//     }
-//
-//     private class SyncRegistry implements TransactionSynchronizationRegistry {
-//             @Override
-//             public Object getTransactionKey() {
-//                     try {
-//                             SimpleTransaction transaction = getCurrent();
-//                             if (transaction == null)
-//                                     return null;
-//                             return getCurrent().getXid();
-//                     } catch (SystemException e) {
-//                             throw new IllegalStateException("Cannot get transaction key", e);
-//                     }
-//             }
-//
-//             @Override
-//             public void putResource(Object key, Object value) {
-//                     throw new UnsupportedOperationException();
-//             }
-//
-//             @Override
-//             public Object getResource(Object key) {
-//                     throw new UnsupportedOperationException();
-//             }
-//
-//             @Override
-//             public void registerInterposedSynchronization(Synchronization sync) {
-//                     throw new UnsupportedOperationException();
-//             }
-//
-//             @Override
-//             public int getTransactionStatus() {
-//                     try {
-//                             return getStatus();
-//                     } catch (SystemException e) {
-//                             throw new IllegalStateException("Cannot get status", e);
-//                     }
-//             }
-//
-//             @Override
-//             public boolean getRollbackOnly() {
-//                     try {
-//                             return getStatus() == Status.STATUS_MARKED_ROLLBACK;
-//                     } catch (SystemException e) {
-//                             throw new IllegalStateException("Cannot get status", e);
-//                     }
-//             }
-//
-//             @Override
-//             public void setRollbackOnly() {
-//                     try {
-//                             getCurrent().setRollbackOnly();
-//                     } catch (Exception e) {
-//                             throw new IllegalStateException("Cannot set rollback only", e);
-//                     }
-//             }
-//
-//     }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java
deleted file mode 100644 (file)
index 87abceb..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.osgi.transaction;
-
-/** Abstract the various approaches to represent transaction status. */
-public interface TransactionStatusAdapter<T> {
-       T getActiveStatus();
-
-       T getPreparingStatus();
-
-       T getMarkedRollbackStatus();
-
-       T getPreparedStatus();
-
-       T getCommittingStatus();
-
-       T getCommittedStatus();
-
-       T getRollingBackStatus();
-
-       T getRolledBackStatus();
-
-       T getNoTransactionStatus();
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java b/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java
deleted file mode 100644 (file)
index 729aef8..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.argeo.osgi.transaction;
-
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.UUID;
-
-import javax.transaction.xa.Xid;
-
-/**
- * Implementation of {@link Xid} based on {@link UUID}, using max significant
- * bits as global transaction id, and least significant bits as branch
- * qualifier.
- */
-public class UuidXid implements Xid, Serializable {
-       private static final long serialVersionUID = -5380531989917886819L;
-       public final static int FORMAT = (int) serialVersionUID;
-
-       private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
-
-       private final int format;
-       private final byte[] globalTransactionId;
-       private final byte[] branchQualifier;
-       private final String uuid;
-       private final int hashCode;
-
-       public UuidXid() {
-               this(UUID.randomUUID());
-       }
-
-       public UuidXid(UUID uuid) {
-               this.format = FORMAT;
-               this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
-               this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
-               this.uuid = uuid.toString();
-               this.hashCode = uuid.hashCode();
-       }
-
-       public UuidXid(Xid xid) {
-               this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
-                               .getBranchQualifier());
-       }
-
-       private UuidXid(int format, byte[] globalTransactionId,
-                       byte[] branchQualifier) {
-               this.format = format;
-               this.globalTransactionId = globalTransactionId;
-               this.branchQualifier = branchQualifier;
-               this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
-                               .toString();
-               this.hashCode = uuid.hashCode();
-       }
-
-       @Override
-       public int getFormatId() {
-               return format;
-       }
-
-       @Override
-       public byte[] getGlobalTransactionId() {
-               return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
-       }
-
-       @Override
-       public byte[] getBranchQualifier() {
-               return Arrays.copyOf(branchQualifier, branchQualifier.length);
-       }
-
-       @Override
-       public int hashCode() {
-               return hashCode;
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (this == obj)
-                       return true;
-               if (obj instanceof UuidXid) {
-                       UuidXid that = (UuidXid) obj;
-                       return Arrays.equals(globalTransactionId, that.globalTransactionId)
-                                       && Arrays.equals(branchQualifier, that.branchQualifier);
-               }
-               if (obj instanceof Xid) {
-                       Xid that = (Xid) obj;
-                       return Arrays.equals(globalTransactionId,
-                                       that.getGlobalTransactionId())
-                                       && Arrays
-                                                       .equals(branchQualifier, that.getBranchQualifier());
-               }
-               return uuid.equals(obj.toString());
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return new UuidXid(format, globalTransactionId, branchQualifier);
-       }
-
-       @Override
-       public String toString() {
-               return uuid;
-       }
-
-       public UUID asUuid() {
-               return bytesToUUID(globalTransactionId, branchQualifier);
-       }
-
-       public static byte[] uuidToBytes(long bits) {
-               ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
-               buffer.putLong(0, bits);
-               return buffer.array();
-       }
-
-       public static UUID bytesToUUID(byte[] most, byte[] least) {
-               if (most.length < BYTES_PER_LONG)
-                       most = Arrays.copyOf(most, BYTES_PER_LONG);
-               if (least.length < BYTES_PER_LONG)
-                       least = Arrays.copyOf(least, BYTES_PER_LONG);
-               ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
-               buffer.put(most, 0, BYTES_PER_LONG);
-               buffer.put(least, 0, BYTES_PER_LONG);
-               buffer.flip();
-               return new UUID(buffer.getLong(), buffer.getLong());
-       }
-
-       // public static void main(String[] args) {
-       // UUID uuid = UUID.randomUUID();
-       // System.out.println(uuid);
-       // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
-       // uuidToBytes(uuid.getLeastSignificantBits()));
-       // System.out.println(uuid);
-       // }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java
deleted file mode 100644 (file)
index f50f208..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.osgi.transaction;
-
-import javax.transaction.xa.XAResource;
-
-/**
- * A minimalistic interface similar to OSGi transaction context in order to
- * register XA resources.
- */
-public interface WorkContext {
-       void registerXAResource(XAResource resource, String recoveryId);
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java
deleted file mode 100644 (file)
index 7668095..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.transaction;
-
-import java.util.concurrent.Callable;
-
-/**
- * A minimalistic interface inspired by OSGi transaction control in order to
- * commit units of work externally.
- */
-public interface WorkControl {
-       <T> T required(Callable<T> work);
-
-       void setRollbackOnly();
-
-       WorkContext getWorkContext();
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java
deleted file mode 100644 (file)
index 6533909..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.transaction;
-
-/**
- * A minimalistic interface inspired by JTA user transaction in order to commit
- * units of work externally.
- */
-public interface WorkTransaction {
-       void begin();
-
-       void commit();
-
-       void rollback();
-
-       boolean isNoTransactionStatus();
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java b/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java
deleted file mode 100644 (file)
index 3d37562..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Minimalistic and partial XA transaction manager implementation. */
-package org.argeo.osgi.transaction;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
deleted file mode 100644 (file)
index e028e38..0000000
+++ /dev/null
@@ -1,517 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.extensibleObject;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-import static org.argeo.util.naming.LdapObjs.organizationalPerson;
-import static org.argeo.util.naming.LdapObjs.person;
-import static org.argeo.util.naming.LdapObjs.top;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.transaction.WorkControl;
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Base class for a {@link UserDirectory}. */
-public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
-       static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
-       static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
-
-       private final Hashtable<String, Object> properties;
-       private final LdapName baseDn, userBaseDn, groupBaseDn;
-       private final String userObjectClass, userBase, groupObjectClass, groupBase;
-
-       private final boolean readOnly;
-       private final boolean disabled;
-       private final String uri;
-
-       private UserAdmin externalRoles;
-       // private List<String> indexedUserProperties = Arrays
-       // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
-       // LdapAttrs.cn.name() });
-
-       private final boolean scoped;
-
-       private String memberAttributeId = "member";
-       private List<String> credentialAttributeIds = Arrays
-                       .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
-
-       // Transaction
-//     private TransactionManager transactionManager;
-       private WorkControl transactionControl;
-       private WcXaResource xaResource = new WcXaResource(this);
-
-       private String forcedPassword;
-
-       AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
-               this.scoped = scoped;
-               properties = new Hashtable<String, Object>();
-               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                       String key = keys.nextElement();
-                       properties.put(key, props.get(key));
-               }
-
-               if (uriArg != null) {
-                       uri = uriArg.toString();
-                       // uri from properties is ignored
-               } else {
-                       String uriStr = UserAdminConf.uri.getValue(properties);
-                       if (uriStr == null)
-                               uri = null;
-                       else
-                               uri = uriStr;
-               }
-
-               forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
-
-               userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
-               userBase = UserAdminConf.userBase.getValue(properties);
-               groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
-               groupBase = UserAdminConf.groupBase.getValue(properties);
-               try {
-                       baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
-                       userBaseDn = new LdapName(userBase + "," + baseDn);
-                       groupBaseDn = new LdapName(groupBase + "," + baseDn);
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
-               }
-               String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
-               if (readOnlyStr == null) {
-                       readOnly = readOnlyDefault(uri);
-                       properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
-               } else
-                       readOnly = Boolean.parseBoolean(readOnlyStr);
-               String disabledStr = UserAdminConf.disabled.getValue(properties);
-               if (disabledStr != null)
-                       disabled = Boolean.parseBoolean(disabledStr);
-               else
-                       disabled = false;
-       }
-
-       /** Returns the groups this user is a direct member of. */
-       protected abstract List<LdapName> getDirectGroups(LdapName dn);
-
-       protected abstract Boolean daoHasRole(LdapName dn);
-
-       protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
-
-       protected abstract List<DirectoryUser> doGetRoles(Filter f);
-
-       protected abstract AbstractUserDirectory scope(User user);
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       protected boolean isEditing() {
-               return xaResource.wc() != null;
-       }
-
-       protected UserDirectoryWorkingCopy getWorkingCopy() {
-               UserDirectoryWorkingCopy wc = xaResource.wc();
-               if (wc == null)
-                       return null;
-               return wc;
-       }
-
-       protected void checkEdit() {
-//             Transaction transaction;
-//             try {
-//                     transaction = transactionManager.getTransaction();
-//             } catch (SystemException e) {
-//                     throw new UserDirectoryException("Cannot get transaction", e);
-//             }
-//             if (transaction == null)
-//                     throw new UserDirectoryException("A transaction needs to be active in order to edit");
-               if (xaResource.wc() == null) {
-                       try {
-//                             transaction.enlistResource(xaResource);
-                               transactionControl.getWorkContext().registerXAResource(xaResource, null);
-                       } catch (Exception e) {
-                               throw new UserDirectoryException("Cannot enlist " + xaResource, e);
-                       }
-               } else {
-               }
-       }
-
-       protected List<Role> getAllRoles(DirectoryUser user) {
-               List<Role> allRoles = new ArrayList<Role>();
-               if (user != null) {
-                       collectRoles(user, allRoles);
-                       allRoles.add(user);
-               } else
-                       collectAnonymousRoles(allRoles);
-               return allRoles;
-       }
-
-       private void collectRoles(DirectoryUser user, List<Role> allRoles) {
-               Attributes attrs = user.getAttributes();
-               // TODO centralize attribute name
-               Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
-               // if user belongs to this directory, we only check meberOf
-               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
-                       try {
-                               NamingEnumeration<?> values = memberOf.getAll();
-                               while (values.hasMore()) {
-                                       Object value = values.next();
-                                       LdapName groupDn = new LdapName(value.toString());
-                                       DirectoryUser group = doGetRole(groupDn);
-                                       if (group != null)
-                                               allRoles.add(group);
-                               }
-                       } catch (Exception e) {
-                               throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
-                       }
-               } else {
-                       for (LdapName groupDn : getDirectGroups(user.getDn())) {
-                               // TODO check for loops
-                               DirectoryUser group = doGetRole(groupDn);
-                               if (group != null) {
-                                       allRoles.add(group);
-                                       collectRoles(group, allRoles);
-                               }
-                       }
-               }
-       }
-
-       private void collectAnonymousRoles(List<Role> allRoles) {
-               // TODO gather anonymous roles
-       }
-
-       // USER ADMIN
-       @Override
-       public Role getRole(String name) {
-               return doGetRole(toDn(name));
-       }
-
-       protected DirectoryUser doGetRole(LdapName dn) {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               DirectoryUser user;
-               try {
-                       user = daoGetRole(dn);
-               } catch (NameNotFoundException e) {
-                       user = null;
-               }
-               if (wc != null) {
-                       if (user == null && wc.getNewUsers().containsKey(dn))
-                               user = wc.getNewUsers().get(dn);
-                       else if (wc.getDeletedUsers().containsKey(dn))
-                               user = null;
-               }
-               return user;
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
-               List<DirectoryUser> res = doGetRoles(f);
-               if (wc != null) {
-                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
-                               DirectoryUser user = it.next();
-                               LdapName dn = user.getDn();
-                               if (wc.getDeletedUsers().containsKey(dn))
-                                       it.remove();
-                       }
-                       for (DirectoryUser user : wc.getNewUsers().values()) {
-                               if (f == null || f.match(user.getProperties()))
-                                       res.add(user);
-                       }
-                       // no need to check modified users,
-                       // since doGetRoles was already based on the modified attributes
-               }
-               return res.toArray(new Role[res.size()]);
-       }
-
-       @Override
-       public User getUser(String key, String value) {
-               // TODO check value null or empty
-               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
-               if (key != null) {
-                       doGetUser(key, value, collectedUsers);
-               } else {
-                       throw new UserDirectoryException("Key cannot be null");
-               }
-
-               if (collectedUsers.size() == 1) {
-                       return collectedUsers.get(0);
-               } else if (collectedUsers.size() > 1) {
-                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
-                       // "") + value);
-               }
-               return null;
-       }
-
-       protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
-               try {
-                       Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
-                       List<DirectoryUser> users = doGetRoles(f);
-                       collectedUsers.addAll(users);
-               } catch (InvalidSyntaxException e) {
-                       throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
-               }
-       }
-
-       @Override
-       public Authorization getAuthorization(User user) {
-               if (user == null || user instanceof DirectoryUser) {
-                       return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
-               } else {
-                       // bind
-                       AbstractUserDirectory scopedUserAdmin = scope(user);
-                       try {
-                               DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
-                               if (directoryUser == null)
-                                       throw new UserDirectoryException("No scoped user found for " + user);
-                               LdifAuthorization authorization = new LdifAuthorization(directoryUser,
-                                               scopedUserAdmin.getAllRoles(directoryUser));
-                               return authorization;
-                       } finally {
-                               scopedUserAdmin.destroy();
-                       }
-               }
-       }
-
-       @Override
-       public Role createRole(String name, int type) {
-               checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toDn(name);
-               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
-                       throw new UserDirectoryException("Already a role " + name);
-               BasicAttributes attrs = new BasicAttributes(true);
-               // attrs.put(LdifName.dn.name(), dn.toString());
-               Rdn nameRdn = dn.getRdn(dn.size() - 1);
-               // TODO deal with multiple attr RDN
-               attrs.put(nameRdn.getType(), nameRdn.getValue());
-               if (wc.getDeletedUsers().containsKey(dn)) {
-                       wc.getDeletedUsers().remove(dn);
-                       wc.getModifiedUsers().put(dn, attrs);
-                       return getRole(name);
-               } else {
-                       wc.getModifiedUsers().put(dn, attrs);
-                       DirectoryUser newRole = newRole(dn, type, attrs);
-                       wc.getNewUsers().put(dn, newRole);
-                       return newRole;
-               }
-       }
-
-       protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
-               LdifUser newRole;
-               BasicAttribute objClass = new BasicAttribute(objectClass.name());
-               if (type == Role.USER) {
-                       String userObjClass = newUserObjectClass(dn);
-                       objClass.add(userObjClass);
-                       if (inetOrgPerson.name().equals(userObjClass)) {
-                               objClass.add(organizationalPerson.name());
-                               objClass.add(person.name());
-                       } else if (organizationalPerson.name().equals(userObjClass)) {
-                               objClass.add(person.name());
-                       }
-                       objClass.add(top.name());
-                       objClass.add(extensibleObject.name());
-                       attrs.put(objClass);
-                       newRole = new LdifUser(this, dn, attrs);
-               } else if (type == Role.GROUP) {
-                       String groupObjClass = getGroupObjectClass();
-                       objClass.add(groupObjClass);
-                       // objClass.add(LdifName.extensibleObject.name());
-                       objClass.add(top.name());
-                       attrs.put(objClass);
-                       newRole = new LdifGroup(this, dn, attrs);
-               } else
-                       throw new UserDirectoryException("Unsupported type " + type);
-               return newRole;
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toDn(name);
-               boolean actuallyDeleted;
-               if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
-                       DirectoryUser user = (DirectoryUser) getRole(name);
-                       wc.getDeletedUsers().put(dn, user);
-                       actuallyDeleted = true;
-               } else {// just removing from groups (e.g. system roles)
-                       actuallyDeleted = false;
-               }
-               for (LdapName groupDn : getDirectGroups(dn)) {
-                       DirectoryUser group = doGetRole(groupDn);
-                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
-               }
-               return actuallyDeleted;
-       }
-
-       // TRANSACTION
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void commit(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       // UTILITIES
-       protected LdapName toDn(String name) {
-               try {
-                       return new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formatted name", e);
-               }
-       }
-
-       // GETTERS
-       protected String getMemberAttributeId() {
-               return memberAttributeId;
-       }
-
-       protected List<String> getCredentialAttributeIds() {
-               return credentialAttributeIds;
-       }
-
-       protected String getUri() {
-               return uri;
-       }
-
-       private static boolean readOnlyDefault(String uriStr) {
-               if (uriStr == null)
-                       return true;
-               /// TODO make it more generic
-               URI uri;
-               try {
-                       uri = new URI(uriStr.split(" ")[0]);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               if (uri.getScheme() == null)
-                       return false;// assume relative file to be writable
-               if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
-                       File file = new File(uri);
-                       if (file.exists())
-                               return !file.canWrite();
-                       else
-                               return !file.getParentFile().canWrite();
-               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
-                       if (uri.getAuthority() != null)// assume writable if authenticated
-                               return false;
-               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
-                       return true;
-               }
-               return true;// read only by default
-       }
-
-       public boolean isReadOnly() {
-               return readOnly;
-       }
-
-       public boolean isDisabled() {
-               return disabled;
-       }
-
-       protected UserAdmin getExternalRoles() {
-               return externalRoles;
-       }
-
-       protected int roleType(LdapName dn) {
-               if (dn.startsWith(groupBaseDn))
-                       return Role.GROUP;
-               else if (dn.startsWith(userBaseDn))
-                       return Role.USER;
-               else
-                       return Role.GROUP;
-       }
-
-       /** dn can be null, in that case a default should be returned. */
-       public String getUserObjectClass() {
-               return userObjectClass;
-       }
-
-       public String getUserBase() {
-               return userBase;
-       }
-
-       protected String newUserObjectClass(LdapName dn) {
-               return getUserObjectClass();
-       }
-
-       public String getGroupObjectClass() {
-               return groupObjectClass;
-       }
-
-       public String getGroupBase() {
-               return groupBase;
-       }
-
-       public LdapName getBaseDn() {
-               return (LdapName) baseDn.clone();
-       }
-
-       public Dictionary<String, Object> getProperties() {
-               return properties;
-       }
-
-       public Dictionary<String, Object> cloneProperties() {
-               return new Hashtable<>(properties);
-       }
-
-       public void setExternalRoles(UserAdmin externalRoles) {
-               this.externalRoles = externalRoles;
-       }
-
-//     public void setTransactionManager(TransactionManager transactionManager) {
-//             this.transactionManager = transactionManager;
-//     }
-
-       public String getForcedPassword() {
-               return forcedPassword;
-       }
-
-       public void setTransactionControl(WorkControl transactionControl) {
-               this.transactionControl = transactionControl;
-       }
-
-       public WcXaResource getXaResource() {
-               return xaResource;
-       }
-
-       public boolean isScoped() {
-               return scoped;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java
deleted file mode 100644 (file)
index 05ba948..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.osgi.service.useradmin.Authorization;
-
-/** An {@link Authorization} which combines roles form various auth sources. */
-class AggregatingAuthorization implements Authorization {
-       private final String name;
-       private final String displayName;
-       private final Set<String> systemRoles;
-       private final Set<String> roles;
-
-       public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
-               this.name = new X500Principal(name).getName();
-               this.displayName = displayName;
-               this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
-               Set<String> temp = new HashSet<>();
-               for (String role : roles) {
-                       if (!temp.contains(role))
-                               temp.add(role);
-               }
-               this.roles = Collections.unmodifiableSet(temp);
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               if (systemRoles.contains(name))
-                       return true;
-               if (roles.contains(name))
-                       return true;
-               return false;
-       }
-
-       @Override
-       public String[] getRoles() {
-               int size = systemRoles.size() + roles.size();
-               List<String> res = new ArrayList<String>(size);
-               res.addAll(systemRoles);
-               res.addAll(roles);
-               return res.toArray(new String[size]);
-       }
-
-       @Override
-       public int hashCode() {
-               if (name == null)
-                       return super.hashCode();
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof Authorization))
-                       return false;
-               Authorization that = (Authorization) obj;
-               if (name == null)
-                       return that.getName() == null;
-               return name.equals(that.getName());
-       }
-
-       @Override
-       public String toString() {
-               return displayName;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
deleted file mode 100644 (file)
index c274ed9..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-public class AggregatingUserAdmin implements UserAdmin {
-       private final LdapName systemRolesBaseDn;
-       private final LdapName tokensBaseDn;
-
-       // DAOs
-       private AbstractUserDirectory systemRoles = null;
-       private AbstractUserDirectory tokens = null;
-       private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
-
-       // TODO rather use an empty constructor and an init method
-       public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
-               try {
-                       this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
-                       if (tokensBaseDn != null)
-                               this.tokensBaseDn = new LdapName(tokensBaseDn);
-                       else
-                               this.tokensBaseDn = null;
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Cannot initialize " + AggregatingUserAdmin.class, e);
-               }
-       }
-
-       @Override
-       public Role createRole(String name, int type) {
-               return findUserAdmin(name).createRole(name, type);
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
-               systemRoles.removeRole(name);
-               return actuallyDeleted;
-       }
-
-       @Override
-       public Role getRole(String name) {
-               return findUserAdmin(name).getRole(name);
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               List<Role> res = new ArrayList<Role>();
-               for (UserAdmin userAdmin : businessRoles.values()) {
-                       res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
-               }
-               res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
-               return res.toArray(new Role[res.size()]);
-       }
-
-       @Override
-       public User getUser(String key, String value) {
-               List<User> res = new ArrayList<User>();
-               for (UserAdmin userAdmin : businessRoles.values()) {
-                               User u = userAdmin.getUser(key, value);
-                               if (u != null)
-                                       res.add(u);
-               }
-               // Note: node roles cannot contain users, so it is not searched
-               return res.size() == 1 ? res.get(0) : null;
-       }
-
-       @Override
-       public Authorization getAuthorization(User user) {
-               if (user == null) {// anonymous
-                       return systemRoles.getAuthorization(null);
-               }
-               AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
-               Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
-               String usernameToUse;
-               String displayNameToUse;
-               if (user instanceof Group) {
-                       // TODO check whether this is still working
-                       String ownerDn = TokenUtils.userDn((Group) user);
-                       if (ownerDn != null) {// tokens
-                               UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
-                               User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
-                               usernameToUse = ownerDn;
-                               displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
-                       } else {
-                               usernameToUse = rawAuthorization.getName();
-                               displayNameToUse = rawAuthorization.toString();
-                       }
-               } else {// regular users
-                       usernameToUse = rawAuthorization.getName();
-                       displayNameToUse = rawAuthorization.toString();
-               }
-
-               // gather roles from other referentials
-               final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
-               if (user instanceof DirectoryUser) {
-                       userAdminToUse = userReferentialOfThisUser;
-               } else if (user instanceof AuthenticatingUser) {
-                       userAdminToUse = userReferentialOfThisUser.scope(user);
-               } else {
-                       throw new IllegalArgumentException("Unsupported user type " + user.getClass());
-               }
-
-               try {
-                       Set<String> sysRoles = new HashSet<String>();
-                       for (String role : rawAuthorization.getRoles()) {
-                               User userOrGroup = (User) userAdminToUse.getRole(role);
-                               Authorization auth = systemRoles.getAuthorization(userOrGroup);
-                               systemRoles: for (String systemRole : auth.getRoles()) {
-                                       if (role.equals(systemRole))
-                                               continue systemRoles;
-                                       sysRoles.add(systemRole);
-                               }
-//                     sysRoles.addAll(Arrays.asList(auth.getRoles()));
-                       }
-                       addAbstractSystemRoles(rawAuthorization, sysRoles);
-                       Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
-                                       rawAuthorization.getRoles());
-                       return authorization;
-               } finally {
-                       if (userAdminToUse != null && userAdminToUse.isScoped()) {
-                               userAdminToUse.destroy();
-                       }
-               }
-       }
-
-       /**
-        * Enrich with application-specific roles which are strictly programmatic, such
-        * as anonymous/user semantics.
-        */
-       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-
-       }
-
-       //
-       // USER ADMIN AGGREGATOR
-       //
-       protected void addUserDirectory(AbstractUserDirectory userDirectory) {
-               LdapName baseDn = userDirectory.getBaseDn();
-               if (isSystemRolesBaseDn(baseDn)) {
-                       this.systemRoles = userDirectory;
-                       systemRoles.setExternalRoles(this);
-               } else if (isTokensBaseDn(baseDn)) {
-                       this.tokens = userDirectory;
-                       tokens.setExternalRoles(this);
-               } else {
-                       if (businessRoles.containsKey(baseDn))
-                               throw new UserDirectoryException("There is already a user admin for " + baseDn);
-                       businessRoles.put(baseDn, userDirectory);
-               }
-               userDirectory.init();
-               postAdd(userDirectory);
-       }
-
-       /** Called after a new user directory has been added */
-       protected void postAdd(AbstractUserDirectory userDirectory) {
-       }
-
-//     private UserAdmin findUserAdmin(User user) {
-//             if (user == null)
-//                     throw new IllegalArgumentException("User should not be null");
-//             AbstractUserDirectory userAdmin = findUserAdmin(user.getName());
-//             if (user instanceof DirectoryUser) {
-//                     return userAdmin;
-//             } else {
-//                     return userAdmin.scope(user);
-//             }
-//     }
-
-       private AbstractUserDirectory findUserAdmin(String name) {
-               try {
-                       return findUserAdmin(new LdapName(name));
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formatted name " + name, e);
-               }
-       }
-
-       private AbstractUserDirectory findUserAdmin(LdapName name) {
-               if (name.startsWith(systemRolesBaseDn))
-                       return systemRoles;
-               if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
-                       return tokens;
-               List<AbstractUserDirectory> res = new ArrayList<>(1);
-               userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(baseDn);
-                       if (name.startsWith(baseDn)) {
-                               if (userDirectory.isDisabled())
-                                       continue userDirectories;
-//                             if (res.isEmpty()) {
-                               res.add(userDirectory);
-//                             } else {
-//                                     for (AbstractUserDirectory ud : res) {
-//                                             LdapName bd = ud.getBaseDn();
-//                                             if (userDirectory.getBaseDn().startsWith(bd)) {
-//                                                     // child user directory
-//                                             }
-//                                     }
-//                             }
-                       }
-               }
-               if (res.size() == 0)
-                       throw new UserDirectoryException("Cannot find user admin for " + name);
-               if (res.size() > 1)
-                       throw new UserDirectoryException("Multiple user admin found for " + name);
-               return res.get(0);
-       }
-
-       protected boolean isSystemRolesBaseDn(LdapName baseDn) {
-               return baseDn.equals(systemRolesBaseDn);
-       }
-
-       protected boolean isTokensBaseDn(LdapName baseDn) {
-               return tokensBaseDn != null && baseDn.equals(tokensBaseDn);
-       }
-
-//     protected Dictionary<String, Object> currentState() {
-//             Dictionary<String, Object> res = new Hashtable<String, Object>();
-//             // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
-//             for (LdapName name : businessRoles.keySet()) {
-//                     AbstractUserDirectory userDirectory = businessRoles.get(name);
-//                     String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
-//                     res.put(uri, "");
-//             }
-//             return res;
-//     }
-
-       public void destroy() {
-               for (LdapName name : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(name);
-                       destroy(userDirectory);
-               }
-               businessRoles.clear();
-               businessRoles = null;
-               destroy(systemRoles);
-               systemRoles = null;
-       }
-
-       private void destroy(AbstractUserDirectory userDirectory) {
-               preDestroy(userDirectory);
-               userDirectory.destroy();
-       }
-
-       protected void removeUserDirectory(LdapName baseDn) {
-               if (isSystemRolesBaseDn(baseDn))
-                       throw new UserDirectoryException("System roles cannot be removed ");
-               if (!businessRoles.containsKey(baseDn))
-                       throw new UserDirectoryException("No user directory registered for " + baseDn);
-               AbstractUserDirectory userDirectory = businessRoles.remove(baseDn);
-               destroy(userDirectory);
-       }
-
-       /**
-        * Called before each user directory is destroyed, so that additional actions
-        * can be performed.
-        */
-       protected void preDestroy(AbstractUserDirectory userDirectory) {
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java
deleted file mode 100644 (file)
index 01db8be..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/**
- * A special user type used during authentication in order to provide the
- * credentials required for scoping the user admin.
- */
-public class AuthenticatingUser implements User {
-       /** From com.sun.security.auth.module.*LoginModule */
-       public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
-       /** From com.sun.security.auth.module.*LoginModule */
-       public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
-
-       private final String name;
-       private final Dictionary<String, Object> credentials;
-
-       public AuthenticatingUser(LdapName name) {
-               if (name == null)
-                       throw new NullPointerException("Provided name cannot be null.");
-               this.name = name.toString();
-               this.credentials = new Hashtable<>();
-       }
-
-       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
-               this.name = name;
-               this.credentials = credentials;
-       }
-
-       public AuthenticatingUser(String name, char[] password) {
-               if (name == null)
-                       throw new NullPointerException("Provided name cannot be null.");
-               this.name = name;
-               credentials = new Hashtable<>();
-               credentials.put(SHARED_STATE_NAME, name);
-               byte[] pwd = DigestUtils.charsToBytes(password);
-               credentials.put(SHARED_STATE_PWD, pwd);
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int getType() {
-               return User.USER;
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       @Override
-       public Dictionary getProperties() {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       @Override
-       public Dictionary getCredentials() {
-               return credentials;
-       }
-
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return "Authenticating user " + name;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java
deleted file mode 100644 (file)
index 511c2fe..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/** Utilities around digests, mostly those related to passwords. */
-class DigestUtils {
-       final static String PASSWORD_SCHEME_SHA = "SHA";
-       final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
-
-       static byte[] sha1(byte[] bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance("SHA1");
-                       digest.update(bytes);
-                       byte[] checksum = digest.digest();
-                       return checksum;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot SHA1 digest", e);
-               }
-       }
-
-       static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
-                       Integer keyLength) {
-               try {
-                       if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
-                               MessageDigest digest = MessageDigest.getInstance("SHA1");
-                               byte[] bytes = charsToBytes(password);
-                               digest.update(bytes);
-                               return digest.digest();
-                       } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
-                               KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
-
-                               SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
-                               final int ITERATION_LENGTH = 4;
-                               byte[] key = f.generateSecret(spec).getEncoded();
-                               byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
-                               byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
-                               if (iterationsArr.length < ITERATION_LENGTH) {
-                                       Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
-                                       System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
-                                                       iterationsArr.length);
-                               } else {
-                                       System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
-                               }
-                               System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
-                               System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
-                               return result;
-                       } else {
-                               throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot digest", e);
-               }
-       }
-
-       static char[] bytesToChars(Object obj) {
-               if (obj instanceof char[])
-                       return (char[]) obj;
-               if (!(obj instanceof byte[]))
-                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
-               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
-               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
-               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
-               // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
-               // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
-               // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
-               return res;
-       }
-
-       static byte[] charsToBytes(char[] chars) {
-               CharBuffer charBuffer = CharBuffer.wrap(chars);
-               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
-               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
-               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
-               // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
-               return bytes;
-       }
-
-       static String sha1str(String str) {
-               byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
-               return encodeHexString(hash);
-       }
-
-       final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
-       /**
-        * From
-        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
-        * -a-hex-string-in-java
-        */
-       public static String encodeHexString(byte[] bytes) {
-               char[] hexChars = new char[bytes.length * 2];
-               for (int j = 0; j < bytes.length; j++) {
-                       int v = bytes[j] & 0xFF;
-                       hexChars[j * 2] = hexArray[v >>> 4];
-                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
-               }
-               return new String(hexChars);
-       }
-
-       private DigestUtils() {
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java
deleted file mode 100644 (file)
index 7f80463..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.List;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Group;
-
-/** A group in a user directroy. */
-interface DirectoryGroup extends Group, DirectoryUser {
-       List<LdapName> getMemberNames();
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java
deleted file mode 100644 (file)
index 146b805..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/** A user in a user directory. */
-interface DirectoryUser extends User {
-       LdapName getDn();
-
-       Attributes getAttributes();
-
-       void publishAttributes(Attributes modifiedAttributes);
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java
deleted file mode 100644 (file)
index a9bc941..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.DnsBrowser;
-import org.argeo.util.naming.LdapAttrs;
-
-/** Free IPA specific conventions. */
-public class IpaUtils {
-       public final static String IPA_USER_BASE = "cn=users,cn=accounts";
-       public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
-       public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
-
-       private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
-
-       public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
-                       + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
-
-       @Deprecated
-       static String domainToUserDirectoryConfigPath(String realm) {
-               return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
-       }
-
-       public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
-               properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
-               properties.put(UserAdminConf.realm.name(), realm);
-               properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
-               properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
-               properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
-       }
-
-       public static String domainToBaseDn(String domain) {
-               String[] dcs = domain.split("\\.");
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < dcs.length; i++) {
-                       if (i != 0)
-                               sb.append(',');
-                       String dc = dcs[i];
-                       sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
-               }
-               return sb.toString();
-       }
-
-       public static LdapName kerberosToDn(String kerberosName) {
-               String[] kname = kerberosName.split("@");
-               String username = kname[0];
-               String baseDn = domainToBaseDn(kname[1]);
-               String dn;
-               if (!username.contains("/"))
-                       dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
-               else
-                       dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
-               try {
-                       return new LdapName(dn);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
-               }
-       }
-
-       private IpaUtils() {
-
-       }
-
-       public static String kerberosDomainFromDns() {
-               String kerberosDomain;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       String hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       return kerberosDomain;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
-               }
-
-       }
-
-       public static Dictionary<String, Object> convertIpaUri(URI uri) {
-               String path = uri.getPath();
-               String kerberosRealm;
-               if (path == null || path.length() <= 1) {
-                       kerberosRealm = kerberosDomainFromDns();
-               } else {
-                       kerberosRealm = path.substring(1);
-               }
-
-               if (kerberosRealm == null)
-                       throw new UserDirectoryException("No Kerberos domain available for " + uri);
-               // TODO intergrate CA certificate in truststore
-               // String schemeToUse = SCHEME_LDAPS;
-               String schemeToUse = UserAdminConf.SCHEME_LDAP;
-               List<String> ldapHosts;
-               String ldapHostsStr = uri.getHost();
-               if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
-                       try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                               ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
-                                               schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
-                               if (ldapHosts == null || ldapHosts.size() == 0) {
-                                       throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
-                               } else {
-                                       ldapHostsStr = ldapHosts.get(0);
-                               }
-                       } catch (NamingException | IOException e) {
-                               throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
-                       }
-               } else {
-                       ldapHosts = new ArrayList<>();
-                       ldapHosts.add(ldapHostsStr);
-               }
-
-               StringBuilder uriStr = new StringBuilder();
-               try {
-                       for (String host : ldapHosts) {
-                               URI convertedUri = new URI(schemeToUse + "://" + host + "/");
-                               uriStr.append(convertedUri).append(' ');
-                       }
-               } catch (URISyntaxException e) {
-                       throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
-               }
-
-               Hashtable<String, Object> res = new Hashtable<>();
-               res.put(UserAdminConf.uri.name(), uriStr.toString());
-               addIpaConfig(kerberosRealm, res);
-               return res;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java
deleted file mode 100644 (file)
index ed69eb1..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.CommunicationException;
-import javax.naming.Context;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-
-/** A synchronized wrapper for a single {@link InitialLdapContext}. */
-// TODO implement multiple contexts and connection pooling.
-class LdapConnection {
-       private InitialLdapContext initialLdapContext = null;
-
-       LdapConnection(String url, Dictionary<String, ?> properties) {
-               try {
-                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
-                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
-                       connEnv.put(Context.PROVIDER_URL, url);
-                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
-                       // use pooling in order to avoid connection timeout
-//                     connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
-//                     connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
-
-                       initialLdapContext = new InitialLdapContext(connEnv, null);
-                       // StartTlsResponse tls = (StartTlsResponse) ctx
-                       // .extendedOperation(new StartTlsRequest());
-                       // tls.negotiate();
-                       Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
-                       if (securityAuthentication != null)
-                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
-                       else
-                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
-                       Object principal = properties.get(Context.SECURITY_PRINCIPAL);
-                       if (principal != null) {
-                               initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
-                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
-                               if (creds != null) {
-                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot connect to LDAP", e);
-               }
-
-       }
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-               try {
-                       // tls.close();
-                       initialLdapContext.close();
-                       initialLdapContext = null;
-               } catch (NamingException e) {
-                       e.printStackTrace();
-               }
-       }
-
-       protected InitialLdapContext getLdapContext() {
-               return initialLdapContext;
-       }
-
-       protected void reconnect() throws NamingException {
-               initialLdapContext.reconnect(initialLdapContext.getConnectControls());
-       }
-
-       public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
-                       SearchControls searchControls) throws NamingException {
-               NamingEnumeration<SearchResult> results;
-               try {
-                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
-               } catch (CommunicationException e) {
-                       reconnect();
-                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
-               }
-               return results;
-       }
-
-       public synchronized Attributes getAttributes(LdapName name) throws NamingException {
-               try {
-                       return getLdapContext().getAttributes(name);
-               } catch (CommunicationException e) {
-                       reconnect();
-                       return getLdapContext().getAttributes(name);
-               }
-       }
-
-       synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
-               // make sure connection will work
-               reconnect();
-
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       if (!entryExists(dn))
-                               throw new UserDirectoryException("User to delete no found " + dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       if (entryExists(dn))
-                               throw new UserDirectoryException("User to create found " + dn);
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
-                               throw new UserDirectoryException("User to modify not found " + dn);
-               }
-
-       }
-
-       protected boolean entryExists(LdapName dn) throws NamingException {
-               try {
-                       return getAttributes(dn).size() != 0;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
-       synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       getLdapContext().destroySubcontext(dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
-                       getLdapContext().createSubcontext(dn, user.getAttributes());
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
-                       getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
-               }
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
deleted file mode 100644 (file)
index f839608..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.AuthenticationNotSupportedException;
-import javax.naming.Binding;
-import javax.naming.Context;
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDAP server. */
-public class LdapUserAdmin extends AbstractUserDirectory {
-       private LdapConnection ldapConnection;
-
-       public LdapUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-               ldapConnection = new LdapConnection(getUri().toString(), properties);
-       }
-
-       public void destroy() {
-               ldapConnection.destroy();
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DigestUtils.bytesToChars(pwd);
-                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
-               } else {
-                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
-               }
-               return new LdapUserAdmin(properties, true);
-       }
-
-//     protected InitialLdapContext getLdapContext() {
-//             return initialLdapContext;
-//     }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               try {
-                       return daoGetRole(dn) != null;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
-               try {
-                       Attributes attrs = ldapConnection.getAttributes(name);
-                       if (attrs.size() == 0)
-                               return null;
-                       int roleType = roleType(name);
-                       LdifUser res;
-                       if (roleType == Role.GROUP)
-                               res = new LdifGroup(this, name, attrs);
-                       else if (roleType == Role.USER)
-                               res = new LdifUser(this, name, attrs);
-                       else
-                               throw new UserDirectoryException("Unsupported LDAP type for " + name);
-                       return res;
-               } catch (NameNotFoundException e) {
-                       throw e;
-               } catch (NamingException e) {
-                       return null;
-               }
-       }
-
-       @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
-               try {
-                       String searchFilter = f != null ? f.toString()
-                                       : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
-                                                       + getGroupObjectClass() + "))";
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                       LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       results: while (results.hasMoreElements()) {
-                               SearchResult searchResult = results.next();
-                               Attributes attrs = searchResult.getAttributes();
-                               Attribute objectClassAttr = attrs.get(objectClass.name());
-                               LdapName dn = toDn(searchBase, searchResult);
-                               LdifUser role;
-                               if (objectClassAttr.contains(getGroupObjectClass())
-                                               || objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
-                                       role = new LdifGroup(this, dn, attrs);
-                               else if (objectClassAttr.contains(getUserObjectClass())
-                                               || objectClassAttr.contains(getUserObjectClass().toLowerCase()))
-                                       role = new LdifUser(this, dn, attrs);
-                               else {
-//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
-                                       continue results;
-                               }
-                               res.add(role);
-                       }
-                       return res;
-               } catch (AuthenticationNotSupportedException e) {
-                       // ignore (typically an unsupported anonymous bind)
-                       // TODO better logging
-                       return res;
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new UserDirectoryException("Cannot get roles for filter " + f, e);
-               }
-       }
-
-       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
-               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               try {
-                       String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
-                                       + "=" + dn + "))";
-
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                       LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       while (results.hasMoreElements()) {
-                               SearchResult searchResult = (SearchResult) results.nextElement();
-                               directGroups.add(toDn(searchBase, searchResult));
-                       }
-                       return directGroups;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot populate direct members of " + dn, e);
-               }
-       }
-
-       @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-               try {
-                       ldapConnection.prepareChanges(wc);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot prepare LDAP", e);
-               }
-       }
-
-       @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
-               try {
-                       ldapConnection.commitChanges(wc);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot commit LDAP", e);
-               }
-       }
-
-       @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-               // prepare not impacting
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java
deleted file mode 100644 (file)
index 15afe08..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.List;
-
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** Basic authorization. */
-class LdifAuthorization implements Authorization {
-       private final String name;
-       private final String displayName;
-       private final List<String> allRoles;
-
-       public LdifAuthorization(User user, List<Role> allRoles) {
-               if (user == null) {
-                       this.name = null;
-                       this.displayName = "anonymous";
-               } else {
-                       this.name = user.getName();
-                       this.displayName = extractDisplayName(user);
-               }
-               // roles
-               String[] roles = new String[allRoles.size()];
-               for (int i = 0; i < allRoles.size(); i++) {
-                       roles[i] = allRoles.get(i).getName();
-               }
-               this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               return allRoles.contains(name);
-       }
-
-       @Override
-       public String[] getRoles() {
-               return allRoles.toArray(new String[allRoles.size()]);
-       }
-
-       @Override
-       public int hashCode() {
-               if (name == null)
-                       return super.hashCode();
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof Authorization))
-                       return false;
-               Authorization that = (Authorization) obj;
-               if (name == null)
-                       return that.getName() == null;
-               return name.equals(that.getName());
-       }
-
-       @Override
-       public String toString() {
-               return displayName;
-       }
-
-       final static String extractDisplayName(User user) {
-               Dictionary<String, Object> props = user.getProperties();
-               Object displayName = props.get(LdapAttrs.displayName);
-               if (displayName == null)
-                       displayName = props.get(LdapAttrs.cn);
-               if (displayName == null)
-                       displayName = props.get(LdapAttrs.uid);
-               if (displayName == null)
-                       displayName = user.getName();
-               if (displayName == null)
-                       throw new UserDirectoryException("Cannot set display name for " + user);
-               return displayName.toString();
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java
deleted file mode 100644 (file)
index f4e5583..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Role;
-
-/** Directory group implementation */
-class LdifGroup extends LdifUser implements DirectoryGroup {
-       private final String memberAttributeId;
-
-       LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
-               super(userAdmin, dn, attributes);
-               memberAttributeId = userAdmin.getMemberAttributeId();
-       }
-
-       @Override
-       public boolean addMember(Role role) {
-               try {
-                       Role foundRole = findRole(new LdapName(role.getName()));
-                       if (foundRole == null)
-                               throw new UnsupportedOperationException(
-                                               "Adding role " + role.getName() + " is unsupported within this context.");
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
-               }
-
-               getUserAdmin().checkEdit();
-               if (!isEditing())
-                       startEditing();
-
-               Attribute member = getAttributes().get(memberAttributeId);
-               if (member != null) {
-                       if (member.contains(role.getName()))
-                               return false;
-                       else
-                               member.add(role.getName());
-               } else
-                       getAttributes().put(memberAttributeId, role.getName());
-               return true;
-       }
-
-       @Override
-       public boolean addRequiredMember(Role role) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public boolean removeMember(Role role) {
-               getUserAdmin().checkEdit();
-               if (!isEditing())
-                       startEditing();
-
-               Attribute member = getAttributes().get(memberAttributeId);
-               if (member != null) {
-                       if (!member.contains(role.getName()))
-                               return false;
-                       member.remove(role.getName());
-                       return true;
-               } else
-                       return false;
-       }
-
-       @Override
-       public Role[] getMembers() {
-               List<Role> directMembers = new ArrayList<Role>();
-               for (LdapName ldapName : getMemberNames()) {
-                       Role role = findRole(ldapName);
-                       if (role == null) {
-                               throw new UserDirectoryException("Role " + ldapName + " cannot be added.");
-                       }
-                       directMembers.add(role);
-               }
-               return directMembers.toArray(new Role[directMembers.size()]);
-       }
-
-       /**
-        * Whether a role with this name can be found from this context.
-        * 
-        * @return The related {@link Role} or <code>null</code>.
-        */
-       protected Role findRole(LdapName ldapName) {
-               Role role = getUserAdmin().getRole(ldapName.toString());
-               if (role == null) {
-                       if (getUserAdmin().getExternalRoles() != null)
-                               role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
-               }
-               return role;
-       }
-
-       @Override
-       public List<LdapName> getMemberNames() {
-               Attribute memberAttribute = getAttributes().get(memberAttributeId);
-               if (memberAttribute == null)
-                       return new ArrayList<LdapName>();
-               try {
-                       List<LdapName> roles = new ArrayList<LdapName>();
-                       NamingEnumeration<?> values = memberAttribute.getAll();
-                       while (values.hasMore()) {
-                               LdapName dn = new LdapName(values.next().toString());
-                               roles.add(dn);
-                       }
-                       return roles;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot get members", e);
-               }
-       }
-
-       @Override
-       public Role[] getRequiredMembers() {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public int getType() {
-               return GROUP;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java
deleted file mode 100644 (file)
index 135645a..0000000
+++ /dev/null
@@ -1,413 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.AuthPassword;
-import org.argeo.util.naming.LdapAttrs;
-import org.argeo.util.naming.SharedSecret;
-
-/** Directory user implementation */
-class LdifUser implements DirectoryUser {
-       private final AbstractUserDirectory userAdmin;
-
-       private final LdapName dn;
-
-       private final boolean frozen;
-       private Attributes publishedAttributes;
-
-       private final AttributeDictionary properties;
-       private final AttributeDictionary credentials;
-
-       LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
-               this(userAdmin, dn, attributes, false);
-       }
-
-       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
-               this.userAdmin = userAdmin;
-               this.dn = dn;
-               this.publishedAttributes = attributes;
-               properties = new AttributeDictionary(false);
-               credentials = new AttributeDictionary(true);
-               this.frozen = frozen;
-       }
-
-       @Override
-       public String getName() {
-               return dn.toString();
-       }
-
-       @Override
-       public int getType() {
-               return USER;
-       }
-
-       @Override
-       public Dictionary<String, Object> getProperties() {
-               return properties;
-       }
-
-       @Override
-       public Dictionary<String, Object> getCredentials() {
-               return credentials;
-       }
-
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               if (key == null) {
-                       // TODO check other sources (like PKCS12)
-                       // String pwd = new String((char[]) value);
-                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
-                       char[] password = DigestUtils.bytesToChars(value);
-
-                       if (userAdmin.getForcedPassword() != null && userAdmin.getForcedPassword().equals(new String(password)))
-                               return true;
-
-                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
-                       if (authPassword != null) {
-                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
-                                       SharedSecret onceToken = new SharedSecret(authPassword);
-                                       if (onceToken.isExpired()) {
-                                               // AuthPassword.remove(getAttributes(), onceToken);
-                                               return false;
-                                       } else {
-                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
-                                               return true;
-                                       }
-                                       // TODO delete expired tokens?
-                               } else {
-                                       // TODO implement SHA
-                                       throw new UnsupportedOperationException(
-                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
-                               }
-                       }
-
-                       // Regular password
-//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
-                       if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
-                               return true;
-                       return false;
-               }
-
-               Object storedValue = getCredentials().get(key);
-               if (storedValue == null || value == null)
-                       return false;
-               if (!(value instanceof String || value instanceof byte[]))
-                       return false;
-               if (storedValue instanceof String && value instanceof String)
-                       return storedValue.equals(value);
-               if (storedValue instanceof byte[] && value instanceof byte[]) {
-                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
-                       String passwordScheme = null;
-                       if (storedBase64.charAt(0) == '{') {
-                               int index = storedBase64.indexOf('}');
-                               if (index > 0) {
-                                       passwordScheme = storedBase64.substring(1, index);
-                                       String storedValueBase64 = storedBase64.substring(index + 1);
-                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
-                                       char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
-                                       byte[] valueBytes;
-                                       if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
-                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
-                                       } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
-                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
-                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
-                                               BigInteger iterations = new BigInteger(iterationsArr);
-                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
-                                                               iterationsArr.length + 64);
-                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
-                                                               storedValueBytes.length);
-                                               int keyLengthBits = keyArr.length * 8;
-                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
-                                                               iterations.intValue(), keyLengthBits);
-                                       } else {
-                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
-                                       }
-                                       return Arrays.equals(storedValueBytes, valueBytes);
-                               }
-                       }
-               }
-//             if (storedValue instanceof byte[] && value instanceof byte[]) {
-//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
-//             }
-               return false;
-       }
-
-       /** Hash the password */
-       byte[] sha1hash(char[] password) {
-               byte[] hashedPassword = ("{SHA}"
-                               + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
-                                               .getBytes(StandardCharsets.UTF_8);
-               return hashedPassword;
-       }
-
-//     byte[] hash(char[] password, String passwordScheme) {
-//             if (passwordScheme == null)
-//                     passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
-//             byte[] hashedPassword = ("{" + passwordScheme + "}"
-//                             + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
-//                                             .getBytes(US_ASCII);
-//             return hashedPassword;
-//     }
-
-       @Override
-       public LdapName getDn() {
-               return dn;
-       }
-
-       @Override
-       public synchronized Attributes getAttributes() {
-               return isEditing() ? getModifiedAttributes() : publishedAttributes;
-       }
-
-       /** Should only be called from working copy thread. */
-       private synchronized Attributes getModifiedAttributes() {
-               assert getWc() != null;
-               return getWc().getAttributes(getDn());
-       }
-
-       protected synchronized boolean isEditing() {
-               return getWc() != null && getModifiedAttributes() != null;
-       }
-
-       private synchronized UserDirectoryWorkingCopy getWc() {
-               return userAdmin.getWorkingCopy();
-       }
-
-       protected synchronized void startEditing() {
-               if (frozen)
-                       throw new UserDirectoryException("Cannot edit frozen view");
-               if (getUserAdmin().isReadOnly())
-                       throw new UserDirectoryException("User directory is read-only");
-               assert getModifiedAttributes() == null;
-               getWc().startEditing(this);
-               // modifiedAttributes = (Attributes) publishedAttributes.clone();
-       }
-
-       public synchronized void publishAttributes(Attributes modifiedAttributes) {
-               publishedAttributes = modifiedAttributes;
-       }
-
-       public DirectoryUser getPublished() {
-               return new LdifUser(userAdmin, dn, publishedAttributes, true);
-       }
-
-       @Override
-       public int hashCode() {
-               return dn.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (this == obj)
-                       return true;
-               if (obj instanceof LdifUser) {
-                       LdifUser that = (LdifUser) obj;
-                       return this.dn.equals(that.dn);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return dn.toString();
-       }
-
-       protected AbstractUserDirectory getUserAdmin() {
-               return userAdmin;
-       }
-
-       private class AttributeDictionary extends Dictionary<String, Object> {
-               private final List<String> effectiveKeys = new ArrayList<String>();
-               private final List<String> attrFilter;
-               private final Boolean includeFilter;
-
-               public AttributeDictionary(Boolean includeFilter) {
-                       this.attrFilter = userAdmin.getCredentialAttributeIds();
-                       this.includeFilter = includeFilter;
-                       try {
-                               NamingEnumeration<String> ids = getAttributes().getIDs();
-                               while (ids.hasMore()) {
-                                       String id = ids.next();
-                                       if (includeFilter && attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                                       else if (!includeFilter && !attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                               }
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot initialise attribute dictionary", e);
-                       }
-               }
-
-               @Override
-               public int size() {
-                       return effectiveKeys.size();
-               }
-
-               @Override
-               public boolean isEmpty() {
-                       return effectiveKeys.size() == 0;
-               }
-
-               @Override
-               public Enumeration<String> keys() {
-                       return Collections.enumeration(effectiveKeys);
-               }
-
-               @Override
-               public Enumeration<Object> elements() {
-                       final Iterator<String> it = effectiveKeys.iterator();
-                       return new Enumeration<Object>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public Object nextElement() {
-                                       String key = it.next();
-                                       return get(key);
-                               }
-
-                       };
-               }
-
-               @Override
-               public Object get(Object key) {
-                       try {
-                               Attribute attr = getAttributes().get(key.toString());
-                               if (attr == null)
-                                       return null;
-                               Object value = attr.get();
-                               if (value instanceof byte[]) {
-                                       if (key.equals(LdapAttrs.userPassword.name()))
-                                               // TODO other cases (certificates, images)
-                                               return value;
-                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
-                               }
-                               if (attr.size() == 1)
-                                       return value;
-                               if (!attr.getID().equals(LdapAttrs.objectClass.name()))
-                                       return value;
-                               // special case for object class
-                               NamingEnumeration<?> en = attr.getAll();
-                               Set<String> objectClasses = new HashSet<String>();
-                               while (en.hasMore()) {
-                                       String objectClass = en.next().toString();
-                                       objectClasses.add(objectClass);
-                               }
-
-                               if (objectClasses.contains(userAdmin.getUserObjectClass()))
-                                       return userAdmin.getUserObjectClass();
-                               else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
-                                       return userAdmin.getGroupObjectClass();
-                               else
-                                       return value;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       if (key == null) {
-                               // TODO persist to other sources (like PKCS12)
-                               char[] password = DigestUtils.bytesToChars(value);
-                               byte[] hashedPassword = sha1hash(password);
-                               return put(LdapAttrs.userPassword.name(), hashedPassword);
-                       }
-                       if (key.startsWith("X-")) {
-                               return put(LdapAttrs.authPassword.name(), value);
-                       }
-
-                       userAdmin.checkEdit();
-                       if (!isEditing())
-                               startEditing();
-
-                       if (!(value instanceof String || value instanceof byte[]))
-                               throw new IllegalArgumentException("Value must be String or byte[]");
-
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
-
-                       try {
-                               Attribute attribute = getModifiedAttributes().get(key.toString());
-                               // if (attribute == null) // block unit tests
-                               attribute = new BasicAttribute(key.toString());
-                               if (value instanceof String && !isAsciiPrintable(((String) value)))
-                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
-                               else
-                                       attribute.add(value);
-                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
-                               if (previousAttribute != null)
-                                       return previousAttribute.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object remove(Object key) {
-                       userAdmin.checkEdit();
-                       if (!isEditing())
-                               startEditing();
-
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
-
-                       try {
-                               Attribute attr = getModifiedAttributes().remove(key.toString());
-                               if (attr != null)
-                                       return attr.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot remove attribute " + key, e);
-                       }
-               }
-       }
-
-       private static boolean isAsciiPrintable(String str) {
-               if (str == null) {
-                       return false;
-               }
-               int sz = str.length();
-               for (int i = 0; i < sz; i++) {
-                       if (isAsciiPrintable(str.charAt(i)) == false) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       private static boolean isAsciiPrintable(char ch) {
-               return ch >= 32 && ch < 127;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
deleted file mode 100644 (file)
index 8b1206a..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdifParser;
-import org.argeo.util.naming.LdifWriter;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** A user admin based on a LDIF files. */
-public class LdifUserAdmin extends AbstractUserDirectory {
-       private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
-       private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
-
-       public LdifUserAdmin(String uri, String baseDn) {
-               this(fromUri(uri, baseDn), false);
-       }
-
-       public LdifUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-       }
-
-       public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
-               super(uri, properties, false);
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DigestUtils.bytesToChars(pwd);
-                       User directoryUser = (User) getRole(username);
-                       if (!directoryUser.hasCredential(null, password))
-                               throw new UserDirectoryException("Invalid credentials");
-               } else {
-                       throw new UserDirectoryException("Password is required");
-               }
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(UserAdminConf.readOnly.name(), "true");
-               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
-               scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
-               scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
-               return scopedUserAdmin;
-       }
-
-       private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
-               Hashtable<String, Object> res = new Hashtable<String, Object>();
-               res.put(UserAdminConf.uri.name(), uri);
-               res.put(UserAdminConf.baseDn.name(), baseDn);
-               return res;
-       }
-
-       public void init() {
-
-               try {
-                       URI u = new URI(getUri());
-                       if (u.getScheme().equals("file")) {
-                               File file = new File(u);
-                               if (!file.exists())
-                                       return;
-                       }
-                       load(u.toURL().openStream());
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot open URL " + getUri(), e);
-               }
-       }
-
-       public void save() {
-               if (getUri() == null)
-                       throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
-               if (isReadOnly())
-                       throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
-               try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
-                       save(out);
-               } catch (IOException | URISyntaxException e) {
-                       throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
-               }
-       }
-
-       public void save(OutputStream out) throws IOException {
-               try {
-                       LdifWriter ldifWriter = new LdifWriter(out);
-                       for (LdapName name : groups.keySet())
-                               ldifWriter.writeEntry(name, groups.get(name).getAttributes());
-                       for (LdapName name : users.keySet())
-                               ldifWriter.writeEntry(name, users.get(name).getAttributes());
-               } finally {
-                       out.close();
-               }
-       }
-
-       protected void load(InputStream in) {
-               try {
-                       users.clear();
-                       groups.clear();
-
-                       LdifParser ldifParser = new LdifParser();
-                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
-                       for (LdapName key : allEntries.keySet()) {
-                               Attributes attributes = allEntries.get(key);
-                               // check for inconsistency
-                               Set<String> lowerCase = new HashSet<String>();
-                               NamingEnumeration<String> ids = attributes.getIDs();
-                               while (ids.hasMoreElements()) {
-                                       String id = ids.nextElement().toLowerCase();
-                                       if (lowerCase.contains(id))
-                                               throw new UserDirectoryException(key + " has duplicate id " + id);
-                                       lowerCase.add(id);
-                               }
-
-                               // analyse object classes
-                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
-                               // System.out.println(key);
-                               objectClasses: while (objectClasses.hasMore()) {
-                                       String objectClass = objectClasses.next().toString();
-                                       // System.out.println(" " + objectClass);
-                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
-                                               users.put(key, new LdifUser(this, key, attributes));
-                                               break objectClasses;
-                                       } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
-                                               groups.put(key, new LdifGroup(this, key, attributes));
-                                               break objectClasses;
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
-               }
-       }
-
-       public void destroy() {
-               if (users == null || groups == null)
-                       throw new UserDirectoryException("User directory " + getBaseDn() + " is already destroyed");
-               users = null;
-               groups = null;
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
-               if (groups.containsKey(key))
-                       return groups.get(key);
-               if (users.containsKey(key))
-                       return users.get(key);
-               throw new NameNotFoundException(key + " not persisted");
-       }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               return users.containsKey(dn) || groups.containsKey(dn);
-       }
-
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
-               if (f == null) {
-                       res.addAll(users.values());
-                       res.addAll(groups.values());
-               } else {
-                       for (DirectoryUser user : users.values()) {
-                               if (f.match(user.getProperties()))
-                                       res.add(user);
-                       }
-                       for (DirectoryUser group : groups.values())
-                               if (f.match(group.getProperties()))
-                                       res.add(group);
-               }
-               return res;
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               for (LdapName name : groups.keySet()) {
-                       DirectoryGroup group = groups.get(name);
-                       if (group.getMemberNames().contains(dn))
-                               directGroups.add(group.getDn());
-               }
-               return directGroups;
-       }
-
-       @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       if (users.containsKey(dn))
-                               users.remove(dn);
-                       else if (groups.containsKey(dn))
-                               groups.remove(dn);
-                       else
-                               throw new UserDirectoryException("User to delete not found " + dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
-                       if (users.containsKey(dn) || groups.containsKey(dn))
-                               throw new UserDirectoryException("User to create found " + dn);
-                       else if (Role.USER == user.getType())
-                               users.put(dn, user);
-                       else if (Role.GROUP == user.getType())
-                               groups.put(dn, (DirectoryGroup) user);
-                       else
-                               throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn);
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
-                       DirectoryUser user;
-                       if (users.containsKey(dn))
-                               user = users.get(dn);
-                       else if (groups.containsKey(dn))
-                               user = groups.get(dn);
-                       else
-                               throw new UserDirectoryException("User to modify no found " + dn);
-                       user.publishAttributes(modifiedAttrs);
-               }
-       }
-
-       @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
-               save();
-       }
-
-       @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-               init();
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java
deleted file mode 100644 (file)
index dd16e1a..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.User;
-
-public class OsUserDirectory extends AbstractUserDirectory {
-       private final String osUsername = System.getProperty("user.name");
-       private final LdapName osUserDn;
-       private final LdifUser osUser;
-
-       public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
-               super(uriArg, props, false);
-               try {
-                       osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn());
-                       Attributes attributes = new BasicAttributes();
-                       attributes.put(LdapAttrs.uid.name(), osUsername);
-                       osUser = new LdifUser(this, osUserDn, attributes);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot create system user", e);
-               }
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               return new ArrayList<>();
-       }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               return osUserDn.equals(dn);
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
-               if (osUserDn.equals(key))
-                       return osUser;
-               else
-                       throw new NameNotFoundException("Not an OS role");
-       }
-
-       @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               List<DirectoryUser> res = new ArrayList<>();
-               if (f == null || f.match(osUser.getProperties()))
-                       res.add(osUser);
-               return res;
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               throw new UnsupportedOperationException();
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java
deleted file mode 100644 (file)
index ad6bf88..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.NoSuchAlgorithmException;
-import java.security.URIParameter;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-public class OsUserUtils {
-       private static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
-       private static String LOGIN_CONTEXT_USER_NT = "USER_NT";
-
-       public static String getOsUsername() {
-               return System.getProperty("user.name");
-       }
-
-       public static LoginContext loginAsSystemUser(Subject subject) {
-               try {
-                       URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
-                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
-                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
-                       Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
-                       LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
-                                       null, jaasConfiguration);
-                       lc.login();
-                       return lc;
-               } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
-                       throw new RuntimeException("Cannot login as system user", e);
-               }
-       }
-
-       public static void main(String args[]) {
-               Subject subject = new Subject();
-               LoginContext loginContext = loginAsSystemUser(subject);
-               System.out.println(subject);
-               try {
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       // silent
-               }
-       }
-
-       private static boolean isWindows() {
-               return System.getProperty("os.name").startsWith("Windows");
-       }
-
-       private OsUserUtils() {
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java
deleted file mode 100644 (file)
index 4b00e6c..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.util.naming.LdapAttrs.description;
-import static org.argeo.util.naming.LdapAttrs.owner;
-
-import java.security.Principal;
-import java.time.Instant;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.util.naming.NamingUtils;
-import org.osgi.service.useradmin.Group;
-
-/**
- * Canonically implements the Argeo token conventions.
- */
-public class TokenUtils {
-       public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
-               Set<String> res = new HashSet<>();
-               for (Principal principal : subject.getPrincipals()) {
-                       String name = principal.getName();
-                       if (name.endsWith(tokensBaseDn)) {
-                               try {
-                                       LdapName ldapName = new LdapName(name);
-                                       String token = ldapName.getRdn(ldapName.size()).getValue().toString();
-                                       res.add(token);
-                               } catch (InvalidNameException e) {
-                                       throw new UserDirectoryException("Invalid principal " + principal, e);
-                               }
-                       }
-               }
-               return res;
-       }
-
-       /** The user related to this token group */
-       public static String userDn(Group tokenGroup) {
-               return (String) tokenGroup.getProperties().get(owner.name());
-       }
-
-       public static boolean isExpired(Group tokenGroup) {
-               return isExpired(tokenGroup, Instant.now());
-
-       }
-
-       public static boolean isExpired(Group tokenGroup, Instant instant) {
-               String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
-               if (expiryDateStr != null) {
-                       Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
-                       if (expiryDate.isBefore(instant)) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-//     private final String token;
-//
-//     public TokenUtils(String token) {
-//             this.token = token;
-//     }
-//
-//     public String getToken() {
-//             return token;
-//     }
-//
-//     @Override
-//     public int hashCode() {
-//             return token.hashCode();
-//     }
-//
-//     @Override
-//     public boolean equals(Object obj) {
-//             if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
-//                     return true;
-//             return false;
-//     }
-//
-//     @Override
-//     public String toString() {
-//             return "Token #" + hashCode();
-//     }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java
deleted file mode 100644 (file)
index 3631de4..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.Context;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.util.naming.NamingUtils;
-
-/** Properties used to configure user admins. */
-public enum UserAdminConf {
-       /** Base DN (cannot be configured externally) */
-       baseDn("dc=example,dc=com"),
-
-       /** URI of the underlying resource (cannot be configured externally) */
-       uri("ldap://localhost:10389"),
-
-       /** User objectClass */
-       userObjectClass("inetOrgPerson"),
-
-       /** Relative base DN for users */
-       userBase("ou=People"),
-
-       /** Groups objectClass */
-       groupObjectClass("groupOfNames"),
-
-       /** Relative base DN for users */
-       groupBase("ou=Groups"),
-
-       /** Read-only source */
-       readOnly(null),
-
-       /** Disabled source */
-       disabled(null),
-
-       /** Authentication realm */
-       realm(null),
-
-       /** Override all passwords with this value (typically for testing purposes) */
-       forcedPassword(null);
-
-       public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
-
-       public final static String SCHEME_LDAP = "ldap";
-       public final static String SCHEME_LDAPS = "ldaps";
-       public final static String SCHEME_FILE = "file";
-       public final static String SCHEME_OS = "os";
-       public final static String SCHEME_IPA = "ipa";
-
-       /** The default value. */
-       private Object def;
-
-       UserAdminConf(Object def) {
-               this.def = def;
-       }
-
-       public Object getDefault() {
-               return def;
-       }
-
-       /**
-        * For use as Java property.
-        * 
-        * @deprecated use {@link #name()} instead
-        */
-       @Deprecated
-       public String property() {
-               return name();
-       }
-
-       public String getValue(Dictionary<String, ?> properties) {
-               Object res = getRawValue(properties);
-               if (res == null)
-                       return null;
-               return res.toString();
-       }
-
-       @SuppressWarnings("unchecked")
-       public <T> T getRawValue(Dictionary<String, ?> properties) {
-               Object res = properties.get(name());
-               if (res == null)
-                       res = getDefault();
-               return (T) res;
-       }
-
-       /** @deprecated use {@link #valueOf(String)} instead */
-       @Deprecated
-       public static UserAdminConf local(String property) {
-               return UserAdminConf.valueOf(property);
-       }
-
-       /** Hides host and credentials. */
-       public static URI propertiesAsUri(Dictionary<String, ?> properties) {
-               StringBuilder query = new StringBuilder();
-
-               boolean first = true;
-//             for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
-//                     String key = keys.nextElement();
-//                     // TODO clarify which keys are relevant (list only the enum?)
-//                     if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
-//                                     && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
-//                                     && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
-//                                     && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
-//                             if (first)
-//                                     first = false;
-//                             else
-//                                     query.append('&');
-//                             query.append(valueOf(key).name());
-//                             query.append('=').append(properties.get(key).toString());
-//                     }
-//             }
-
-               keys: for (UserAdminConf key : UserAdminConf.values()) {
-                       if (key.equals(baseDn) || key.equals(uri))
-                               continue keys;
-                       Object value = properties.get(key.name());
-                       if (value == null)
-                               continue keys;
-                       if (first)
-                               first = false;
-                       else
-                               query.append('&');
-                       query.append(key.name());
-                       query.append('=').append(value.toString());
-
-               }
-
-               Object bDnObj = properties.get(baseDn.name());
-               String bDn = bDnObj != null ? bDnObj.toString() : null;
-               try {
-                       return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
-                                       null);
-               } catch (URISyntaxException e) {
-                       throw new UserDirectoryException("Cannot create URI from properties", e);
-               }
-       }
-
-       public static Dictionary<String, Object> uriAsProperties(String uriStr) {
-               try {
-                       Hashtable<String, Object> res = new Hashtable<String, Object>();
-                       URI u = new URI(uriStr);
-                       String scheme = u.getScheme();
-                       if (scheme != null && scheme.equals(SCHEME_IPA)) {
-                               return IpaUtils.convertIpaUri(u);
-//                             scheme = u.getScheme();
-                       }
-                       String path = u.getPath();
-                       // base DN
-                       String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
-                       if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
-                               bDn = getBaseDnFromHostname();
-                       }
-
-                       if (bDn.endsWith(".ldif"))
-                               bDn = bDn.substring(0, bDn.length() - ".ldif".length());
-
-                       // Normalize base DN as LDAP name
-                       bDn = new LdapName(bDn).toString();
-
-                       String principal = null;
-                       String credentials = null;
-                       if (scheme != null)
-                               if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
-                                       // TODO additional checks
-                                       if (u.getUserInfo() != null) {
-                                               String[] userInfo = u.getUserInfo().split(":");
-                                               principal = userInfo.length > 0 ? userInfo[0] : null;
-                                               credentials = userInfo.length > 1 ? userInfo[1] : null;
-                                       }
-                               } else if (scheme.equals(SCHEME_FILE)) {
-                               } else if (scheme.equals(SCHEME_IPA)) {
-                               } else if (scheme.equals(SCHEME_OS)) {
-                               } else
-                                       throw new UserDirectoryException("Unsupported scheme " + scheme);
-                       Map<String, List<String>> query = NamingUtils.queryToMap(u);
-                       for (String key : query.keySet()) {
-                               UserAdminConf ldapProp = UserAdminConf.valueOf(key);
-                               List<String> values = query.get(key);
-                               if (values.size() == 1) {
-                                       res.put(ldapProp.name(), values.get(0));
-                               } else {
-                                       throw new UserDirectoryException("Only single values are supported");
-                               }
-                       }
-                       res.put(baseDn.name(), bDn);
-                       if (SCHEME_OS.equals(scheme))
-                               res.put(readOnly.name(), "true");
-                       if (principal != null)
-                               res.put(Context.SECURITY_PRINCIPAL, principal);
-                       if (credentials != null)
-                               res.put(Context.SECURITY_CREDENTIALS, credentials);
-                       if (scheme != null) {// relative URIs are dealt with externally
-                               if (SCHEME_OS.equals(scheme)) {
-                                       res.put(uri.name(), SCHEME_OS + ":///");
-                               } else {
-                                       URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
-                                                       scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
-                                       res.put(uri.name(), bareUri.toString());
-                               }
-                       }
-                       return res;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot convert " + uri + " to properties", e);
-               }
-       }
-
-       private static String getBaseDnFromHostname() {
-               String hostname;
-               try {
-                       hostname = InetAddress.getLocalHost().getHostName();
-               } catch (UnknownHostException e) {
-                       hostname = "localhost.localdomain";
-               }
-               int dotIdx = hostname.indexOf('.');
-               if (dotIdx >= 0) {
-                       String domain = hostname.substring(dotIdx + 1, hostname.length());
-                       String bDn = ("." + domain).replaceAll("\\.", ",dc=");
-                       bDn = bDn.substring(1, bDn.length());
-                       return bDn;
-               } else {
-                       return "dc=" + hostname;
-               }
-       }
-
-       /**
-        * Hash the base DN in order to have a deterministic string to be used as a cn
-        * for the underlying user directory.
-        */
-       public static String baseDnHash(Dictionary<String, Object> properties) {
-               String bDn = (String) properties.get(baseDn.name());
-               if (bDn == null)
-                       throw new UserDirectoryException("No baseDn in " + properties);
-               return DigestUtils.sha1str(bDn);
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java
deleted file mode 100644 (file)
index ff80c5a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** Information about a user directory. */
-public interface UserDirectory {
-       /** The base DN of all entries in this user directory */
-       LdapName getBaseDn();
-
-       /** The related {@link XAResource} */
-       XAResource getXaResource();
-
-       boolean isReadOnly();
-
-       boolean isDisabled();
-
-       String getUserObjectClass();
-
-       String getUserBase();
-
-       String getGroupObjectClass();
-
-       String getGroupBase();
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java
deleted file mode 100644 (file)
index 613d0fd..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin}
- * service.
- */
-public class UserDirectoryException extends RuntimeException {
-       private static final long serialVersionUID = 1419352360062048603L;
-
-       public UserDirectoryException(String message) {
-               super(message);
-       }
-
-       public UserDirectoryException(String message, Throwable e) {
-               super(message, e);
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java
deleted file mode 100644 (file)
index 0e25bdf..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** {@link XAResource} for a user directory being edited. */
-class UserDirectoryWorkingCopy {
-       // private final static Log log = LogFactory
-       // .getLog(UserDirectoryWorkingCopy.class);
-
-       private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
-       private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
-       private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
-
-       void cleanUp() {
-               // clean collections
-               newUsers.clear();
-               newUsers = null;
-               modifiedUsers.clear();
-               modifiedUsers = null;
-               deletedUsers.clear();
-               deletedUsers = null;
-       }
-
-       public boolean noModifications() {
-               return newUsers.size() == 0 && modifiedUsers.size() == 0
-                               && deletedUsers.size() == 0;
-       }
-
-       public Attributes getAttributes(LdapName dn) {
-               if (modifiedUsers.containsKey(dn))
-                       return modifiedUsers.get(dn);
-               return null;
-       }
-
-       public void startEditing(DirectoryUser user) {
-               LdapName dn = user.getDn();
-               if (modifiedUsers.containsKey(dn))
-                       throw new UserDirectoryException("Already editing " + dn);
-               modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
-       }
-
-       public Map<LdapName, DirectoryUser> getNewUsers() {
-               return newUsers;
-       }
-
-       public Map<LdapName, DirectoryUser> getDeletedUsers() {
-               return deletedUsers;
-       }
-
-       public Map<LdapName, Attributes> getModifiedUsers() {
-               return modifiedUsers;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java
deleted file mode 100644 (file)
index 1630b6b..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** {@link XAResource} for a user directory being edited. */
-class WcXaResource implements XAResource {
-       private final AbstractUserDirectory userDirectory;
-
-       private Map<Xid, UserDirectoryWorkingCopy> workingCopies = new HashMap<Xid, UserDirectoryWorkingCopy>();
-       private Xid editingXid = null;
-       private int transactionTimeout = 0;
-
-       public WcXaResource(AbstractUserDirectory userDirectory) {
-               this.userDirectory = userDirectory;
-       }
-
-       @Override
-       public synchronized void start(Xid xid, int flags) throws XAException {
-               if (editingXid != null)
-                       throw new UserDirectoryException("Already editing " + editingXid);
-               UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy());
-               if (wc != null)
-                       throw new UserDirectoryException("There is already a working copy for " + xid);
-               this.editingXid = xid;
-       }
-
-       @Override
-       public void end(Xid xid, int flags) throws XAException {
-               checkXid(xid);
-       }
-
-       private UserDirectoryWorkingCopy wc(Xid xid) {
-               return workingCopies.get(xid);
-       }
-
-       synchronized UserDirectoryWorkingCopy wc() {
-               if (editingXid == null)
-                       return null;
-               UserDirectoryWorkingCopy wc = workingCopies.get(editingXid);
-               if (wc == null)
-                       throw new UserDirectoryException("No working copy found for " + editingXid);
-               return wc;
-       }
-
-       private synchronized void cleanUp(Xid xid) {
-               wc(xid).cleanUp();
-               workingCopies.remove(xid);
-               editingXid = null;
-       }
-
-       @Override
-       public int prepare(Xid xid) throws XAException {
-               checkXid(xid);
-               UserDirectoryWorkingCopy wc = wc(xid);
-               if (wc.noModifications())
-                       return XA_RDONLY;
-               try {
-                       userDirectory.prepare(wc);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               }
-               return XA_OK;
-       }
-
-       @Override
-       public void commit(Xid xid, boolean onePhase) throws XAException {
-               try {
-                       checkXid(xid);
-                       UserDirectoryWorkingCopy wc = wc(xid);
-                       if (wc.noModifications())
-                               return;
-                       if (onePhase)
-                               userDirectory.prepare(wc);
-                       userDirectory.commit(wc);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               } finally {
-                       cleanUp(xid);
-               }
-       }
-
-       @Override
-       public void rollback(Xid xid) throws XAException {
-               try {
-                       checkXid(xid);
-                       userDirectory.rollback(wc(xid));
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               } finally {
-                       cleanUp(xid);
-               }
-       }
-
-       @Override
-       public void forget(Xid xid) throws XAException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public boolean isSameRM(XAResource xares) throws XAException {
-               return xares == this;
-       }
-
-       @Override
-       public Xid[] recover(int flag) throws XAException {
-               return new Xid[0];
-       }
-
-       @Override
-       public int getTransactionTimeout() throws XAException {
-               return transactionTimeout;
-       }
-
-       @Override
-       public boolean setTransactionTimeout(int seconds) throws XAException {
-               transactionTimeout = seconds;
-               return true;
-       }
-
-       private void checkXid(Xid xid) throws XAException {
-               if (xid == null)
-                       throw new XAException(XAException.XAER_OUTSIDE);
-               if (!xid.equals(xid))
-                       throw new XAException(XAException.XAER_NOTA);
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg b/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg
deleted file mode 100644 (file)
index da04505..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-USER_NIX {
-    com.sun.security.auth.module.UnixLoginModule requisite; 
-};
-
-USER_NT {
-    com.sun.security.auth.module.NTLoginModule requisite; 
-};
-
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java b/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java
deleted file mode 100644 (file)
index c108d2c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** LDAP and LDIF based OSGi useradmin implementation. */
-package org.argeo.osgi.useradmin;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java
deleted file mode 100644 (file)
index 31f1d4d..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.osgi.util;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.osgi.resource.Namespace;
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-
-/** Simplify filtering resources. */
-public class FilterRequirement implements Requirement {
-       private String namespace;
-       private String filter;
-
-       public FilterRequirement(String namespace, String filter) {
-               this.namespace = namespace;
-               this.filter = filter;
-       }
-
-       @Override
-       public Resource getResource() {
-               return null;
-       }
-
-       @Override
-       public String getNamespace() {
-               return namespace;
-       }
-
-       @Override
-       public Map<String, String> getDirectives() {
-               Map<String, String> directives = new HashMap<>();
-               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
-               return directives;
-       }
-
-       @Override
-       public Map<String, Object> getAttributes() {
-               return new HashMap<>();
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java b/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java
deleted file mode 100644 (file)
index 5a6760e..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.osgi.util;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.function.Function;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class OnServiceRegistration<R> implements Future<R> {
-       private BundleContext ownBundleContext = FrameworkUtil.getBundle(OnServiceRegistration.class).getBundleContext();
-
-       private ServiceTracker<?, ?> st;
-
-       private R result;
-       private boolean cancelled = false;
-       private Throwable exception;
-
-       public <T> OnServiceRegistration(Class<T> clss, Function<T, R> function) {
-               this(null, clss, function);
-       }
-
-       public <T> OnServiceRegistration(BundleContext bundleContext, Class<T> clss, Function<T, R> function) {
-               st = new ServiceTracker<T, T>(bundleContext != null ? bundleContext : ownBundleContext, clss, null) {
-
-                       @Override
-                       public T addingService(ServiceReference<T> reference) {
-                               T service = super.addingService(reference);
-                               try {
-                                       if (result != null)// we only want the first one
-                                               return service;
-                                       result = function.apply(service);
-                                       return service;
-                               } catch (Exception e) {
-                                       exception = e;
-                                       return service;
-                               } finally {
-                                       close();
-                               }
-                       }
-               };
-               st.open(bundleContext == null);
-       }
-
-       @Override
-       public boolean cancel(boolean mayInterruptIfRunning) {
-               if (result != null || exception != null || cancelled)
-                       return false;
-               st.close();
-               cancelled = true;
-               return true;
-       }
-
-       @Override
-       public boolean isCancelled() {
-               return cancelled;
-       }
-
-       @Override
-       public boolean isDone() {
-               return result != null || cancelled;
-       }
-
-       @Override
-       public R get() throws InterruptedException, ExecutionException {
-               st.waitForService(0);
-               return tryGetResult();
-       }
-
-       @Override
-       public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
-               st.waitForService(TimeUnit.MILLISECONDS.convert(timeout, unit));
-               if (result == null)
-                       throw new TimeoutException("No result after " + timeout + " " + unit);
-               return tryGetResult();
-       }
-
-       protected R tryGetResult() throws ExecutionException, CancellationException {
-               if (cancelled)
-                       throw new CancellationException();
-               if (exception != null)
-                       throw new ExecutionException(exception);
-               if (result == null)// this should not happen
-                       try {
-                               throw new IllegalStateException("No result available");
-                       } catch (Exception e) {
-                               exception = e;
-                               throw new ExecutionException(e);
-                       }
-               return result;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java b/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java
deleted file mode 100644 (file)
index 7132b7c..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.osgi.util;
-
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ForkJoinPool;
-
-import org.argeo.util.register.Register;
-import org.argeo.util.register.Singleton;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-public class OsgiRegister implements Register {
-       private final BundleContext bundleContext;
-       private Executor executor;
-
-       private CompletableFuture<Void> shutdownStarting = new CompletableFuture<Void>();
-
-       public OsgiRegister(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-               // TODO experiment with dedicated executors
-               this.executor = ForkJoinPool.commonPool();
-       }
-
-       @Override
-       public <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes) {
-               CompletableFuture<ServiceRegistration<?>> srf = new CompletableFuture<ServiceRegistration<?>>();
-               CompletableFuture<T> postRegistration = CompletableFuture.supplyAsync(() -> {
-                       List<String> lst = new ArrayList<>();
-                       lst.add(clss.getName());
-                       for (Class<?> c : classes) {
-                               lst.add(c.getName());
-                       }
-                       ServiceRegistration<?> sr = bundleContext.registerService(lst.toArray(new String[lst.size()]), obj,
-                                       new Hashtable<String, Object>(attributes));
-                       srf.complete(sr);
-                       return obj;
-               }, executor);
-               Singleton<T> singleton = new Singleton<T>(clss, postRegistration);
-
-               shutdownStarting. //
-                               thenCompose(singleton::prepareUnregistration). //
-                               thenRunAsync(() -> {
-                                       try {
-                                               srf.get().unregister();
-                                       } catch (InterruptedException | ExecutionException e) {
-                                               e.printStackTrace();
-                                       }
-                               }, executor);
-               return singleton;
-       }
-
-       public void shutdown() {
-               shutdownStarting.complete(null);
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/CsvParser.java b/org.argeo.util/src/org/argeo/util/CsvParser.java
deleted file mode 100644 (file)
index b903f77..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-package org.argeo.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Parses a CSV file interpreting the first line as a header. The
- * {@link #parse(InputStream)} method and the setters are synchronized so that
- * the object cannot be modified when parsing.
- */
-public abstract class CsvParser {
-       private char separator = ',';
-       private char quote = '\"';
-
-       private Boolean noHeader = false;
-       private Boolean strictLineAsLongAsHeader = true;
-
-       /**
-        * Actually process a parsed line. If
-        * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
-        * and the tokens are guaranteed to have the same size.
-        * 
-        * @param lineNumber the current line number, starts at 1 (the header, if header
-        *                   processing is enabled, the first line otherwise)
-        * @param header     the read-only header or null if
-        *                   {@link #setNoHeader(Boolean)} is true (default is false)
-        * @param tokens     the parsed tokens
-        */
-       protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in the stream to parse
-        * 
-        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
-        */
-       @Deprecated
-       public synchronized void parse(InputStream in) {
-               parse(in, (Charset) null);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in       the stream to parse
-        * @param encoding the encoding to use.
-        * 
-        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
-        */
-       @Deprecated
-       public synchronized void parse(InputStream in, String encoding) {
-               Reader reader;
-               if (encoding == null)
-                       reader = new InputStreamReader(in);
-               else
-                       try {
-                               reader = new InputStreamReader(in, encoding);
-                       } catch (UnsupportedEncodingException e) {
-                               throw new IllegalArgumentException(e);
-                       }
-               parse(reader);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in      the stream to parse
-        * @param charset the charset to use
-        */
-       public synchronized void parse(InputStream in, Charset charset) {
-               Reader reader;
-               if (charset == null)
-                       reader = new InputStreamReader(in);
-               else
-                       reader = new InputStreamReader(in, charset);
-               parse(reader);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param reader the reader to use (it will be buffered)
-        */
-       public synchronized void parse(Reader reader) {
-               Integer lineCount = 0;
-               try (BufferedReader bufferedReader = new BufferedReader(reader)) {
-                       List<String> header = null;
-                       if (!noHeader) {
-                               String headerStr = bufferedReader.readLine();
-                               if (headerStr == null)// empty file
-                                       return;
-                               lineCount++;
-                               header = new ArrayList<String>();
-                               StringBuffer currStr = new StringBuffer("");
-                               Boolean wasInquote = false;
-                               while (parseLine(headerStr, header, currStr, wasInquote)) {
-                                       headerStr = bufferedReader.readLine();
-                                       if (headerStr == null)
-                                               break;
-                                       wasInquote = true;
-                               }
-                               header = Collections.unmodifiableList(header);
-                       }
-
-                       String line = null;
-                       lines: while ((line = bufferedReader.readLine()) != null) {
-                               line = preProcessLine(line);
-                               if (line == null) {
-                                       // skip line
-                                       continue lines;
-                               }
-                               lineCount++;
-                               List<String> tokens = new ArrayList<String>();
-                               StringBuffer currStr = new StringBuffer("");
-                               Boolean wasInquote = false;
-                               sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
-                                       line = bufferedReader.readLine();
-                                       if (line == null)
-                                               break sublines;
-                                       wasInquote = true;
-                               }
-                               if (!noHeader && strictLineAsLongAsHeader) {
-                                       int headerSize = header.size();
-                                       int tokenSize = tokens.size();
-                                       if (tokenSize == 1 && line.trim().equals(""))
-                                               continue lines;// empty line
-                                       if (headerSize != tokenSize) {
-                                               throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
-                                                               + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
-                                                               + ", tokens: " + tokens);
-                                       }
-                               }
-                               processLine(lineCount, header, tokens);
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
-               }
-       }
-
-       /**
-        * Called before each (logical) line is processed, giving a change to modify it
-        * (typically for cleaning dirty files). To be overridden, return the line
-        * unchanged by default. Skip the line if 'null' is returned.
-        */
-       protected String preProcessLine(String line) {
-               return line;
-       }
-
-       /**
-        * Parses a line character by character for performance purpose
-        * 
-        * @return whether to continue parsing this line
-        */
-       protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
-               if (wasInquote)
-                       currStr.append('\n');
-
-               char[] arr = str.toCharArray();
-               boolean inQuote = wasInquote;
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       if (c == separator) {
-                               if (!inQuote) {
-                                       tokens.add(currStr.toString());
-//                                     currStr.delete(0, currStr.length());
-                                       currStr.setLength(0);
-                                       currStr.trimToSize();
-                               } else {
-                                       // we don't remove separator that are in a quoted substring
-                                       // System.out
-                                       // .println("IN QUOTE, got a separator: [" + c + "]");
-                                       currStr.append(c);
-                               }
-                       } else if (c == quote) {
-                               if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
-                                       // case of double quote
-                                       currStr.append(quote);
-                                       i++;
-                               } else {// standard
-                                       inQuote = inQuote ? false : true;
-                               }
-                       } else {
-                               currStr.append(c);
-                       }
-               }
-
-               if (!inQuote) {
-                       tokens.add(currStr.toString());
-                       // System.out.println("# TOKEN: " + currStr);
-               }
-               // if (inQuote)
-               // throw new ArgeoException("Missing quote at the end of the line "
-               // + str + " (parsed: " + tokens + ")");
-               if (inQuote)
-                       return true;
-               else
-                       return false;
-               // return tokens;
-       }
-
-       public char getSeparator() {
-               return separator;
-       }
-
-       public synchronized void setSeparator(char separator) {
-               this.separator = separator;
-       }
-
-       public char getQuote() {
-               return quote;
-       }
-
-       public synchronized void setQuote(char quote) {
-               this.quote = quote;
-       }
-
-       public Boolean getNoHeader() {
-               return noHeader;
-       }
-
-       public synchronized void setNoHeader(Boolean noHeader) {
-               this.noHeader = noHeader;
-       }
-
-       public Boolean getStrictLineAsLongAsHeader() {
-               return strictLineAsLongAsHeader;
-       }
-
-       public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
-               this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java b/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java
deleted file mode 100644 (file)
index 8eb6e94..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.util;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * CSV parser allowing to process lines as maps whose keys are the header
- * fields.
- */
-public abstract class CsvParserWithLinesAsMap extends CsvParser {
-
-       /**
-        * Actually processes a line.
-        * 
-        * @param lineNumber the current line number, starts at 1 (the header, if header
-        *                   processing is enabled, the first lien otherwise)
-        * @param line       the parsed tokens as a map whose keys are the header fields
-        */
-       protected abstract void processLine(Integer lineNumber, Map<String, String> line);
-
-       protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-               if (header == null)
-                       throw new IllegalArgumentException("Only CSV with header is supported");
-               Map<String, String> line = new HashMap<String, String>();
-               for (int i = 0; i < header.size(); i++) {
-                       String key = header.get(i);
-                       String value = null;
-                       if (i < tokens.size())
-                               value = tokens.get(i);
-                       line.put(key, value);
-               }
-               processLine(lineNumber, line);
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/CsvWriter.java b/org.argeo.util/src/org/argeo/util/CsvWriter.java
deleted file mode 100644 (file)
index c3b3a3a..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.util.Iterator;
-import java.util.List;
-
-/** Write in CSV format. */
-public class CsvWriter {
-       private final Writer out;
-
-       private char separator = ',';
-       private char quote = '\"';
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out the stream to write to. Caller is responsible for closing it.
-        * 
-        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
-        * 
-        */
-       @Deprecated
-       public CsvWriter(OutputStream out) {
-               this.out = new OutputStreamWriter(out);
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out      the stream to write to. Caller is responsible for closing it.
-        * @param encoding the encoding to use.
-        * 
-        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
-        */
-       @Deprecated
-       public CsvWriter(OutputStream out, String encoding) {
-               try {
-                       this.out = new OutputStreamWriter(out, encoding);
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException(e);
-               }
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out     the stream to write to. Caller is responsible for closing it.
-        * @param charset the charset to use
-        */
-       public CsvWriter(OutputStream out, Charset charset) {
-               this.out = new OutputStreamWriter(out, charset);
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out the stream to write to. Caller is responsible for closing it.
-        */
-       public CsvWriter(Writer writer) {
-               this.out = writer;
-       }
-
-       /**
-        * Write a CSV line. Also used to write a header if needed (this is transparent
-        * for the CSV writer): simply call it first, before writing the lines.
-        */
-       public void writeLine(List<?> tokens) {
-               try {
-                       Iterator<?> it = tokens.iterator();
-                       while (it.hasNext()) {
-                               Object obj = it.next();
-                               writeToken(obj != null ? obj.toString() : null);
-                               if (it.hasNext())
-                                       out.write(separator);
-                       }
-                       out.write('\n');
-                       out.flush();
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not write " + tokens, e);
-               }
-       }
-
-       /**
-        * Write a CSV line. Also used to write a header if needed (this is transparent
-        * for the CSV writer): simply call it first, before writing the lines.
-        */
-       public void writeLine(Object[] tokens) {
-               try {
-                       for (int i = 0; i < tokens.length; i++) {
-                               if (tokens[i] == null) {
-                                       writeToken(null);
-                               } else {
-                                       writeToken(tokens[i].toString());
-                               }
-                               if (i != (tokens.length - 1))
-                                       out.write(separator);
-                       }
-                       out.write('\n');
-                       out.flush();
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not write " + tokens, e);
-               }
-       }
-
-       protected void writeToken(String token) throws IOException {
-               if (token == null) {
-                       // TODO configure how to deal with null
-                       out.write("");
-                       return;
-               }
-               // +2 for possible quotes, another +2 assuming there would be an already
-               // quoted string where quotes needs to be duplicated
-               // another +2 for safety
-               // we don't want to increase buffer size while writing
-               StringBuffer buf = new StringBuffer(token.length() + 6);
-               char[] arr = token.toCharArray();
-               boolean shouldQuote = false;
-               for (char c : arr) {
-                       if (!shouldQuote) {
-                               if (c == separator)
-                                       shouldQuote = true;
-                               if (c == '\n')
-                                       shouldQuote = true;
-                       }
-
-                       if (c == quote) {
-                               shouldQuote = true;
-                               // duplicate quote
-                               buf.append(quote);
-                       }
-
-                       // generic case
-                       buf.append(c);
-               }
-
-               if (shouldQuote == true)
-                       out.write(quote);
-               out.write(buf.toString());
-               if (shouldQuote == true)
-                       out.write(quote);
-       }
-
-       public void setSeparator(char separator) {
-               this.separator = separator;
-       }
-
-       public void setQuote(char quote) {
-               this.quote = quote;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/DictionaryKeys.java b/org.argeo.util/src/org/argeo/util/DictionaryKeys.java
deleted file mode 100644 (file)
index d17c86f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.util;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-
-/**
- * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
- * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
- * for-each loops.
- */
-class DictionaryKeys implements Iterable<String> {
-       private final Dictionary<String, ?> dictionary;
-
-       public DictionaryKeys(Dictionary<String, ?> dictionary) {
-               this.dictionary = dictionary;
-       }
-
-       @Override
-       public Iterator<String> iterator() {
-               return new KeyIterator(dictionary.keys());
-       }
-
-       private static class KeyIterator implements Iterator<String> {
-               private final Enumeration<String> keys;
-
-               KeyIterator(Enumeration<String> keys) {
-                       this.keys = keys;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return keys.hasMoreElements();
-               }
-
-               @Override
-               public String next() {
-                       return keys.nextElement();
-               }
-
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/DigestUtils.java b/org.argeo.util/src/org/argeo/util/DigestUtils.java
deleted file mode 100644 (file)
index 38b4e70..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-package org.argeo.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileChannel.MapMode;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/** Utilities around cryptographic digests */
-public class DigestUtils {
-       public final static String MD5 = "MD5";
-       public final static String SHA1 = "SHA1";
-       public final static String SHA256 = "SHA-256";
-       public final static String SHA512 = "SHA-512";
-
-       private static Boolean debug = false;
-       // TODO: make it configurable
-       private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
-
-       public static byte[] sha1(byte[]... bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(SHA1);
-                       for (byte[] arr : bytes)
-                               digest.update(arr);
-                       byte[] checksum = digest.digest();
-                       return checksum;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new UnsupportedOperationException("SHA1 is not avalaible", e);
-               }
-       }
-
-       public static byte[] digestAsBytes(String algorithm, byte[]... bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       for (byte[] arr : bytes)
-                               digest.update(arr);
-                       byte[] checksum = digest.digest();
-                       return checksum;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e);
-               }
-       }
-
-       public static String digest(String algorithm, byte[]... bytes) {
-               return toHexString(digestAsBytes(algorithm, bytes));
-       }
-
-       public static String digest(String algorithm, InputStream in) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       // ReadableByteChannel channel = Channels.newChannel(in);
-                       // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
-                       // while (channel.read(bb) > 0)
-                       // digest.update(bb);
-                       byte[] buffer = new byte[byteBufferCapacity];
-                       int read = 0;
-                       while ((read = in.read(buffer)) > 0) {
-                               digest.update(buffer, 0, read);
-                       }
-
-                       byte[] checksum = digest.digest();
-                       String res = toHexString(checksum);
-                       return res;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(in);
-               }
-       }
-
-       public static String digest(String algorithm, File file) {
-               FileInputStream fis = null;
-               FileChannel fc = null;
-               try {
-                       fis = new FileInputStream(file);
-                       fc = fis.getChannel();
-
-                       // Get the file's size and then map it into memory
-                       int sz = (int) fc.size();
-                       ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
-                       return digest(algorithm, bb);
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
-               } finally {
-                       StreamUtils.closeQuietly(fis);
-                       if (fc.isOpen())
-                               try {
-                                       fc.close();
-                               } catch (IOException e) {
-                                       // silent
-                               }
-               }
-       }
-
-       protected static String digest(String algorithm, ByteBuffer bb) {
-               long begin = System.currentTimeMillis();
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       digest.update(bb);
-                       byte[] checksum = digest.digest();
-                       String res = toHexString(checksum);
-                       long end = System.currentTimeMillis();
-                       if (debug)
-                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
-                       return res;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               }
-       }
-
-       public static String sha1hex(Path path) {
-               return digest(SHA1, path, byteBufferCapacity);
-       }
-
-       public static String digest(String algorithm, Path path, long bufferSize) {
-               byte[] digest = digestAsBytes(algorithm, path, bufferSize);
-               return toHexString(digest);
-       }
-
-       public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) {
-               long begin = System.currentTimeMillis();
-               try {
-                       MessageDigest md = MessageDigest.getInstance(algorithm);
-                       FileChannel fc = FileChannel.open(file);
-                       long fileSize = Files.size(file);
-                       if (fileSize <= bufferSize) {
-                               ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
-                               md.update(bb);
-                       } else {
-                               long lastCycle = (fileSize / bufferSize) - 1;
-                               long position = 0;
-                               for (int i = 0; i <= lastCycle; i++) {
-                                       ByteBuffer bb;
-                                       if (i != lastCycle) {
-                                               bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
-                                               position = position + bufferSize;
-                                       } else {
-                                               bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
-                                               position = fileSize;
-                                       }
-                                       md.update(bb);
-                               }
-                       }
-                       long end = System.currentTimeMillis();
-                       if (debug)
-                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
-                       return md.digest();
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest " + file + "  with algorithm " + algorithm, e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot digest " + file + "  with algorithm " + algorithm, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               File file;
-               if (args.length > 0)
-                       file = new File(args[0]);
-               else {
-                       System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
-                                       + "docs/guide/security/CryptoSpec.html#AppA)");
-                       return;
-               }
-
-               if (args.length > 1) {
-                       String algorithm = args[1];
-                       System.out.println(digest(algorithm, file));
-               } else {
-                       String algorithm = "MD5";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       algorithm = "SHA";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       System.out.println(algorithm + ": " + sha1hex(file.toPath()));
-                       algorithm = "SHA-256";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       algorithm = "SHA-512";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-               }
-       }
-
-       final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
-       /** Converts a byte array to an hex String. */
-       public static String toHexString(byte[] bytes) {
-               char[] hexChars = new char[bytes.length * 2];
-               for (int j = 0; j < bytes.length; j++) {
-                       int v = bytes[j] & 0xFF;
-                       hexChars[j * 2] = hexArray[v >>> 4];
-                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
-               }
-               return new String(hexChars);
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/DirH.java b/org.argeo.util/src/org/argeo/util/DirH.java
deleted file mode 100644 (file)
index 013897d..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Hashes the hashes of the files in a directory. */
-public class DirH {
-
-       private final static Charset charset = Charset.forName("UTF-16");
-       private final static long bufferSize = 200 * 1024 * 1024;
-       private final static String algorithm = "SHA";
-
-       private final static byte EOL = (byte) '\n';
-       private final static byte SPACE = (byte) ' ';
-
-       private final int hashSize;
-
-       private final byte[][] hashes;
-       private final byte[][] fileNames;
-       private final byte[] digest;
-       private final byte[] dirName;
-
-       /**
-        * @param dirName can be null or empty
-        */
-       private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
-               if (hashes.length != fileNames.length)
-                       throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
-               this.hashes = hashes;
-               this.fileNames = fileNames;
-               this.dirName = dirName == null ? new byte[0] : dirName;
-               if (hashes.length == 0) {// empty dir
-                       hashSize = 20;
-                       // FIXME what is the digest of an empty dir?
-                       digest = new byte[hashSize];
-                       Arrays.fill(digest, SPACE);
-                       return;
-               }
-               hashSize = hashes[0].length;
-               for (int i = 0; i < hashes.length; i++) {
-                       if (hashes[i].length != hashSize)
-                               throw new IllegalArgumentException(
-                                               "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
-               }
-
-               try {
-                       MessageDigest md = MessageDigest.getInstance(algorithm);
-                       for (int i = 0; i < hashes.length; i++) {
-                               md.update(this.hashes[i]);
-                               md.update(SPACE);
-                               md.update(this.fileNames[i]);
-                               md.update(EOL);
-                       }
-                       digest = md.digest();
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest", e);
-               }
-       }
-
-       public void print(PrintStream out) {
-               out.print(DigestUtils.toHexString(digest));
-               if (dirName.length > 0) {
-                       out.print(' ');
-                       out.print(new String(dirName, charset));
-               }
-               out.print('\n');
-               for (int i = 0; i < hashes.length; i++) {
-                       out.print(DigestUtils.toHexString(hashes[i]));
-                       out.print(' ');
-                       out.print(new String(fileNames[i], charset));
-                       out.print('\n');
-               }
-       }
-
-       public static DirH digest(Path dir) {
-               try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
-                       List<byte[]> hs = new ArrayList<byte[]>();
-                       List<String> fNames = new ArrayList<>();
-                       for (Path file : files) {
-                               if (!Files.isDirectory(file)) {
-                                       byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize);
-                                       hs.add(digest);
-                                       fNames.add(file.getFileName().toString());
-                               }
-                       }
-
-                       byte[][] fileNames = new byte[fNames.size()][];
-                       for (int i = 0; i < fNames.size(); i++) {
-                               fileNames[i] = fNames.get(i).getBytes(charset);
-                       }
-                       byte[][] hashes = hs.toArray(new byte[hs.size()][]);
-                       return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot digest " + dir, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               try {
-                       DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
-                       dirH.print(System.out);
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/FsUtils.java b/org.argeo.util/src/org/argeo/util/FsUtils.java
deleted file mode 100644 (file)
index b317f4b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-
-/** Utilities around the standard Java file abstractions. */
-public class FsUtils {
-       /**
-        * Deletes this path, recursively if needed. Does nothing if the path does not
-        * exist.
-        */
-       public static void delete(Path path) {
-               try {
-                       if (!Files.exists(path))
-                               return;
-                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
-                               @Override
-                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
-                                       if (e != null)
-                                               throw e;
-                                       Files.delete(directory);
-                                       return FileVisitResult.CONTINUE;
-                               }
-
-                               @Override
-                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       Files.delete(file);
-                                       return FileVisitResult.CONTINUE;
-                               }
-                       });
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot delete " + path, e);
-               }
-       }
-
-       /** Singleton. */
-       private FsUtils() {
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java
deleted file mode 100644 (file)
index 1622945..0000000
+++ /dev/null
@@ -1,284 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.Temporal;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Utilities around Java basic features. */
-public class LangUtils {
-       /*
-        * NON-API OSGi
-        */
-       /**
-        * Returns an array with the names of the provided classes. Useful when
-        * registering services with multiple interfaces in OSGi.
-        */
-       public static String[] names(Class<?>... clzz) {
-               String[] res = new String[clzz.length];
-               for (int i = 0; i < clzz.length; i++)
-                       res[i] = clzz[i].getName();
-               return res;
-       }
-
-       /*
-        * MAP
-        */
-       /**
-        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
-        * null, but if the value is null, it returns an empty {@link Dictionary}.
-        */
-       public static Map<String, Object> map(String key, Object value) {
-               assert key != null;
-               HashMap<String, Object> props = new HashMap<>();
-               if (value != null)
-                       props.put(key, value);
-               return props;
-       }
-
-       /*
-        * DICTIONARY
-        */
-
-       /**
-        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
-        * null, but if the value is null, it returns an empty {@link Dictionary}.
-        */
-       public static Dictionary<String, Object> dict(String key, Object value) {
-               assert key != null;
-               Hashtable<String, Object> props = new Hashtable<>();
-               if (value != null)
-                       props.put(key, value);
-               return props;
-       }
-
-       /** @deprecated Use {@link #dict(String, Object)} instead. */
-       @Deprecated
-       public static Dictionary<String, Object> dico(String key, Object value) {
-               return dict(key, value);
-       }
-
-       /** Converts a {@link Dictionary} to a {@link Map} of strings. */
-       public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
-               if (properties == null) {
-                       return null;
-               }
-               Map<String, String> res = new HashMap<>(properties.size());
-               Enumeration<String> keys = properties.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       res.put(key, properties.get(key).toString());
-               }
-               return res;
-       }
-
-       /**
-        * Get a string property from this map, expecting to find it, or
-        * <code>null</code> if not found.
-        */
-       public static String get(Map<String, ?> map, String key) {
-               Object res = map.get(key);
-               if (res == null)
-                       return null;
-               return res.toString();
-       }
-
-       /**
-        * Get a string property from this map, expecting to find it.
-        * 
-        * @throws IllegalArgumentException if the key was not found
-        */
-       public static String getNotNull(Map<String, ?> map, String key) {
-               Object res = map.get(key);
-               if (res == null)
-                       throw new IllegalArgumentException("Map " + map + " should contain key " + key);
-               return res.toString();
-       }
-
-       /**
-        * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
-        */
-       public static Iterable<String> keys(Dictionary<String, ?> props) {
-               assert props != null;
-               return new DictionaryKeys(props);
-       }
-
-       static String toJson(Dictionary<String, ?> props) {
-               return toJson(props, false);
-       }
-
-       static String toJson(Dictionary<String, ?> props, boolean pretty) {
-               StringBuilder sb = new StringBuilder();
-               sb.append('{');
-               if (pretty)
-                       sb.append('\n');
-               Enumeration<String> keys = props.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       if (pretty)
-                               sb.append(' ');
-                       sb.append('\"').append(key).append('\"');
-                       if (pretty)
-                               sb.append(" : ");
-                       else
-                               sb.append(':');
-                       sb.append('\"').append(props.get(key)).append('\"');
-                       if (keys.hasMoreElements())
-                               sb.append(", ");
-                       if (pretty)
-                               sb.append('\n');
-               }
-               sb.append('}');
-               return sb.toString();
-       }
-
-       static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
-               if (props == null)
-                       throw new IllegalArgumentException("Props cannot be null");
-               Properties toStore = new Properties();
-               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                       String key = keys.nextElement();
-                       toStore.setProperty(key, props.get(key).toString());
-               }
-               try (OutputStream out = Files.newOutputStream(path)) {
-                       toStore.store(out, null);
-               }
-       }
-
-       static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
-                       throws IOException {
-               if (props == null)
-                       throw new IllegalArgumentException("Props cannot be null");
-               Object dnValue = props.get(dnKey);
-               String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
-               LdapName dn;
-               try {
-                       dn = new LdapName(dnStr);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
-               }
-               if (dnValue == null)
-                       throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
-               try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
-                       writer.append("\ndn: ");
-                       writer.append(dn.toString());
-                       writer.append('\n');
-                       for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                               String key = keys.nextElement();
-                               Object value = props.get(key);
-                               writer.append(key);
-                               writer.append(": ");
-                               // FIXME deal with binary and multiple values
-                               writer.append(value.toString());
-                               writer.append('\n');
-                       }
-               }
-       }
-
-       static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
-               Properties toLoad = new Properties();
-               try (InputStream in = Files.newInputStream(path)) {
-                       toLoad.load(in);
-               }
-               Dictionary<String, Object> res = new Hashtable<String, Object>();
-               for (Object key : toLoad.keySet())
-                       res.put(key.toString(), toLoad.get(key));
-               return res;
-       }
-
-       /*
-        * COLLECTIONS
-        */
-       /**
-        * Convert a comma-separated separated {@link String} or a {@link String} array
-        * to a {@link List} of {@link String}, trimming them. Useful to quickly
-        * interpret OSGi services properties.
-        * 
-        * @return a {@link List} containing the trimmed {@link String}s, or an empty
-        *         {@link List} if the argument was <code>null</code>.
-        */
-       public static List<String> toStringList(Object value) {
-               List<String> values = new ArrayList<>();
-               if (value == null)
-                       return values;
-               String[] arr;
-               if (value instanceof String) {
-                       arr = ((String) value).split(",");
-               } else if (value instanceof String[]) {
-                       arr = (String[]) value;
-               } else {
-                       throw new IllegalArgumentException("Unsupported value type " + value.getClass());
-               }
-               for (String str : arr) {
-                       values.add(str.trim());
-               }
-               return values;
-       }
-
-       /*
-        * EXCEPTIONS
-        */
-       /**
-        * Chain the messages of all causes (one per line, <b>starts with a line
-        * return</b>) without all the stack
-        */
-       public static String chainCausesMessages(Throwable t) {
-               StringBuffer buf = new StringBuffer();
-               chainCauseMessage(buf, t);
-               return buf.toString();
-       }
-
-       /** Recursive chaining of messages */
-       private static void chainCauseMessage(StringBuffer buf, Throwable t) {
-               buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
-               if (t.getCause() != null)
-                       chainCauseMessage(buf, t.getCause());
-       }
-
-       /*
-        * TIME
-        */
-       /** Formats time elapsed since start. */
-       public static String since(ZonedDateTime start) {
-               ZonedDateTime now = ZonedDateTime.now();
-               return duration(start, now);
-       }
-
-       /** Formats a duration. */
-       public static String duration(Temporal start, Temporal end) {
-               long count = ChronoUnit.DAYS.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " days" : count + " day";
-               count = ChronoUnit.HOURS.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " hours" : count + " hours";
-               count = ChronoUnit.MINUTES.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " minutes" : count + " minute";
-               count = ChronoUnit.SECONDS.between(start, end);
-               return count > 1 ? count + " seconds" : count + " second";
-       }
-
-       /** Singleton constructor. */
-       private LangUtils() {
-
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/OS.java b/org.argeo.util/src/org/argeo/util/OS.java
deleted file mode 100644 (file)
index d8127b6..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.argeo.util;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-/** When OS specific informations are needed. */
-public class OS {
-       public final static OS LOCAL = new OS();
-
-       private final String arch, name, version;
-
-       /** The OS of the running JVM */
-       protected OS() {
-               arch = System.getProperty("os.arch");
-               name = System.getProperty("os.name");
-               version = System.getProperty("os.version");
-       }
-
-       public String getArch() {
-               return arch;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public String getVersion() {
-               return version;
-       }
-
-       public boolean isMSWindows() {
-               // only MS Windows would use such an horrendous separator...
-               return File.separatorChar == '\\';
-       }
-
-       public String[] getDefaultShellCommand() {
-               if (!isMSWindows())
-                       return new String[] { "/bin/sh", "-l", "-i" };
-               else
-                       return new String[] { "cmd.exe", "/C" };
-       }
-
-       public static Integer getJvmPid() {
-               /*
-                * This method works on most platforms (including Linux). Although when Java 9
-                * comes along, there is a better way: long pid =
-                * ProcessHandle.current().getPid();
-                *
-                * See:
-                * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-
-                * process-id
-                */
-               String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
-               return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/PasswordEncryption.java b/org.argeo.util/src/org/argeo/util/PasswordEncryption.java
deleted file mode 100644 (file)
index c95c787..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-public class PasswordEncryption {
-       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
-       /** Stronger with 256, but causes problem with Oracle JVM */
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
-       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
-//     public final static String DEFAULT_CHARSET = "UTF-8";
-       public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
-       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
-       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
-       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
-       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
-       private String cipherName = DEFAULT_CIPHER_NAME;
-
-       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-
-       private Key key;
-       private Cipher ecipher;
-       private Cipher dcipher;
-
-       private String securityProviderName = null;
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copy of nor
-        * reference to the passed array is kept
-        */
-       public PasswordEncryption(char[] password) {
-               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
-       }
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copies of nor
-        * references to the passed arrays are kept
-        */
-       public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
-               try {
-                       initKeyAndCiphers(password, passwordSalt, initializationVector);
-               } catch (InvalidKeyException e) {
-                       Integer previousSecreteKeyLength = secreteKeyLength;
-                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
-                       System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
-                                       + " secrete key length instead of " + previousSecreteKeyLength);
-                       try {
-                               initKeyAndCiphers(password, passwordSalt, initializationVector);
-                       } catch (GeneralSecurityException e1) {
-                               throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
-                       }
-               } catch (GeneralSecurityException e) {
-                       throw new IllegalStateException("Cannot get secret key", e);
-               }
-       }
-
-       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
-                       throws GeneralSecurityException {
-               byte[] salt = new byte[8];
-               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
-               // for (int i = 0; i < password.length && i < salt.length; i++)
-               // salt[i] = (byte) password[i];
-               byte[] iv = new byte[16];
-               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
-
-               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
-               PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
-               String secKeyEncryption = getSecretKeyEncryption();
-               if (secKeyEncryption != null) {
-                       SecretKey tmp = keyFac.generateSecret(keySpec);
-                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
-               } else {
-                       key = keyFac.generateSecret(keySpec);
-               }
-               if (securityProviderName != null)
-                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
-               else
-                       ecipher = Cipher.getInstance(getCipherName());
-               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-               dcipher = Cipher.getInstance(getCipherName());
-               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-       }
-
-       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
-               try {
-                       CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
-                       StreamUtils.copy(decryptedIn, out);
-                       StreamUtils.closeQuietly(out);
-               } catch (IOException e) {
-                       throw e;
-               } finally {
-                       StreamUtils.closeQuietly(decryptedIn);
-               }
-       }
-
-       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
-               try {
-                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
-                       StreamUtils.copy(decryptedIn, decryptedOut);
-               } catch (IOException e) {
-                       throw e;
-               } finally {
-                       StreamUtils.closeQuietly(encryptedIn);
-               }
-       }
-
-       public byte[] encryptString(String str) {
-               ByteArrayOutputStream out = null;
-               ByteArrayInputStream in = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
-                       encrypt(in, out);
-                       return out.toByteArray();
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(out);
-               }
-       }
-
-       /** Closes the input stream */
-       public String decryptAsString(InputStream in) {
-               ByteArrayOutputStream out = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       decrypt(in, out);
-                       return new String(out.toByteArray(), DEFAULT_CHARSET);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(out);
-               }
-       }
-
-       protected Key getKey() {
-               return key;
-       }
-
-       protected Cipher getEcipher() {
-               return ecipher;
-       }
-
-       protected Cipher getDcipher() {
-               return dcipher;
-       }
-
-       protected Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       protected Integer getKeyLength() {
-               return secreteKeyLength;
-       }
-
-       protected String getSecretKeyFactoryName() {
-               return secreteKeyFactoryName;
-       }
-
-       protected String getSecretKeyEncryption() {
-               return secreteKeyEncryption;
-       }
-
-       protected String getCipherName() {
-               return cipherName;
-       }
-
-       public void setIterationCount(Integer iterationCount) {
-               this.iterationCount = iterationCount;
-       }
-
-       public void setSecreteKeyLength(Integer keyLength) {
-               this.secreteKeyLength = keyLength;
-       }
-
-       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
-               this.secreteKeyFactoryName = secreteKeyFactoryName;
-       }
-
-       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
-               this.secreteKeyEncryption = secreteKeyEncryption;
-       }
-
-       public void setCipherName(String cipherName) {
-               this.cipherName = cipherName;
-       }
-
-       public void setSecurityProviderName(String securityProviderName) {
-               this.securityProviderName = securityProviderName;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/ServiceChannel.java b/org.argeo.util/src/org/argeo/util/ServiceChannel.java
deleted file mode 100644 (file)
index 7997384..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousByteChannel;
-import java.nio.channels.CompletionHandler;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
-public class ServiceChannel implements AsynchronousByteChannel {
-       private final ReadableByteChannel in;
-       private final WritableByteChannel out;
-
-       private boolean open = true;
-
-       private ExecutorService executor;
-
-       public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
-               this.in = in;
-               this.out = out;
-               this.executor = executor;
-       }
-
-       @Override
-       public Future<Integer> read(ByteBuffer dst) {
-               return executor.submit(() -> in.read(dst));
-       }
-
-       @Override
-       public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
-               try {
-                       Future<Integer> res = read(dst);
-                       handler.completed(res.get(), attachment);
-               } catch (Exception e) {
-                       handler.failed(e, attachment);
-               }
-       }
-
-       @Override
-       public Future<Integer> write(ByteBuffer src) {
-               return executor.submit(() -> out.write(src));
-       }
-
-       @Override
-       public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
-               try {
-                       Future<Integer> res = write(src);
-                       handler.completed(res.get(), attachment);
-               } catch (Exception e) {
-                       handler.failed(e, attachment);
-               }
-       }
-
-       @Override
-       public synchronized void close() throws IOException {
-               try {
-                       in.close();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-               try {
-                       out.close();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-               open = false;
-               notifyAll();
-       }
-
-       @Override
-       public synchronized boolean isOpen() {
-               return open;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/StreamUtils.java b/org.argeo.util/src/org/argeo/util/StreamUtils.java
deleted file mode 100644 (file)
index 6d7d940..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.Writer;
-
-/** Utilities to be used when Apache Commons IO is not available. */
-class StreamUtils {
-       private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
-
-       /*
-        * APACHE COMMONS IO (inspired)
-        */
-
-       /** @return the number of bytes */
-       public static Long copy(InputStream in, OutputStream out)
-                       throws IOException {
-               Long count = 0l;
-               byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
-               while (true) {
-                       int length = in.read(buf);
-                       if (length < 0)
-                               break;
-                       out.write(buf, 0, length);
-                       count = count + length;
-               }
-               return count;
-       }
-
-       /** @return the number of chars */
-       public static Long copy(Reader in, Writer out) throws IOException {
-               Long count = 0l;
-               char[] buf = new char[DEFAULT_BUFFER_SIZE];
-               while (true) {
-                       int length = in.read(buf);
-                       if (length < 0)
-                               break;
-                       out.write(buf, 0, length);
-                       count = count + length;
-               }
-               return count;
-       }
-
-       public static void closeQuietly(InputStream in) {
-               if (in != null)
-                       try {
-                               in.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(OutputStream out) {
-               if (out != null)
-                       try {
-                               out.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(Reader in) {
-               if (in != null)
-                       try {
-                               in.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(Writer out) {
-               if (out != null)
-                       try {
-                               out.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/Tester.java b/org.argeo.util/src/org/argeo/util/Tester.java
deleted file mode 100644 (file)
index 31a2be4..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.argeo.util;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/** A generic tester based on Java assertions and functional programming. */
-public class Tester {
-       private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       private ClassLoader classLoader;
-
-       /** Use {@link Thread#getContextClassLoader()} by default. */
-       public Tester() {
-               this(Thread.currentThread().getContextClassLoader());
-       }
-
-       public Tester(ClassLoader classLoader) {
-               this.classLoader = classLoader;
-       }
-
-       public void execute(String className) {
-               Class<?> clss;
-               try {
-                       clss = classLoader.loadClass(className);
-                       boolean assertionsEnabled = clss.desiredAssertionStatus();
-                       if (!assertionsEnabled)
-                               throw new IllegalStateException("Test runner " + getClass().getName()
-                                               + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
-               } catch (Exception e1) {
-                       throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
-
-               }
-               List<Method> methods = findMethods(clss);
-               if (methods.size() == 0)
-                       throw new IllegalArgumentException("No test method found in " + clss);
-               // TODO make order more predictable?
-               for (Method method : methods) {
-                       String uid = method.getDeclaringClass().getName() + "#" + method.getName();
-                       TesterStatus testStatus = new TesterStatus(uid);
-                       Object obj = null;
-                       try {
-                               beforeTest(uid, method);
-                               obj = clss.getDeclaredConstructor().newInstance();
-                               method.invoke(obj);
-                               testStatus.setPassed();
-                               afterTestPassed(uid, method, obj);
-                       } catch (Exception e) {
-                               testStatus.setFailed(e);
-                               afterTestFailed(uid, method, obj, e);
-                       } finally {
-                               results.put(uid, testStatus);
-                       }
-               }
-       }
-
-       protected void beforeTest(String uid, Method method) {
-               // System.out.println(uid + ": STARTING");
-       }
-
-       protected void afterTestPassed(String uid, Method method, Object obj) {
-               System.out.println(uid + ": PASSED");
-       }
-
-       protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
-               System.out.println(uid + ": FAILED");
-               e.printStackTrace();
-       }
-
-       protected List<Method> findMethods(Class<?> clss) {
-               List<Method> methods = new ArrayList<Method>();
-//             Method call = getMethod(clss, "call");
-//             if (call != null)
-//                     methods.add(call);
-//
-               for (Method method : clss.getMethods()) {
-                       if (method.getName().startsWith("test")) {
-                               methods.add(method);
-                       }
-               }
-               return methods;
-       }
-
-       protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
-               try {
-                       return clss.getMethod(name, parameterTypes);
-               } catch (NoSuchMethodException e) {
-                       return null;
-               } catch (SecurityException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public static void main(String[] args) {
-               // deal with arguments
-               String className;
-               if (args.length < 1) {
-                       System.err.println(usage());
-                       System.exit(1);
-                       throw new IllegalArgumentException();
-               } else {
-                       className = args[0];
-               }
-
-               Tester test = new Tester();
-               try {
-                       test.execute(className);
-               } catch (Throwable e) {
-                       e.printStackTrace();
-               }
-
-               Map<String, TesterStatus> r = test.results;
-               for (String uid : r.keySet()) {
-                       TesterStatus testStatus = r.get(uid);
-                       System.out.println(testStatus);
-               }
-       }
-
-       public static String usage() {
-               return "java " + Tester.class.getName() + " [test class name]";
-
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/TesterStatus.java b/org.argeo.util/src/org/argeo/util/TesterStatus.java
deleted file mode 100644 (file)
index d1d14ed..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.util;
-
-import java.io.Serializable;
-
-/** The status of a test. */
-public class TesterStatus implements Serializable {
-       private static final long serialVersionUID = 6272975746885487000L;
-
-       private Boolean passed = null;
-       private final String uid;
-       private Throwable throwable = null;
-
-       public TesterStatus(String uid) {
-               this.uid = uid;
-       }
-
-       /** For cloning. */
-       public TesterStatus(String uid, Boolean passed, Throwable throwable) {
-               this(uid);
-               this.passed = passed;
-               this.throwable = throwable;
-       }
-
-       public synchronized Boolean isRunning() {
-               return passed == null;
-       }
-
-       public synchronized Boolean isPassed() {
-               assert passed != null;
-               return passed;
-       }
-
-       public synchronized Boolean isFailed() {
-               assert passed != null;
-               return !passed;
-       }
-
-       public synchronized void setPassed() {
-               setStatus(true);
-       }
-
-       public synchronized void setFailed() {
-               setStatus(false);
-       }
-
-       public synchronized void setFailed(Throwable throwable) {
-               setStatus(false);
-               setThrowable(throwable);
-       }
-
-       protected void setStatus(Boolean passed) {
-               if (this.passed != null)
-                       throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
-               this.passed = passed;
-       }
-
-       protected void setThrowable(Throwable throwable) {
-               if (this.throwable != null)
-                       throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
-               this.throwable = throwable;
-       }
-
-       public String getUid() {
-               return uid;
-       }
-
-       public Throwable getThrowable() {
-               return throwable;
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               // TODO Auto-generated method stub
-               return super.clone();
-       }
-
-       @Override
-       public boolean equals(Object o) {
-               if (o instanceof TesterStatus) {
-                       TesterStatus other = (TesterStatus) o;
-                       // we don't check consistency for performance purposes
-                       // this equals() is supposed to be used in collections or for transfer
-                       return other.uid.equals(uid);
-               }
-               return false;
-       }
-
-       @Override
-       public int hashCode() {
-               return uid.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return uid + "\t" + (passed ? "passed" : "failed");
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/Throughput.java b/org.argeo.util/src/org/argeo/util/Throughput.java
deleted file mode 100644 (file)
index 266ddbc..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.util;
-
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.Locale;
-
-/** A throughput, that is, a value per unit of time. */
-public class Throughput {
-       private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
-
-       public enum Unit {
-               s, m, h, d
-       }
-
-       private final Double value;
-       private final Unit unit;
-
-       public Throughput(Double value, Unit unit) {
-               this.value = value;
-               this.unit = unit;
-       }
-
-       public Throughput(Long periodMs, Long count, Unit unit) {
-               if (unit.equals(Unit.s))
-                       value = ((double) count * 1000d) / periodMs;
-               else if (unit.equals(Unit.m))
-                       value = ((double) count * 60d * 1000d) / periodMs;
-               else if (unit.equals(Unit.h))
-                       value = ((double) count * 60d * 60d * 1000d) / periodMs;
-               else if (unit.equals(Unit.d))
-                       value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
-               else
-                       throw new IllegalArgumentException("Unsupported unit " + unit);
-               this.unit = unit;
-       }
-
-       public Throughput(Double value, String unitStr) {
-               this(value, Unit.valueOf(unitStr));
-       }
-
-       public Throughput(String def) {
-               int index = def.indexOf('/');
-               if (def.length() < 3 || index <= 0 || index != def.length() - 2)
-                       throw new IllegalArgumentException(
-                                       def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
-               String valueStr = def.substring(0, index);
-               String unitStr = def.substring(index + 1);
-               try {
-                       this.value = usNumberFormat.parse(valueStr).doubleValue();
-               } catch (ParseException e) {
-                       throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
-               }
-               this.unit = Unit.valueOf(unitStr);
-       }
-
-       public Long asMsPeriod() {
-               if (unit.equals(Unit.s))
-                       return Math.round(1000d / value);
-               else if (unit.equals(Unit.m))
-                       return Math.round((60d * 1000d) / value);
-               else if (unit.equals(Unit.h))
-                       return Math.round((60d * 60d * 1000d) / value);
-               else if (unit.equals(Unit.d))
-                       return Math.round((24d * 60d * 60d * 1000d) / value);
-               else
-                       throw new IllegalArgumentException("Unsupported unit " + unit);
-       }
-
-       @Override
-       public String toString() {
-               return usNumberFormat.format(value) + '/' + unit;
-       }
-
-       public Double getValue() {
-               return value;
-       }
-
-       public Unit getUnit() {
-               return unit;
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java b/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java
deleted file mode 100644 (file)
index 7c645f3..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.argeo.util.naming;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-
-public class AttributesDictionary extends Dictionary<String, Object> {
-       private final Attributes attributes;
-
-       /** The provided attributes is wrapped, not copied. */
-       public AttributesDictionary(Attributes attributes) {
-               if (attributes == null)
-                       throw new IllegalArgumentException("Attributes cannot be null");
-               this.attributes = attributes;
-       }
-
-       @Override
-       public int size() {
-               return attributes.size();
-       }
-
-       @Override
-       public boolean isEmpty() {
-               return attributes.size() == 0;
-       }
-
-       @Override
-       public Enumeration<String> keys() {
-               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
-               return new Enumeration<String>() {
-
-                       @Override
-                       public boolean hasMoreElements() {
-                               return namingEnumeration.hasMoreElements();
-                       }
-
-                       @Override
-                       public String nextElement() {
-                               return namingEnumeration.nextElement();
-                       }
-
-               };
-       }
-
-       @Override
-       public Enumeration<Object> elements() {
-               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
-               return new Enumeration<Object>() {
-
-                       @Override
-                       public boolean hasMoreElements() {
-                               return namingEnumeration.hasMoreElements();
-                       }
-
-                       @Override
-                       public Object nextElement() {
-                               String key = namingEnumeration.nextElement();
-                               return get(key);
-                       }
-
-               };
-       }
-
-       @Override
-       /** @returns a <code>String</code> or <code>String[]</code> */
-       public Object get(Object key) {
-               try {
-                       if (key == null)
-                               throw new IllegalArgumentException("Key cannot be null");
-                       Attribute attr = attributes.get(key.toString());
-                       if (attr == null)
-                               return null;
-                       if (attr.size() == 0)
-                               throw new IllegalStateException("There must be at least one value");
-                       else if (attr.size() == 1) {
-                               return attr.get().toString();
-                       } else {// multiple
-                               String[] res = new String[attr.size()];
-                               for (int i = 0; i < attr.size(); i++) {
-                                       Object value = attr.get();
-                                       if (value == null)
-                                               throw new RuntimeException("Values cannot be null");
-                                       res[i] = attr.get(i).toString();
-                               }
-                               return res;
-                       }
-               } catch (NamingException e) {
-                       throw new RuntimeException("Cannot get value for " + key, e);
-               }
-       }
-
-       @Override
-       public Object put(String key, Object value) {
-               if (key == null)
-                       throw new IllegalArgumentException("Key cannot be null");
-               if (value == null)
-                       throw new IllegalArgumentException("Value cannot be null");
-
-               Object oldValue = get(key);
-               Attribute attr = attributes.get(key);
-               if (attr == null) {
-                       attr = new BasicAttribute(key);
-                       attributes.put(attr);
-               }
-
-               if (value instanceof String[]) {
-                       String[] values = (String[]) value;
-                       // clean additional values
-                       for (int i = values.length; i < attr.size(); i++)
-                               attr.remove(i);
-                       // set values
-                       for (int i = 0; i < values.length; i++) {
-                               attr.set(i, values[i]);
-                       }
-               } else {
-                       if (attr.size() > 1)
-                               throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
-                       if (attr.size() == 1) {
-                               try {
-                                       if (!attr.get(0).equals(value))
-                                               attr.set(0, value.toString());
-                               } catch (NamingException e) {
-                                       throw new RuntimeException("Cannot check existing value", e);
-                               }
-                       } else {
-                               attr.add(value.toString());
-                       }
-               }
-               return oldValue;
-       }
-
-       @Override
-       public Object remove(Object key) {
-               if (key == null)
-                       throw new IllegalArgumentException("Key cannot be null");
-               Object oldValue = get(key);
-               if (oldValue == null)
-                       return null;
-               return attributes.remove(key.toString());
-       }
-
-       /**
-        * Copy the <b>content</b> of an {@link Attributes} to the provided
-        * {@link Dictionary}.
-        */
-       public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
-               AttributesDictionary ad = new AttributesDictionary(attributes);
-               Enumeration<String> keys = ad.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       dictionary.put(key, ad.get(key));
-               }
-       }
-
-       /**
-        * Copy a {@link Dictionary} into an {@link Attributes}.
-        */
-       public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
-               AttributesDictionary ad = new AttributesDictionary(attributes);
-               Enumeration<String> keys = dictionary.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       ad.put(key, dictionary.get(key));
-               }
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java b/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java
deleted file mode 100644 (file)
index 973b90f..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.argeo.util.naming;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.StringTokenizer;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** LDAP authPassword field according to RFC 3112 */
-public class AuthPassword implements CallbackHandler {
-       private final String authScheme;
-       private final String authInfo;
-       private final String authValue;
-
-       public AuthPassword(String value) {
-               StringTokenizer st = new StringTokenizer(value, "$");
-               // TODO make it more robust, deal with bad formatting
-               this.authScheme = st.nextToken().trim();
-               this.authInfo = st.nextToken().trim();
-               this.authValue = st.nextToken().trim();
-
-               String expectedAuthScheme = getExpectedAuthScheme();
-               if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
-                       throw new IllegalArgumentException(
-                                       "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
-       }
-
-       protected AuthPassword(String authInfo, String authValue) {
-               this.authScheme = getExpectedAuthScheme();
-               if (authScheme == null)
-                       throw new IllegalArgumentException("Expected auth scheme cannot be null");
-               this.authInfo = authInfo;
-               this.authValue = authValue;
-       }
-
-       protected AuthPassword(AuthPassword authPassword) {
-               this.authScheme = authPassword.getAuthScheme();
-               this.authInfo = authPassword.getAuthInfo();
-               this.authValue = authPassword.getAuthValue();
-       }
-
-       protected String getExpectedAuthScheme() {
-               return null;
-       }
-
-       protected boolean matchAuthValue(Object object) {
-               return authValue.equals(object.toString());
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof AuthPassword))
-                       return false;
-               AuthPassword authPassword = (AuthPassword) obj;
-               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
-                               && authValue.equals(authValue);
-       }
-
-       public boolean keyEquals(AuthPassword authPassword) {
-               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
-       }
-
-       @Override
-       public int hashCode() {
-               return authValue.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return toAuthPassword();
-       }
-
-       public final String toAuthPassword() {
-               return getAuthScheme() + '$' + authInfo + '$' + authValue;
-       }
-
-       public String getAuthScheme() {
-               return authScheme;
-       }
-
-       public String getAuthInfo() {
-               return authInfo;
-       }
-
-       public String getAuthValue() {
-               return authValue;
-       }
-
-       public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
-               try {
-                       Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
-                       if (authPassword != null) {
-                               NamingEnumeration<?> values = authPassword.getAll();
-                               while (values.hasMore()) {
-                                       Object val = values.next();
-                                       AuthPassword token = new AuthPassword(val.toString());
-                                       String auth;
-                                       if (Arrays.binarySearch(value, '$') >= 0) {
-                                               auth = token.authInfo + '$' + token.authValue;
-                                       } else {
-                                               auth = token.authValue;
-                                       }
-                                       if (Arrays.equals(auth.toCharArray(), value))
-                                               return token;
-                                       // if (token.matchAuthValue(value))
-                                       // return token;
-                               }
-                       }
-                       return null;
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot check attribute", e);
-               }
-       }
-
-       public static boolean remove(Attributes attributes, AuthPassword value) {
-               Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
-               return authPassword.remove(value.toAuthPassword());
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               for (Callback callback : callbacks) {
-                       if (callback instanceof NameCallback)
-                               ((NameCallback) callback).setName(toAuthPassword());
-                       else if (callback instanceof PasswordCallback)
-                               ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
-               }
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/Distinguished.java b/org.argeo.util/src/org/argeo/util/naming/Distinguished.java
deleted file mode 100644 (file)
index 6aefc16..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.util.naming;
-
-import java.util.EnumSet;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/**
- * An object that can be identified with an X.500 distinguished name.
- * 
- * @see https://tools.ietf.org/html/rfc1779
- */
-public interface Distinguished {
-       /** The related distinguished name. */
-       String dn();
-
-       /** The related distinguished name as an {@link LdapName}. */
-       default LdapName distinguishedName() {
-               try {
-                       return new LdapName(dn());
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
-               }
-       }
-
-       /** List all DNs of an enumeration as strings. */
-       static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
-               Set<String> res = new TreeSet<>();
-               for (Enum<? extends Distinguished> enm : enumSet) {
-                       res.add(((Distinguished) enm).dn());
-               }
-               return res;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java b/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java
deleted file mode 100644 (file)
index 1a67eea..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-package org.argeo.util.naming;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.naming.Binding;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-
-public class DnsBrowser implements Closeable {
-       private final DirContext initialCtx;
-
-       public DnsBrowser() throws NamingException {
-               this(null);
-       }
-
-       public DnsBrowser(String dnsServerUrls) throws NamingException {
-               Hashtable<String, Object> env = new Hashtable<>();
-               env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
-               if (dnsServerUrls != null)
-                       env.put("java.naming.provider.url", dnsServerUrls);
-               initialCtx = new InitialDirContext(env);
-       }
-
-       public Map<String, List<String>> getAllRecords(String name) throws NamingException {
-               Map<String, List<String>> res = new TreeMap<>();
-               Attributes attrs = initialCtx.getAttributes(name);
-               NamingEnumeration<String> ids = attrs.getIDs();
-               while (ids.hasMore()) {
-                       String recordType = ids.next();
-                       List<String> lst = new ArrayList<String>();
-                       res.put(recordType, lst);
-                       Attribute attr = attrs.get(recordType);
-                       addValues(attr, lst);
-               }
-               return Collections.unmodifiableMap(res);
-       }
-
-       /**
-        * Return a single record (typically A, AAAA, etc. or null if not available.
-        * Will fail if multiple records.
-        */
-       public String getRecord(String name, String recordType) throws NamingException {
-               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
-               if (attrs.size() == 0)
-                       return null;
-               Attribute attr = attrs.get(recordType);
-               if (attr.size() > 1)
-                       throw new IllegalArgumentException("Multiple record type " + recordType);
-               assert attr.size() != 0;
-               Object value = attr.get();
-               assert value != null;
-               return value.toString();
-       }
-
-       /**
-        * Return records of a given type.
-        */
-       public List<String> getRecords(String name, String recordType) throws NamingException {
-               List<String> res = new ArrayList<String>();
-               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
-               Attribute attr = attrs.get(recordType);
-               addValues(attr, res);
-               return res;
-       }
-
-       /** Ordered, with preferred first. */
-       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
-               List<String> raw = getRecords(name, "SRV");
-               if (raw.size() == 0)
-                       return null;
-               SortedSet<SrvRecord> res = new TreeSet<>();
-               for (int i = 0; i < raw.size(); i++) {
-                       String record = raw.get(i);
-                       String[] arr = record.split(" ");
-                       Integer priority = Integer.parseInt(arr[0]);
-                       Integer weight = Integer.parseInt(arr[1]);
-                       Integer port = Integer.parseInt(arr[2]);
-                       String hostname = arr[3];
-                       SrvRecord order = new SrvRecord(priority, weight, port, hostname);
-                       res.add(order);
-               }
-               List<String> lst = new ArrayList<>();
-               for (SrvRecord order : res) {
-                       lst.add(order.toHost(withPort));
-               }
-               return Collections.unmodifiableList(lst);
-       }
-
-       private void addValues(Attribute attr, List<String> lst) throws NamingException {
-               NamingEnumeration<?> values = attr.getAll();
-               while (values.hasMore()) {
-                       Object value = values.next();
-                       if (value != null) {
-                               if (value instanceof byte[]) {
-                                       String str = Base64.getEncoder().encodeToString((byte[]) value);
-                                       lst.add(str);
-                               } else
-                                       lst.add(value.toString());
-                       }
-               }
-
-       }
-
-       public List<String> listEntries(String name) throws NamingException {
-               List<String> res = new ArrayList<String>();
-               NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
-               while (ne.hasMore()) {
-                       Binding b = ne.next();
-                       res.add(b.getName());
-               }
-               return Collections.unmodifiableList(res);
-       }
-
-       @Override
-       public void close() throws IOException {
-               destroy();
-       }
-
-       public void destroy() {
-               try {
-                       initialCtx.close();
-               } catch (NamingException e) {
-                       // silent
-               }
-       }
-
-       public static void main(String[] args) {
-               if (args.length == 0) {
-                       printUsage(System.err);
-                       System.exit(1);
-               }
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       String hostname = args[0];
-                       String recordType = args.length > 1 ? args[1] : "A";
-                       if (recordType.equals("*")) {
-                               Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
-                               for (String type : records.keySet()) {
-                                       for (String record : records.get(type)) {
-                                               String typeLabel;
-                                               if ("44".equals(type))
-                                                       typeLabel = "SSHFP";
-                                               else if ("46".equals(type))
-                                                       typeLabel = "RRSIG";
-                                               else if ("48".equals(type))
-                                                       typeLabel = "DNSKEY";
-                                               else
-                                                       typeLabel = type;
-                                               System.out.println(typeLabel + "\t" + record);
-                                       }
-                               }
-                       } else {
-                               System.out.println(dnsBrowser.getRecord(hostname, recordType));
-                       }
-
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public static void printUsage(PrintStream out) {
-               out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv
deleted file mode 100644 (file)
index 676d727..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-uid,,,0.9.2342.19200300.100.1.1,,RFC 4519
-mail,,,0.9.2342.19200300.100.1.3,,RFC 4524
-info,,,0.9.2342.19200300.100.1.4,,RFC 4524
-drink,,,0.9.2342.19200300.100.1.5,,RFC 4524
-roomNumber,,,0.9.2342.19200300.100.1.6,,RFC 4524
-photo,,,0.9.2342.19200300.100.1.7,,RFC 2798
-userClass,,,0.9.2342.19200300.100.1.8,,RFC 4524
-host,,,0.9.2342.19200300.100.1.9,,RFC 4524
-manager,,,0.9.2342.19200300.100.1.10,,RFC 4524
-documentIdentifier,,,0.9.2342.19200300.100.1.11,,RFC 4524
-documentTitle,,,0.9.2342.19200300.100.1.12,,RFC 4524
-documentVersion,,,0.9.2342.19200300.100.1.13,,RFC 4524
-documentAuthor,,,0.9.2342.19200300.100.1.14,,RFC 4524
-documentLocation,,,0.9.2342.19200300.100.1.15,,RFC 4524
-homePhone,,,0.9.2342.19200300.100.1.20,,RFC 4524
-secretary,,,0.9.2342.19200300.100.1.21,,RFC 4524
-dc,,,0.9.2342.19200300.100.1.25,,RFC 4519
-associatedDomain,,,0.9.2342.19200300.100.1.37,,RFC 4524
-associatedName,,,0.9.2342.19200300.100.1.38,,RFC 4524
-homePostalAddress,,,0.9.2342.19200300.100.1.39,,RFC 4524
-personalTitle,,,0.9.2342.19200300.100.1.40,,RFC 4524
-mobile,,,0.9.2342.19200300.100.1.41,,RFC 4524
-pager,,,0.9.2342.19200300.100.1.42,,RFC 4524
-co,,,0.9.2342.19200300.100.1.43,,RFC 4524
-uniqueIdentifier,,,0.9.2342.19200300.100.1.44,,RFC 4524
-organizationalStatus,,,0.9.2342.19200300.100.1.45,,RFC 4524
-buildingName,,,0.9.2342.19200300.100.1.48,,RFC 4524
-audio,,,0.9.2342.19200300.100.1.55,,RFC 2798
-documentPublisher,,,0.9.2342.19200300.100.1.56,,RFC 4524
-jpegPhoto,,,0.9.2342.19200300.100.1.60,,RFC 2798
-vendorName,,,1.3.6.1.1.4,,RFC 3045
-vendorVersion,,,1.3.6.1.1.5,,RFC 3045
-entryUUID,,,1.3.6.1.1.16.4,,RFC 4530
-entryDN,,,1.3.6.1.1.20,,RFC 5020
-labeledURI,,,1.3.6.1.4.1.250.1.57,,RFC 2798
-numSubordinates,,,1.3.6.1.4.1.453.16.2.103,,draft-ietf-boreham-numsubordinates
-namingContexts,,,1.3.6.1.4.1.1466.101.120.5,,RFC 4512
-altServer,,,1.3.6.1.4.1.1466.101.120.6,,RFC 4512
-supportedExtension,,,1.3.6.1.4.1.1466.101.120.7,,RFC 4512
-supportedControl,,,1.3.6.1.4.1.1466.101.120.13,,RFC 4512
-supportedSASLMechanisms,,,1.3.6.1.4.1.1466.101.120.14,,RFC 4512
-supportedLDAPVersion,,,1.3.6.1.4.1.1466.101.120.15,,RFC 4512
-ldapSyntaxes,,,1.3.6.1.4.1.1466.101.120.16,,RFC 4512
-supportedAuthPasswordSchemes,,,1.3.6.1.4.1.4203.1.3.3,,RFC 3112
-authPassword,,,1.3.6.1.4.1.4203.1.3.4,,RFC 3112
-supportedFeatures,,,1.3.6.1.4.1.4203.1.3.5,,RFC 4512
-inheritable,,,1.3.6.1.4.1.7628.5.4.1,,draft-ietf-ldup-subentry
-blockInheritance,,,1.3.6.1.4.1.7628.5.4.2,,draft-ietf-ldup-subentry
-objectClass,,,2.5.4.0,,RFC 4512
-aliasedObjectName,,,2.5.4.1,,RFC 4512
-cn,,,2.5.4.3,,RFC 4519
-sn,,,2.5.4.4,,RFC 4519
-serialNumber,,,2.5.4.5,,RFC 4519
-c,,,2.5.4.6,,RFC 4519
-l,,,2.5.4.7,,RFC 4519
-st,,,2.5.4.8,,RFC 4519
-street,,,2.5.4.9,,RFC 4519
-o,,,2.5.4.10,,RFC 4519
-ou,,,2.5.4.11,,RFC 4519
-title,,,2.5.4.12,,RFC 4519
-description,,,2.5.4.13,,RFC 4519
-searchGuide,,,2.5.4.14,,RFC 4519
-businessCategory,,,2.5.4.15,,RFC 4519
-postalAddress,,,2.5.4.16,,RFC 4519
-postalCode,,,2.5.4.17,,RFC 4519
-postOfficeBox,,,2.5.4.18,,RFC 4519
-physicalDeliveryOfficeName,,,2.5.4.19,,RFC 4519
-telephoneNumber,,,2.5.4.20,,RFC 4519
-telexNumber,,,2.5.4.21,,RFC 4519
-teletexTerminalIdentifier,,,2.5.4.22,,RFC 4519
-facsimileTelephoneNumber,,,2.5.4.23,,RFC 4519
-x121Address,,,2.5.4.24,,RFC 4519
-internationalISDNNumber,,,2.5.4.25,,RFC 4519
-registeredAddress,,,2.5.4.26,,RFC 4519
-destinationIndicator,,,2.5.4.27,,RFC 4519
-preferredDeliveryMethod,,,2.5.4.28,,RFC 4519
-member,,,2.5.4.31,,RFC 4519
-owner,,,2.5.4.32,,RFC 4519
-roleOccupant,,,2.5.4.33,,RFC 4519
-seeAlso,,,2.5.4.34,,RFC 4519
-userPassword,,,2.5.4.35,,RFC 4519
-userCertificate,,,2.5.4.36,,RFC 4523
-cACertificate,,,2.5.4.37,,RFC 4523
-authorityRevocationList,,,2.5.4.38,,RFC 4523
-certificateRevocationList,,,2.5.4.39,,RFC 4523
-crossCertificatePair,,,2.5.4.40,,RFC 4523
-name,,,2.5.4.41,,RFC 4519
-givenName,,,2.5.4.42,,RFC 4519
-initials,,,2.5.4.43,,RFC 4519
-generationQualifier,,,2.5.4.44,,RFC 4519
-x500UniqueIdentifier,,,2.5.4.45,,RFC 4519
-dnQualifier,,,2.5.4.46,,RFC 4519
-enhancedSearchGuide,,,2.5.4.47,,RFC 4519
-distinguishedName,,,2.5.4.49,,RFC 4519
-uniqueMember,,,2.5.4.50,,RFC 4519
-houseIdentifier,,,2.5.4.51,,RFC 4519
-supportedAlgorithms,,,2.5.4.52,,RFC 4523
-deltaRevocationList,,,2.5.4.53,,RFC 4523
-createTimestamp,,,2.5.18.1,,RFC 4512
-modifyTimestamp,,,2.5.18.2,,RFC 4512
-creatorsName,,,2.5.18.3,,RFC 4512
-modifiersName,,,2.5.18.4,,RFC 4512
-subschemaSubentry,,,2.5.18.10,,RFC 4512
-dITStructureRules,,,2.5.21.1,,RFC 4512
-dITContentRules,,,2.5.21.2,,RFC 4512
-matchingRules,,,2.5.21.4,,RFC 4512
-attributeTypes,,,2.5.21.5,,RFC 4512
-objectClasses,,,2.5.21.6,,RFC 4512
-nameForms,,,2.5.21.7,,RFC 4512
-matchingRuleUse,,,2.5.21.8,,RFC 4512
-structuralObjectClass,,,2.5.21.9,,RFC 4512
-governingStructureRule,,,2.5.21.10,,RFC 4512
-carLicense,,,2.16.840.1.113730.3.1.1,,RFC 2798
-departmentNumber,,,2.16.840.1.113730.3.1.2,,RFC 2798
-employeeNumber,,,2.16.840.1.113730.3.1.3,,RFC 2798
-employeeType,,,2.16.840.1.113730.3.1.4,,RFC 2798
-changeNumber,,,2.16.840.1.113730.3.1.5,,draft-good-ldap-changelog
-targetDN,,,2.16.840.1.113730.3.1.6,,draft-good-ldap-changelog
-changeType,,,2.16.840.1.113730.3.1.7,,draft-good-ldap-changelog
-changes,,,2.16.840.1.113730.3.1.8,,draft-good-ldap-changelog
-newRDN,,,2.16.840.1.113730.3.1.9,,draft-good-ldap-changelog
-deleteOldRDN,,,2.16.840.1.113730.3.1.10,,draft-good-ldap-changelog
-newSuperior,,,2.16.840.1.113730.3.1.11,,draft-good-ldap-changelog
-ref,,,2.16.840.1.113730.3.1.34,,RFC 3296
-changelog,,,2.16.840.1.113730.3.1.35,,draft-good-ldap-changelog
-preferredLanguage,,,2.16.840.1.113730.3.1.39,,RFC 2798
-userSMIMECertificate,,,2.16.840.1.113730.3.1.40,,RFC 2798
-userPKCS12,,,2.16.840.1.113730.3.1.216,,RFC 2798
-displayName,,,2.16.840.1.113730.3.1.241,,RFC 2798
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java
deleted file mode 100644 (file)
index 43cfe03..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-package org.argeo.util.naming;
-
-import java.util.function.Supplier;
-
-/**
- * Standard LDAP attributes as per:<br>
- * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
- * - <a href=
- * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
- * LDAP (partial)</a>
- */
-public enum LdapAttrs implements SpecifiedName, Supplier<String> {
-       /** */
-       uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
-       /** */
-       mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
-       /** */
-       info("0.9.2342.19200300.100.1.4", "RFC 4524"),
-       /** */
-       drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
-       /** */
-       roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
-       /** */
-       photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
-       /** */
-       userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
-       /** */
-       host("0.9.2342.19200300.100.1.9", "RFC 4524"),
-       /** */
-       manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
-       /** */
-       documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
-       /** */
-       documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
-       /** */
-       documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
-       /** */
-       documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
-       /** */
-       documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
-       /** */
-       homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
-       /** */
-       secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
-       /** */
-       dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
-       /** */
-       associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
-       /** */
-       associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
-       /** */
-       homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
-       /** */
-       personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
-       /** */
-       mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
-       /** */
-       pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
-       /** */
-       co("0.9.2342.19200300.100.1.43", "RFC 4524"),
-       /** */
-       uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
-       /** */
-       organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
-       /** */
-       buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
-       /** */
-       audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
-       /** */
-       documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
-       /** */
-       jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
-       /** */
-       vendorName("1.3.6.1.1.4", "RFC 3045"),
-       /** */
-       vendorVersion("1.3.6.1.1.5", "RFC 3045"),
-       /** */
-       entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
-       /** */
-       entryDN("1.3.6.1.1.20", "RFC 5020"),
-       /** */
-       labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
-       /** */
-       numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
-       /** */
-       namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
-       /** */
-       altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
-       /** */
-       supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
-       /** */
-       supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
-       /** */
-       supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
-       /** */
-       supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
-       /** */
-       ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
-       /** */
-       supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
-       /** */
-       authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
-       /** */
-       supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
-       /** */
-       inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
-       /** */
-       blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
-       /** */
-       objectClass("2.5.4.0", "RFC 4512"),
-       /** */
-       aliasedObjectName("2.5.4.1", "RFC 4512"),
-       /** */
-       cn("2.5.4.3", "RFC 4519"),
-       /** */
-       sn("2.5.4.4", "RFC 4519"),
-       /** */
-       serialNumber("2.5.4.5", "RFC 4519"),
-       /** */
-       c("2.5.4.6", "RFC 4519"),
-       /** */
-       l("2.5.4.7", "RFC 4519"),
-       /** */
-       st("2.5.4.8", "RFC 4519"),
-       /** */
-       street("2.5.4.9", "RFC 4519"),
-       /** */
-       o("2.5.4.10", "RFC 4519"),
-       /** */
-       ou("2.5.4.11", "RFC 4519"),
-       /** */
-       title("2.5.4.12", "RFC 4519"),
-       /** */
-       description("2.5.4.13", "RFC 4519"),
-       /** */
-       searchGuide("2.5.4.14", "RFC 4519"),
-       /** */
-       businessCategory("2.5.4.15", "RFC 4519"),
-       /** */
-       postalAddress("2.5.4.16", "RFC 4519"),
-       /** */
-       postalCode("2.5.4.17", "RFC 4519"),
-       /** */
-       postOfficeBox("2.5.4.18", "RFC 4519"),
-       /** */
-       physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
-       /** */
-       telephoneNumber("2.5.4.20", "RFC 4519"),
-       /** */
-       telexNumber("2.5.4.21", "RFC 4519"),
-       /** */
-       teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
-       /** */
-       facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
-       /** */
-       x121Address("2.5.4.24", "RFC 4519"),
-       /** */
-       internationalISDNNumber("2.5.4.25", "RFC 4519"),
-       /** */
-       registeredAddress("2.5.4.26", "RFC 4519"),
-       /** */
-       destinationIndicator("2.5.4.27", "RFC 4519"),
-       /** */
-       preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
-       /** */
-       member("2.5.4.31", "RFC 4519"),
-       /** */
-       owner("2.5.4.32", "RFC 4519"),
-       /** */
-       roleOccupant("2.5.4.33", "RFC 4519"),
-       /** */
-       seeAlso("2.5.4.34", "RFC 4519"),
-       /** */
-       userPassword("2.5.4.35", "RFC 4519"),
-       /** */
-       userCertificate("2.5.4.36", "RFC 4523"),
-       /** */
-       cACertificate("2.5.4.37", "RFC 4523"),
-       /** */
-       authorityRevocationList("2.5.4.38", "RFC 4523"),
-       /** */
-       certificateRevocationList("2.5.4.39", "RFC 4523"),
-       /** */
-       crossCertificatePair("2.5.4.40", "RFC 4523"),
-       /** */
-       name("2.5.4.41", "RFC 4519"),
-       /** */
-       givenName("2.5.4.42", "RFC 4519"),
-       /** */
-       initials("2.5.4.43", "RFC 4519"),
-       /** */
-       generationQualifier("2.5.4.44", "RFC 4519"),
-       /** */
-       x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
-       /** */
-       dnQualifier("2.5.4.46", "RFC 4519"),
-       /** */
-       enhancedSearchGuide("2.5.4.47", "RFC 4519"),
-       /** */
-       distinguishedName("2.5.4.49", "RFC 4519"),
-       /** */
-       uniqueMember("2.5.4.50", "RFC 4519"),
-       /** */
-       houseIdentifier("2.5.4.51", "RFC 4519"),
-       /** */
-       supportedAlgorithms("2.5.4.52", "RFC 4523"),
-       /** */
-       deltaRevocationList("2.5.4.53", "RFC 4523"),
-       /** */
-       createTimestamp("2.5.18.1", "RFC 4512"),
-       /** */
-       modifyTimestamp("2.5.18.2", "RFC 4512"),
-       /** */
-       creatorsName("2.5.18.3", "RFC 4512"),
-       /** */
-       modifiersName("2.5.18.4", "RFC 4512"),
-       /** */
-       subschemaSubentry("2.5.18.10", "RFC 4512"),
-       /** */
-       dITStructureRules("2.5.21.1", "RFC 4512"),
-       /** */
-       dITContentRules("2.5.21.2", "RFC 4512"),
-       /** */
-       matchingRules("2.5.21.4", "RFC 4512"),
-       /** */
-       attributeTypes("2.5.21.5", "RFC 4512"),
-       /** */
-       objectClasses("2.5.21.6", "RFC 4512"),
-       /** */
-       nameForms("2.5.21.7", "RFC 4512"),
-       /** */
-       matchingRuleUse("2.5.21.8", "RFC 4512"),
-       /** */
-       structuralObjectClass("2.5.21.9", "RFC 4512"),
-       /** */
-       governingStructureRule("2.5.21.10", "RFC 4512"),
-       /** */
-       carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
-       /** */
-       departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
-       /** */
-       employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
-       /** */
-       employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
-       /** */
-       changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
-       /** */
-       targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
-       /** */
-       changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
-       /** */
-       changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
-       /** */
-       newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
-       /** */
-       deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
-       /** */
-       newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
-       /** */
-       ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
-       /** */
-       changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
-       /** */
-       preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
-       /** */
-       userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
-       /** */
-       userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
-       /** */
-       displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
-
-       // Sun memberOf
-       memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
-
-       // KERBEROS (partial)
-       krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
-
-       // RFC 2985 and RFC 3039 (partial)
-       dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
-       /** */
-       placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
-       /** */
-       gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
-       /** */
-       countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
-       /** */
-       countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
-       //
-       ;
-
-       public final static String DN = "dn";
-
-//     private final static String LDAP_ = "ldap:";
-
-       private final String oid, spec;
-
-       LdapAttrs(String oid, String spec) {
-               this.oid = oid;
-               this.spec = spec;
-       }
-
-       @Override
-       public String getID() {
-               return oid;
-       }
-
-       @Override
-       public String getSpec() {
-               return spec;
-       }
-
-       public String getPrefix() {
-               return prefix();
-       }
-
-       public static String prefix() {
-               return "ldap";
-       }
-
-       @Deprecated
-       public String property() {
-               return get();
-       }
-
-       @Deprecated
-       public String qualified() {
-               return get();
-       }
-
-       public String get() {
-               String prefix = getPrefix();
-               return prefix != null ? prefix + ":" + name() : name();
-       }
-
-       public String getNamespace() {
-               return namespace();
-       }
-
-       public static String namespace() {
-               return "http://www.argeo.org/ns/ldap";
-       }
-
-       @Override
-       public final String toString() {
-               // must return the name
-               return name();
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv
deleted file mode 100644 (file)
index 3d907cb..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-account,,,0.9.2342.19200300.100.4.5,,RFC 4524
-document,,,0.9.2342.19200300.100.4.6,,RFC 4524
-room,,,0.9.2342.19200300.100.4.7,,RFC 4524
-documentSeries,,,0.9.2342.19200300.100.4.9,,RFC 4524
-domain,,,0.9.2342.19200300.100.4.13,,RFC 4524
-rFC822localPart,,,0.9.2342.19200300.100.4.14,,RFC 4524
-domainRelatedObject,,,0.9.2342.19200300.100.4.17,,RFC 4524
-friendlyCountry,,,0.9.2342.19200300.100.4.18,,RFC 4524
-simpleSecurityObject,,,0.9.2342.19200300.100.4.19,,RFC 4524
-uidObject,,,1.3.6.1.1.3.1,,RFC 4519
-extensibleObject,,,1.3.6.1.4.1.1466.101.120.111,,RFC 4512
-dcObject,,,1.3.6.1.4.1.1466.344,,RFC 4519
-authPasswordObject,,,1.3.6.1.4.1.4203.1.4.7,,RFC 3112
-namedObject,,,1.3.6.1.4.1.5322.13.1.1,,draft-howard-namedobject
-inheritableLDAPSubEntry,,,1.3.6.1.4.1.7628.5.6.1.1,,draft-ietf-ldup-subentry
-top,,,2.5.6.0,,RFC 4512
-alias,,,2.5.6.1,,RFC 4512
-country,,,2.5.6.2,,RFC 4519
-locality,,,2.5.6.3,,RFC 4519
-organization,,,2.5.6.4,,RFC 4519
-organizationalUnit,,,2.5.6.5,,RFC 4519
-person,,,2.5.6.6,,RFC 4519
-organizationalPerson,,,2.5.6.7,,RFC 4519
-organizationalRole,,,2.5.6.8,,RFC 4519
-groupOfNames,,,2.5.6.9,,RFC 4519
-residentialPerson,,,2.5.6.10,,RFC 4519
-applicationProcess,,,2.5.6.11,,RFC 4519
-device,,,2.5.6.14,,RFC 4519
-strongAuthenticationUser,,,2.5.6.15,,RFC 4523
-certificationAuthority,,,2.5.6.16,,RFC 4523
-certificationAuthority-V2,,,2.5.6.16.2,,RFC 4523
-groupOfUniqueNames,,,2.5.6.17,,RFC 4519
-userSecurityInformation,,,2.5.6.18,,RFC 4523
-cRLDistributionPoint,,,2.5.6.19,,RFC 4523
-pkiUser,,,2.5.6.21,,RFC 4523
-pkiCA,,,2.5.6.22,,RFC 4523
-deltaCRL,,,2.5.6.23,,RFC 4523
-subschema,,,2.5.20.1,,RFC 4512
-ldapSubEntry,,,2.16.840.1.113719.2.142.6.1.1,,draft-ietf-ldup-subentry
-changeLogEntry,,,2.16.840.1.113730.3.2.1,,draft-good-ldap-changelog
-inetOrgPerson,,,2.16.840.1.113730.3.2.2,,RFC 2798
-referral,,,2.16.840.1.113730.3.2.6,,RFC 3296
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java
deleted file mode 100644 (file)
index c616d14..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.argeo.util.naming;
-
-/**
- * Standard LDAP object classes as per
- * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
- * oid-reference</a>
- */
-public enum LdapObjs implements SpecifiedName {
-       account("0.9.2342.19200300.100.4.5", "RFC 4524"),
-       /** */
-       document("0.9.2342.19200300.100.4.6", "RFC 4524"),
-       /** */
-       room("0.9.2342.19200300.100.4.7", "RFC 4524"),
-       /** */
-       documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
-       /** */
-       domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
-       /** */
-       rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
-       /** */
-       domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
-       /** */
-       friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
-       /** */
-       simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
-       /** */
-       uidObject("1.3.6.1.1.3.1", "RFC 4519"),
-       /** */
-       extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
-       /** */
-       dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
-       /** */
-       authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
-       /** */
-       namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
-       /** */
-       inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
-       /** */
-       top("2.5.6.0", "RFC 4512"),
-       /** */
-       alias("2.5.6.1", "RFC 4512"),
-       /** */
-       country("2.5.6.2", "RFC 4519"),
-       /** */
-       locality("2.5.6.3", "RFC 4519"),
-       /** */
-       organization("2.5.6.4", "RFC 4519"),
-       /** */
-       organizationalUnit("2.5.6.5", "RFC 4519"),
-       /** */
-       person("2.5.6.6", "RFC 4519"),
-       /** */
-       organizationalPerson("2.5.6.7", "RFC 4519"),
-       /** */
-       organizationalRole("2.5.6.8", "RFC 4519"),
-       /** */
-       groupOfNames("2.5.6.9", "RFC 4519"),
-       /** */
-       residentialPerson("2.5.6.10", "RFC 4519"),
-       /** */
-       applicationProcess("2.5.6.11", "RFC 4519"),
-       /** */
-       device("2.5.6.14", "RFC 4519"),
-       /** */
-       strongAuthenticationUser("2.5.6.15", "RFC 4523"),
-       /** */
-       certificationAuthority("2.5.6.16", "RFC 4523"),
-       // /** Should be certificationAuthority-V2 */
-       // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
-       // },
-       /** */
-       groupOfUniqueNames("2.5.6.17", "RFC 4519"),
-       /** */
-       userSecurityInformation("2.5.6.18", "RFC 4523"),
-       /** */
-       cRLDistributionPoint("2.5.6.19", "RFC 4523"),
-       /** */
-       pkiUser("2.5.6.21", "RFC 4523"),
-       /** */
-       pkiCA("2.5.6.22", "RFC 4523"),
-       /** */
-       deltaCRL("2.5.6.23", "RFC 4523"),
-       /** */
-       subschema("2.5.20.1", "RFC 4512"),
-       /** */
-       ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
-       /** */
-       changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
-       /** */
-       inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
-       /** */
-       referral("2.16.840.1.113730.3.2.6", "RFC 3296");
-
-       private final static String LDAP_ = "ldap:";
-       private final String oid, spec;
-
-       private LdapObjs(String oid, String spec) {
-               this.oid = oid;
-               this.spec = spec;
-       }
-
-       public String getOid() {
-               return oid;
-       }
-
-       public String getSpec() {
-               return spec;
-       }
-
-       public String property() {
-               return new StringBuilder(LDAP_).append(name()).toString();
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifParser.java b/org.argeo.util/src/org/argeo/util/naming/LdifParser.java
deleted file mode 100644 (file)
index d68173a..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.argeo.util.naming;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF parser. */
-public class LdifParser {
-       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
-       protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
-                       Attributes currentAttributes) {
-               try {
-                       Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
-                       Attribute nameAttr = currentAttributes.get(nameRdn.getType());
-                       if (nameAttr == null)
-                               currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
-                       else if (!nameAttr.get().equals(nameRdn.getValue()))
-                               throw new UserDirectoryException(
-                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
-                                                               + " (shortly before line " + lineNumber + " in LDIF file)");
-                       Attributes previous = res.put(currentDn, currentAttributes);
-                       return previous;
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot add " + currentDn, e);
-               }
-       }
-
-       /** With UTF-8 charset */
-       public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
-               try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
-                       return read(reader);
-               } finally {
-                       try {
-                               in.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-       }
-
-       /** Will close the reader. */
-       public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
-               SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
-               try {
-                       List<String> lines = new ArrayList<>();
-                       try (BufferedReader br = new BufferedReader(reader)) {
-                               String line;
-                               while ((line = br.readLine()) != null) {
-                                       lines.add(line);
-                               }
-                       }
-                       if (lines.size() == 0)
-                               return res;
-                       // add an empty new line since the last line is not checked
-                       if (!lines.get(lines.size() - 1).equals(""))
-                               lines.add("");
-
-                       LdapName currentDn = null;
-                       Attributes currentAttributes = null;
-                       StringBuilder currentEntry = new StringBuilder();
-
-                       readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
-                               String line = lines.get(lineNumber);
-                               boolean isLastLine = false;
-                               if (lineNumber == lines.size() - 1)
-                                       isLastLine = true;
-                               if (line.startsWith(" ")) {
-                                       currentEntry.append(line.substring(1));
-                                       if (!isLastLine)
-                                               continue readLines;
-                               }
-
-                               if (currentEntry.length() != 0 || isLastLine) {
-                                       // read previous attribute
-                                       StringBuilder attrId = new StringBuilder(8);
-                                       boolean isBase64 = false;
-                                       readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
-                                               char c = currentEntry.charAt(i);
-                                               if (c == ':') {
-                                                       if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
-                                                               isBase64 = true;
-                                                       currentEntry.delete(0, i + (isBase64 ? 2 : 1));
-                                                       break readAttrId;
-                                               } else {
-                                                       attrId.append(c);
-                                               }
-                                       }
-
-                                       String attributeId = attrId.toString();
-                                       // TODO should we really trim the end of the string as well?
-                                       String cleanValueStr = currentEntry.toString().trim();
-                                       Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
-
-                                       // manage DN attributes
-                                       if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
-                                               if (currentDn != null) {
-                                                       //
-                                                       // ADD
-                                                       //
-                                                       Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
-                                                       if (previous != null) {
-//                                                             log.warn("There was already an entry with DN " + currentDn
-//                                                                             + ", which has been discarded by a subsequent one.");
-                                                       }
-                                               }
-
-                                               if (attributeId.equals(LdapAttrs.DN))
-                                                       try {
-                                                               currentDn = new LdapName(attributeValue.toString());
-                                                               currentAttributes = new BasicAttributes(true);
-                                                       } catch (InvalidNameException e) {
-//                                                             log.error(attributeValue + " not a valid DN, skipping the entry.");
-                                                               currentDn = null;
-                                                               currentAttributes = null;
-                                                       }
-                                       }
-
-                                       // store attribute
-                                       if (currentAttributes != null) {
-                                               Attribute attribute = currentAttributes.get(attributeId);
-                                               if (attribute == null) {
-                                                       attribute = new BasicAttribute(attributeId);
-                                                       currentAttributes.put(attribute);
-                                               }
-                                               attribute.add(attributeValue);
-                                       }
-                                       currentEntry = new StringBuilder();
-                               }
-                               currentEntry.append(line);
-                       }
-               } finally {
-                       try {
-                               reader.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-               return res;
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java b/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java
deleted file mode 100644 (file)
index 457380b..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.util.naming;
-
-import static org.argeo.util.naming.LdapAttrs.DN;
-import static org.argeo.util.naming.LdapAttrs.member;
-import static org.argeo.util.naming.LdapAttrs.objectClass;
-import static org.argeo.util.naming.LdapAttrs.uniqueMember;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF writer */
-public class LdifWriter {
-       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-       private final Writer writer;
-
-       /** Writer must be closed by caller */
-       public LdifWriter(Writer writer) {
-               this.writer = writer;
-       }
-
-       /** Stream must be closed by caller */
-       public LdifWriter(OutputStream out) {
-               this(new OutputStreamWriter(out, DEFAULT_CHARSET));
-       }
-
-       public void writeEntry(LdapName name, Attributes attributes) throws IOException {
-               try {
-                       // check consistency
-                       Rdn nameRdn = name.getRdn(name.size() - 1);
-                       Attribute nameAttr = attributes.get(nameRdn.getType());
-                       if (!nameAttr.get().equals(nameRdn.getValue()))
-                               throw new UserDirectoryException(
-                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
-
-                       writer.append(DN + ": ").append(name.toString()).append('\n');
-                       Attribute objectClassAttr = attributes.get(objectClass.name());
-                       if (objectClassAttr != null)
-                               writeAttribute(objectClassAttr);
-                       attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
-                               Attribute attribute = attrs.next();
-                               if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
-                                       continue attributes;// skip DN attribute
-                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
-                                       continue attributes;// skip member and uniqueMember attributes, so that they are always written last
-                               writeAttribute(attribute);
-                       }
-                       // write member and uniqueMember attributes last
-                       for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
-                               Attribute attribute = attrs.next();
-                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
-                                       writeMemberAttribute(attribute);
-                       }
-                       writer.append('\n');
-                       writer.flush();
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot write LDIF", e);
-               }
-       }
-
-       public void write(Map<LdapName, Attributes> entries) throws IOException {
-               for (LdapName dn : entries.keySet())
-                       writeEntry(dn, entries.get(dn));
-       }
-
-       protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
-               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
-                       Object value = attrValues.next();
-                       if (value instanceof byte[]) {
-                               String encoded = Base64.getEncoder().encodeToString((byte[]) value);
-                               writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
-                       } else {
-                               writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
-                       }
-               }
-       }
-
-       protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
-               // Note: duplicate entries will be swallowed
-               SortedSet<String> values = new TreeSet<>();
-               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
-                       String value = attrValues.next().toString();
-                       values.add(value);
-               }
-
-               for (String value : values) {
-                       writer.append(attribute.getID()).append(": ").append(value).append('\n');
-               }
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java b/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java
deleted file mode 100644 (file)
index ff4ed31..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.util.naming;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.ChronoField;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-public class NamingUtils {
-       /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
-       private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
-                       .withZone(ZoneOffset.UTC);
-
-       /** @return null if not parseable */
-       public static Instant ldapDateToInstant(String ldapDate) {
-               try {
-                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
-               } catch (DateTimeParseException e) {
-                       return null;
-               }
-       }
-
-       /** @return null if not parseable */
-       public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
-               try {
-                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
-               } catch (DateTimeParseException e) {
-                       return null;
-               }
-       }
-
-       public static Calendar ldapDateToCalendar(String ldapDate) {
-               OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
-               GregorianCalendar calendar = new GregorianCalendar();
-               calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
-               calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
-               calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
-               return calendar;
-       }
-
-       public static String instantToLdapDate(ZonedDateTime instant) {
-               return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
-       }
-
-       public static String getQueryValue(Map<String, List<String>> query, String key) {
-               if (!query.containsKey(key))
-                       return null;
-               List<String> val = query.get(key);
-               if (val.size() == 1)
-                       return val.get(0);
-               else
-                       throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
-       }
-
-       public static Map<String, List<String>> queryToMap(URI uri) {
-               return queryToMap(uri.getQuery());
-       }
-
-       private static Map<String, List<String>> queryToMap(String queryPart) {
-               try {
-                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
-                       if (queryPart == null)
-                               return query_pairs;
-                       final String[] pairs = queryPart.split("&");
-                       for (String pair : pairs) {
-                               final int idx = pair.indexOf("=");
-                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
-                                               : pair;
-                               if (!query_pairs.containsKey(key)) {
-                                       query_pairs.put(key, new LinkedList<String>());
-                               }
-                               final String value = idx > 0 && pair.length() > idx + 1
-                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
-                                               : null;
-                               query_pairs.get(key).add(value);
-                       }
-                       return query_pairs;
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
-               }
-       }
-
-       private NamingUtils() {
-
-       }
-
-       public static void main(String args[]) {
-               ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
-               String str = utcLdapDate.format(now);
-               System.out.println(str);
-               utcLdapDate.parse(str);
-               utcLdapDate.parse("19520512000000Z");
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java b/org.argeo.util/src/org/argeo/util/naming/NodeOID.java
deleted file mode 100644 (file)
index ea163d6..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.util.naming;
-
-interface NodeOID {
-       String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
-
-       // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
-       String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
-       
-       // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
-       String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
-       
-       // ATTRIBUTE TYPES
-       String ATTRIBUTE_TYPES = BASE + ".4";
-
-       // OBJECT CLASSES
-       String OBJECT_CLASSES = BASE + ".6";
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java b/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java
deleted file mode 100644 (file)
index 7f05754..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.util.naming;
-
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-
-public class SharedSecret extends AuthPassword {
-       public final static String X_SHARED_SECRET = "X-SharedSecret";
-       private final Instant expiry;
-
-       public SharedSecret(String authInfo, String authValue) {
-               super(authInfo, authValue);
-               expiry = null;
-       }
-
-       public SharedSecret(AuthPassword authPassword) {
-               super(authPassword);
-               String authInfo = getAuthInfo();
-               if (authInfo.length() == 16) {
-                       expiry = NamingUtils.ldapDateToInstant(authInfo);
-               } else {
-                       expiry = null;
-               }
-       }
-
-       public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
-               super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
-               expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
-       }
-
-       public SharedSecret(int hours, String value) {
-               this(ZonedDateTime.now().plusHours(hours), value);
-       }
-
-       @Override
-       protected String getExpectedAuthScheme() {
-               return X_SHARED_SECRET;
-       }
-
-       public boolean isExpired() {
-               if (expiry == null)
-                       return false;
-               return expiry.isBefore(Instant.now());
-       }
-
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java b/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java
deleted file mode 100644 (file)
index 22f2a2d..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.util.naming;
-
-/**
- * A name which has been specified and for which an id has been defined
- * (typically an OID).
- */
-public interface SpecifiedName {
-       /** The name */
-       String name();
-
-       /** An RFC or the URLof some specification */
-       default String getSpec() {
-               return null;
-       }
-
-       /** Typically an OID */
-       default String getID() {
-               return getClass().getName() + "." + name();
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java b/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java
deleted file mode 100644 (file)
index f2476a9..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.argeo.util.naming;
-
-class SrvRecord implements Comparable<SrvRecord> {
-       private final Integer priority;
-       private final Integer weight;
-       private final Integer port;
-       private final String hostname;
-
-       public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
-               this.priority = priority;
-               this.weight = weight;
-               this.port = port;
-               this.hostname = hostname;
-       }
-
-       @Override
-       public int compareTo(SrvRecord other) {
-               // https: // en.wikipedia.org/wiki/SRV_record
-               if (priority != other.priority)
-                       return priority - other.priority;
-               if (weight != other.weight)
-                       return other.weight - other.weight;
-               String host = toHost(false);
-               String otherHost = other.toHost(false);
-               if (host.length() == otherHost.length())
-                       return host.compareTo(otherHost);
-               else
-                       return host.length() - otherHost.length();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof SrvRecord) {
-                       SrvRecord other = (SrvRecord) obj;
-                       return priority == other.priority && weight == other.weight && port == other.port
-                                       && hostname.equals(other.hostname);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return priority + " " + weight;
-       }
-
-       public String toHost(boolean withPort) {
-               String hostStr = hostname;
-               if (hostname.charAt(hostname.length() - 1) == '.')
-                       hostStr = hostname.substring(0, hostname.length() - 1);
-               return hostStr + (withPort ? ":" + port : "");
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/naming/package-info.java b/org.argeo.util/src/org/argeo/util/naming/package-info.java
deleted file mode 100644 (file)
index f62af36..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic naming and LDAP support. */
-package org.argeo.util.naming;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/package-info.java b/org.argeo.util/src/org/argeo/util/package-info.java
deleted file mode 100644 (file)
index 4354b0a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic Java utilities. */
-package org.argeo.util;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/register/Component.java b/org.argeo.util/src/org/argeo/util/register/Component.java
deleted file mode 100644 (file)
index 4a812f8..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * A wrapper for an object, whose dependencies and life cycle can be managed.
- */
-public class Component<I> {
-
-       private final I instance;
-
-       private final Runnable init;
-       private final Runnable close;
-
-       private final Map<Class<? super I>, PublishedType<? super I>> types;
-       private final Set<Dependency<?>> dependencies;
-
-       private CompletableFuture<Void> activationStarted = null;
-       private CompletableFuture<Void> activated = null;
-
-       private CompletableFuture<Void> deactivationStarted = null;
-       private CompletableFuture<Void> deactivated = null;
-
-       private Set<Dependency<?>> dependants = new HashSet<>();
-
-       Component(Consumer<Component<?>> register, I instance, Runnable init, Runnable close,
-                       Set<Dependency<?>> dependencies, Set<Class<? super I>> classes) {
-               assert instance != null;
-               assert init != null;
-               assert close != null;
-               assert dependencies != null;
-               assert classes != null;
-
-               this.instance = instance;
-               this.init = init;
-               this.close = close;
-
-               // types
-               Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
-               for (Class<? super I> clss : classes) {
-//                     if (!clss.isAssignableFrom(instance.getClass()))
-//                             throw new IllegalArgumentException(
-//                                             "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
-                       types.put(clss, new PublishedType<>(this, clss));
-               }
-               this.types = Collections.unmodifiableMap(types);
-
-               // dependencies
-               this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
-               for (Dependency<?> dependency : this.dependencies) {
-                       dependency.setDependantComponent(this);
-               }
-
-               // deactivated by default
-               deactivated = CompletableFuture.completedFuture(null);
-               deactivationStarted = CompletableFuture.completedFuture(null);
-
-               // TODO check whether context is active, so that we start right away
-               prepareNextActivation();
-
-               register.accept(this);
-       }
-
-       private void prepareNextActivation() {
-               activationStarted = new CompletableFuture<Void>();
-               activated = activationStarted //
-                               .thenComposeAsync(this::dependenciesActivated) //
-                               .thenRun(this.init) //
-                               .thenRun(() -> prepareNextDeactivation());
-       }
-
-       private void prepareNextDeactivation() {
-               deactivationStarted = new CompletableFuture<Void>();
-               deactivated = deactivationStarted //
-                               .thenComposeAsync(this::dependantsDeactivated) //
-                               .thenRun(this.close) //
-                               .thenRun(() -> prepareNextActivation());
-       }
-
-       public CompletableFuture<Void> getActivated() {
-               return activated;
-       }
-
-       public CompletableFuture<Void> getDeactivated() {
-               return deactivated;
-       }
-
-       void startActivating() {
-               if (activated.isDone() || activationStarted.isDone())
-                       return;
-               activationStarted.complete(null);
-       }
-
-       void startDeactivating() {
-               if (deactivated.isDone() || deactivationStarted.isDone())
-                       return;
-               deactivationStarted.complete(null);
-       }
-
-       CompletableFuture<Void> dependenciesActivated(Void v) {
-               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
-               for (Dependency<?> dependency : this.dependencies) {
-                       CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
-                                       .thenCompose(dependency::set);
-                       constraints.add(dependencyActivated);
-               }
-               return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
-       }
-
-       CompletableFuture<Void> dependantsDeactivated(Void v) {
-               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
-               for (Dependency<?> dependant : this.dependants) {
-                       CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
-                                       .thenCompose(dependant::unset);
-                       constraints.add(dependantDeactivated);
-               }
-               CompletableFuture<Void> dependantsDeactivated = CompletableFuture
-                               .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
-               return dependantsDeactivated;
-
-       }
-
-       void addDependant(Dependency<?> dependant) {
-               dependants.add(dependant);
-       }
-
-       I getInstance() {
-               return instance;
-       }
-
-       @SuppressWarnings("unchecked")
-       <T> PublishedType<T> getType(Class<T> clss) {
-               if (!types.containsKey(clss))
-                       throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
-               return (PublishedType<T>) types.get(clss);
-       }
-
-       <T> boolean isPublishedType(Class<T> clss) {
-               return types.containsKey(clss);
-       }
-
-       public static class PublishedType<T> {
-               private Component<? extends T> component;
-               private Class<T> clss;
-
-//             private CompletableFuture<Component<? extends T>> publisherAvailable;
-               private CompletableFuture<T> value;
-
-               public PublishedType(Component<? extends T> component, Class<T> clss) {
-                       this.clss = clss;
-                       this.component = component;
-                       value = CompletableFuture.completedFuture((T) component.instance);
-//                     value = publisherAvailable.thenApply((c) -> c.getInstance());
-               }
-
-               Component<?> getPublisher() {
-                       return component;
-               }
-
-//             CompletableFuture<Component<? extends T>> publisherAvailable() {
-//                     return publisherAvailable;
-//             }
-
-               Class<T> getType() {
-                       return clss;
-               }
-       }
-
-       public static class Builder<I> {
-               private final I instance;
-
-               private Runnable init;
-               private Runnable close;
-
-               private Set<Dependency<?>> dependencies = new HashSet<>();
-               private Set<Class<? super I>> types = new HashSet<>();
-
-               public Builder(I instance) {
-                       this.instance = instance;
-               }
-
-               public Component<I> build(Consumer<Component<?>> register) {
-                       // default values
-                       if (types.isEmpty()) {
-                               types.add(getInstanceClass());
-                       }
-
-                       if (init == null)
-                               init = () -> {
-                               };
-                       if (close == null)
-                               close = () -> {
-                               };
-
-                       // instantiation
-                       Component<I> component = new Component<I>(register, instance, init, close, dependencies, types);
-                       for (Dependency<?> dependency : dependencies) {
-                               dependency.type.getPublisher().addDependant(dependency);
-                       }
-                       return component;
-               }
-
-               public Builder<I> addType(Class<? super I> clss) {
-                       types.add(clss);
-                       return this;
-               }
-
-               public Builder<I> addInit(Runnable init) {
-                       if (this.init != null)
-                               throw new IllegalArgumentException("init method is already set");
-                       this.init = init;
-                       return this;
-               }
-
-               public Builder<I> addClose(Runnable close) {
-                       if (this.close != null)
-                               throw new IllegalArgumentException("close method is already set");
-                       this.close = close;
-                       return this;
-               }
-
-               public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
-                       dependencies.add(new Dependency<D>(type, set, unset));
-                       return this;
-               }
-
-               public I get() {
-                       return instance;
-               }
-
-               @SuppressWarnings("unchecked")
-               private Class<I> getInstanceClass() {
-                       return (Class<I>) instance.getClass();
-               }
-
-       }
-
-       static class Dependency<D> {
-               private PublishedType<D> type;
-               private Consumer<D> set;
-               private Consumer<D> unset;
-
-               // live
-               Component<?> dependantComponent;
-               CompletableFuture<Void> setStage;
-               CompletableFuture<Void> unsetStage;
-
-               public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
-                       super();
-                       this.type = types;
-                       this.set = set;
-                       this.unset = unset != null ? unset : (v) -> set.accept(null);
-               }
-
-               // live
-               void setDependantComponent(Component<?> component) {
-                       this.dependantComponent = component;
-               }
-
-               CompletableFuture<Void> publisherActivated() {
-                       return type.getPublisher().activated.copy();
-               }
-
-               CompletableFuture<Void> dependantDeactivated() {
-                       return dependantComponent.deactivated.copy();
-               }
-
-               CompletableFuture<Void> set(Void v) {
-                       return type.value.thenAccept(set);
-               }
-
-               CompletableFuture<Void> unset(Void v) {
-                       return type.value.thenAccept(unset);
-               }
-
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/register/Register.java b/org.argeo.util/src/org/argeo/util/register/Register.java
deleted file mode 100644 (file)
index 1706280..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.Map;
-
-/** A dynamic register of objects. */
-public interface Register {
-       <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes);
-
-       void shutdown();
-}
diff --git a/org.argeo.util/src/org/argeo/util/register/Singleton.java b/org.argeo.util/src/org/argeo/util/register/Singleton.java
deleted file mode 100644 (file)
index 5d70e9a..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Future;
-import java.util.function.Consumer;
-
-public class Singleton<T> {
-       private final Class<T> clss;
-       private final CompletableFuture<T> registrationStage;
-       private final List<Consumer<T>> unregistrationHooks = new ArrayList<>();
-
-       public Singleton(Class<T> clss, CompletableFuture<T> registrationStage) {
-               this.clss = clss;
-               this.registrationStage = registrationStage;
-       }
-
-       CompletionStage<T> getRegistrationStage() {
-               return registrationStage.minimalCompletionStage();
-       }
-
-       public void addUnregistrationHook(Consumer<T> todo) {
-               unregistrationHooks.add(todo);
-       }
-
-       public Future<T> getValue() {
-               return registrationStage.copy();
-       }
-
-       public CompletableFuture<Void> prepareUnregistration(Void v) {
-               List<CompletableFuture<Void>> lst = new ArrayList<>();
-               for (Consumer<T> hook : unregistrationHooks) {
-                       lst.add(registrationStage.thenAcceptAsync(hook));
-               }
-               CompletableFuture<Void> prepareUnregistrationStage = CompletableFuture
-                               .allOf(lst.toArray(new CompletableFuture<?>[lst.size()]));
-               return prepareUnregistrationStage;
-       }
-}
diff --git a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java b/org.argeo.util/src/org/argeo/util/register/StaticRegister.java
deleted file mode 100644 (file)
index c186aff..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.argeo.util.register;
-
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/** A minimal component register. */
-public class StaticRegister {
-       private final static AtomicBoolean started = new AtomicBoolean(false);
-       private final static IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
-
-       public static Consumer<Component<?>> asConsumer() {
-               return (c) -> registerComponent(c);
-       }
-
-//     public static BiFunction<Class<?>, Predicate<Map<String, Object>>, Component<?>> asProvider() {
-//
-//     }
-
-       static synchronized <T> Component<? extends T> find(Class<T> clss, Predicate<Map<String, Object>> filter) {
-               Set<Component<? extends T>> result = new HashSet<>();
-               instances: for (Object instance : components.keySet()) {
-                       if (!clss.isAssignableFrom(instance.getClass()))
-                               continue instances;
-                       Component<? extends T> component = (Component<? extends T>) components.get(instance);
-
-                       // TODO filter
-                       if (component.isPublishedType(clss))
-                               result.add(component);
-               }
-               if (result.isEmpty())
-                       return null;
-               return result.iterator().next();
-
-       }
-
-       static synchronized void registerComponent(Component<?> component) {
-               if (started.get()) // TODO make it really dynamic
-                       throw new IllegalStateException("Already activated");
-               if (components.containsKey(component.getInstance()))
-                       throw new IllegalArgumentException("Already registered as component");
-               components.put(component.getInstance(), component);
-       }
-
-       static synchronized Component<?> get(Object instance) {
-               if (!components.containsKey(instance))
-                       throw new IllegalArgumentException("Not registered as component");
-               return components.get(instance);
-       }
-
-       synchronized static void activate() {
-               if (started.get())
-                       throw new IllegalStateException("Already activated");
-               Set<CompletableFuture<?>> constraints = new HashSet<>();
-               for (Component<?> component : components.values()) {
-                       component.startActivating();
-                       constraints.add(component.getActivated());
-               }
-
-               // wait
-               try {
-                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
-                                       .get();
-               } catch (ExecutionException | InterruptedException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       synchronized static void deactivate() {
-               if (!started.get())
-                       throw new IllegalStateException("Not activated");
-               Set<CompletableFuture<?>> constraints = new HashSet<>();
-               for (Component<?> component : components.values()) {
-                       component.startDeactivating();
-                       constraints.add(component.getDeactivated());
-               }
-
-               // wait
-               try {
-                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
-                                       .get();
-               } catch (ExecutionException | InterruptedException e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       synchronized static void clear() {
-               components.clear();
-       }
-
-}
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/.classpath b/osgi/equinox/org.argeo.cms.lib.equinox/.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/osgi/equinox/org.argeo.cms.lib.equinox/.gitignore b/osgi/equinox/org.argeo.cms.lib.equinox/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/.project b/osgi/equinox/org.argeo.cms.lib.equinox/.project
new file mode 100644 (file)
index 0000000..c551d5d
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.lib.equinox</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore b/osgi/equinox/org.argeo.cms.lib.equinox/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml b/osgi/equinox/org.argeo.cms.lib.equinox/OSGI-INF/jettyServiceFactory.xml
new file mode 100644 (file)
index 0000000..6a13362
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
+   <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
+   <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpServer"/>
+   </service>
+</scr:component>
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd b/osgi/equinox/org.argeo.cms.lib.equinox/bnd.bnd
new file mode 100644 (file)
index 0000000..2c83158
--- /dev/null
@@ -0,0 +1,2 @@
+Service-Component: \
+OSGI-INF/jettyServiceFactory.xml,\
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/build.properties b/osgi/equinox/org.argeo.cms.lib.equinox/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/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/equinox/http/jetty/EquinoxJettyServer.java
new file mode 100644 (file)
index 0000000..e6595a0
--- /dev/null
@@ -0,0 +1,151 @@
+package org.argeo.cms.equinox.http.jetty;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionIdListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.argeo.cms.jetty.CmsJettyServer;
+import org.eclipse.equinox.http.servlet.HttpServiceServlet;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.osgi.framework.Constants;
+
+/** A {@link CmsJettyServer} integrating with Equinox HTTP framework. */
+public class EquinoxJettyServer extends CmsJettyServer {
+       private static final String INTERNAL_CONTEXT_CLASSLOADER = "org.eclipse.equinox.http.jetty.internal.ContextClassLoader";
+
+       @Override
+       protected void addServlets(ServletContextHandler rootContextHandler) throws ServletException {
+               ServletHolder holder = new ServletHolder(new InternalHttpServiceServlet());
+               holder.setInitOrder(0);
+               holder.setInitParameter(Constants.SERVICE_VENDOR, "Eclipse.org"); //$NON-NLS-1$
+               holder.setInitParameter(Constants.SERVICE_DESCRIPTION, "Equinox Jetty-based Http Service"); //$NON-NLS-1$
+
+               rootContextHandler.addServlet(holder, "/*");
+
+               // post-start
+               SessionHandler sessionManager = rootContextHandler.getSessionHandler();
+               sessionManager.addEventListener((HttpSessionIdListener) holder.getServlet());
+       }
+
+       public static class InternalHttpServiceServlet implements HttpSessionListener, HttpSessionIdListener, Servlet {
+               private final Servlet httpServiceServlet = new HttpServiceServlet();
+               private ClassLoader contextLoader;
+               private final Method sessionDestroyed;
+               private final Method sessionIdChanged;
+
+               public InternalHttpServiceServlet() {
+                       Class<?> clazz = httpServiceServlet.getClass();
+
+                       try {
+                               sessionDestroyed = clazz.getMethod("sessionDestroyed", new Class<?>[] { String.class }); //$NON-NLS-1$
+                       } catch (Exception e) {
+                               throw new IllegalStateException(e);
+                       }
+                       try {
+                               sessionIdChanged = clazz.getMethod("sessionIdChanged", new Class<?>[] { String.class }); //$NON-NLS-1$
+                       } catch (Exception e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+
+               @Override
+               public void init(ServletConfig config) throws ServletException {
+                       ServletContext context = config.getServletContext();
+                       contextLoader = (ClassLoader) context.getAttribute(INTERNAL_CONTEXT_CLASSLOADER);
+
+                       Thread thread = Thread.currentThread();
+                       ClassLoader current = thread.getContextClassLoader();
+                       thread.setContextClassLoader(contextLoader);
+                       try {
+                               httpServiceServlet.init(config);
+                       } finally {
+                               thread.setContextClassLoader(current);
+                       }
+               }
+
+               @Override
+               public void destroy() {
+                       Thread thread = Thread.currentThread();
+                       ClassLoader current = thread.getContextClassLoader();
+                       thread.setContextClassLoader(contextLoader);
+                       try {
+                               httpServiceServlet.destroy();
+                       } finally {
+                               thread.setContextClassLoader(current);
+                       }
+                       contextLoader = null;
+               }
+
+               @Override
+               public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
+                       Thread thread = Thread.currentThread();
+                       ClassLoader current = thread.getContextClassLoader();
+                       thread.setContextClassLoader(contextLoader);
+                       try {
+                               httpServiceServlet.service(req, res);
+                       } finally {
+                               thread.setContextClassLoader(current);
+                       }
+               }
+
+               @Override
+               public ServletConfig getServletConfig() {
+                       return httpServiceServlet.getServletConfig();
+               }
+
+               @Override
+               public String getServletInfo() {
+                       return httpServiceServlet.getServletInfo();
+               }
+
+               @Override
+               public void sessionCreated(HttpSessionEvent event) {
+                       // Nothing to do.
+               }
+
+               @Override
+               public void sessionDestroyed(HttpSessionEvent event) {
+                       Thread thread = Thread.currentThread();
+                       ClassLoader current = thread.getContextClassLoader();
+                       thread.setContextClassLoader(contextLoader);
+                       try {
+                               sessionDestroyed.invoke(httpServiceServlet, event.getSession().getId());
+                       } catch (IllegalAccessException | IllegalArgumentException e) {
+                               // not likely
+                       } catch (InvocationTargetException e) {
+                               throw new RuntimeException(e.getCause());
+                       } finally {
+                               thread.setContextClassLoader(current);
+                       }
+               }
+
+               @Override
+               public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
+                       Thread thread = Thread.currentThread();
+                       ClassLoader current = thread.getContextClassLoader();
+                       thread.setContextClassLoader(contextLoader);
+                       try {
+                               sessionIdChanged.invoke(httpServiceServlet, oldSessionId);
+                       } catch (IllegalAccessException | IllegalArgumentException e) {
+                               // not likely
+                       } catch (InvocationTargetException e) {
+                               throw new RuntimeException(e.getCause());
+                       } finally {
+                               thread.setContextClassLoader(current);
+                       }
+               }
+       }
+
+}
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyConfig.java
new file mode 100644 (file)
index 0000000..2cd6001
--- /dev/null
@@ -0,0 +1,231 @@
+package org.argeo.cms.servlet.internal.jetty;
+
+import java.io.File;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+import javax.websocket.server.ServerEndpointConfig;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+import org.argeo.cms.util.LangUtils;
+import org.argeo.cms.websocket.server.CmsWebSocketConfigurator;
+import org.argeo.cms.websocket.server.TestEndpoint;
+import org.eclipse.equinox.http.jetty.JettyConfigurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class JettyConfig {
+       private final static CmsLog log = CmsLog.getLog(JettyConfig.class);
+
+       final static String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+       private CmsState cmsState;
+
+       private final BundleContext bc = FrameworkUtil.getBundle(JettyConfig.class).getBundleContext();
+
+       // private static final String JETTY_PROPERTY_PREFIX =
+       // "org.eclipse.equinox.http.jetty.";
+
+       public void start() {
+               // We need to start asynchronously so that Jetty bundle get started by lazy init
+               // due to the non-configurable behaviour of its activator
+               ForkJoinPool.commonPool().execute(() -> {
+                       Dictionary<String, Object> properties = getHttpServerConfig();
+                       startServer(properties);
+               });
+
+               ServiceTracker<ServerContainer, ServerContainer> serverSt = new ServiceTracker<ServerContainer, ServerContainer>(
+                               bc, ServerContainer.class, null) {
+
+                       @Override
+                       public ServerContainer addingService(ServiceReference<ServerContainer> reference) {
+                               ServerContainer serverContainer = super.addingService(reference);
+
+                               BundleContext bc = reference.getBundle().getBundleContext();
+                               ServiceReference<ServerEndpointConfig.Configurator> srConfigurator = bc
+                                               .getServiceReference(ServerEndpointConfig.Configurator.class);
+                               ServerEndpointConfig.Configurator endpointConfigurator = bc.getService(srConfigurator);
+                               ServerEndpointConfig config = ServerEndpointConfig.Builder
+                                               .create(TestEndpoint.class, "/ws/test/events/").configurator(endpointConfigurator).build();
+                               try {
+                                       serverContainer.addEndpoint(config);
+                               } catch (DeploymentException e) {
+                                       throw new IllegalStateException("Cannot initalise the WebSocket server runtime.", e);
+                               }
+                               return serverContainer;
+                       }
+
+               };
+               serverSt.open();
+
+               // check initialisation
+//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+//                     @Override
+//                     public HttpService addingService(ServiceReference<HttpService> sr) {
+//                             Object httpPort = sr.getProperty("http.port");
+//                             Object httpsPort = sr.getProperty("https.port");
+//                             log.info(httpPortsMsg(httpPort, httpsPort));
+//                             close();
+//                             return super.addingService(sr);
+//                     }
+//             };
+//             httpSt.open();
+       }
+
+       public void stop() {
+               try {
+                       JettyConfigurator.stopServer(CmsConstants.DEFAULT);
+               } catch (Exception e) {
+                       log.error("Cannot stop default Jetty server.", e);
+               }
+
+       }
+
+       public void startServer(Dictionary<String, Object> properties) {
+               // Explicitly configures Jetty so that the default server is not started by the
+               // activator of the Equinox Jetty bundle.
+               Map<String, String> config = LangUtils.dictToStringMap(properties);
+               if (!config.isEmpty()) {
+                       config.put("customizer.class", CMS_JETTY_CUSTOMIZER_CLASS);
+
+                       // TODO centralise with Jetty extender
+                       Object webSocketEnabled = config.get(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty());
+                       if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+                               bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null);
+                               // config.put(WEBSOCKET_ENABLED, "true");
+                       }
+               }
+
+               properties.put(Constants.SERVICE_PID, "default");
+               File jettyWorkDir = new File(bc.getDataFile(""), "jettywork"); //$NON-NLS-1$
+               jettyWorkDir.mkdir();
+
+//             HttpServerManager serverManager = new HttpServerManager(jettyWorkDir);
+//             try {
+//                     serverManager.updated("default", properties);
+//             } catch (ConfigurationException e) {
+//                     // TODO Auto-generated catch block
+//                     e.printStackTrace();
+//             }
+               Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+               Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+               log.info(httpPortsMsg(httpPort, httpsPort));
+
+//             long begin = System.currentTimeMillis();
+//             int tryCount = 60;
+//             try {
+//                     while (tryCount > 0) {
+//                             try {
+//                                     // FIXME deal with multiple ids
+//                                     JettyConfigurator.startServer(CmsConstants.DEFAULT, new Hashtable<>(config));
+//
+//                                     Object httpPort = config.get(JettyHttpConstants.HTTP_PORT);
+//                                     Object httpsPort = config.get(JettyHttpConstants.HTTPS_PORT);
+//                                     log.info(httpPortsMsg(httpPort, httpsPort));
+//
+//                                     // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi
+//                                     // configuration is not cleaned
+//                                     FrameworkUtil.getBundle(JettyConfigurator.class).start();
+//                                     return;
+//                             } catch (IllegalStateException e) {
+//                                     // e.printStackTrace();
+//                                     // Jetty may not be ready
+//                                     try {
+//                                             Thread.sleep(1000);
+//                                     } catch (Exception e1) {
+//                                             // silent
+//                                     }
+//                                     tryCount--;
+//                             }
+//                     }
+//                     long duration = System.currentTimeMillis() - begin;
+//                     log.error("Gave up with starting Jetty server after " + (duration / 1000) + " s");
+//             } catch (Exception e) {
+//                     log.error("Cannot start default Jetty server with config " + properties, e);
+//             }
+
+       }
+
+       private String httpPortsMsg(Object httpPort, Object httpsPort) {
+               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+       }
+
+       /** Override the provided config with the framework properties */
+       public Dictionary<String, Object> getHttpServerConfig() {
+               String httpPort = getFrameworkProp(CmsDeployProperty.HTTP_PORT);
+               String httpsPort = getFrameworkProp(CmsDeployProperty.HTTPS_PORT);
+               /// TODO make it more generic
+               String httpHost = getFrameworkProp(CmsDeployProperty.HOST);
+//             String httpsHost = getFrameworkProp(
+//                             JettyConfig.JETTY_PROPERTY_PREFIX + CmsHttpConstants.HTTPS_HOST);
+               String webSocketEnabled = getFrameworkProp(CmsDeployProperty.WEBSOCKET_ENABLED);
+
+               final Hashtable<String, Object> props = new Hashtable<String, Object>();
+               // try {
+               if (httpPort != null || httpsPort != null) {
+                       boolean httpEnabled = httpPort != null;
+                       props.put(JettyHttpConstants.HTTP_ENABLED, httpEnabled);
+                       boolean httpsEnabled = httpsPort != null;
+                       props.put(JettyHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+                       if (httpEnabled) {
+                               props.put(JettyHttpConstants.HTTP_PORT, httpPort);
+                               if (httpHost != null)
+                                       props.put(JettyHttpConstants.HTTP_HOST, httpHost);
+                       }
+
+                       if (httpsEnabled) {
+                               props.put(JettyHttpConstants.HTTPS_PORT, httpsPort);
+                               if (httpHost != null)
+                                       props.put(JettyHttpConstants.HTTPS_HOST, httpHost);
+
+                               // keystore
+                               props.put(JettyHttpConstants.SSL_KEYSTORETYPE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORETYPE));
+                               props.put(JettyHttpConstants.SSL_KEYSTORE, getFrameworkProp(CmsDeployProperty.SSL_KEYSTORE));
+                               props.put(JettyHttpConstants.SSL_PASSWORD, getFrameworkProp(CmsDeployProperty.SSL_PASSWORD));
+
+                               // truststore
+                               props.put(JettyHttpConstants.SSL_TRUSTSTORETYPE,
+                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORETYPE));
+                               props.put(JettyHttpConstants.SSL_TRUSTSTORE, getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTORE));
+                               props.put(JettyHttpConstants.SSL_TRUSTSTOREPASSWORD,
+                                               getFrameworkProp(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD));
+
+                               // client certificate authentication
+                               String wantClientAuth = getFrameworkProp(CmsDeployProperty.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null)
+                                       props.put(JettyHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+                               String needClientAuth = getFrameworkProp(CmsDeployProperty.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null)
+                                       props.put(JettyHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+                       }
+
+                       // web socket
+                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+                               props.put(CmsDeployProperty.WEBSOCKET_ENABLED.getProperty(), true);
+
+                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+               }
+               return props;
+       }
+
+       private String getFrameworkProp(CmsDeployProperty deployProperty) {
+               return cmsState.getDeployProperty(deployProperty.getProperty());
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+}
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/cms/servlet/internal/jetty/JettyHttpConstants.java
new file mode 100644 (file)
index 0000000..8ceb358
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.servlet.internal.jetty;
+
+/** Compatible with Jetty. */
+interface JettyHttpConstants {
+       static final String HTTP_ENABLED = "http.enabled";
+       static final String HTTP_PORT = "http.port";
+       static final String HTTP_HOST = "http.host";
+       static final String HTTPS_ENABLED = "https.enabled";
+       static final String HTTPS_HOST = "https.host";
+       static final String HTTPS_PORT = "https.port";
+       static final String SSL_KEYSTORE = "ssl.keystore";
+       static final String SSL_PASSWORD = "ssl.password";
+       static final String SSL_KEYPASSWORD = "ssl.keypassword";
+       static final String SSL_NEEDCLIENTAUTH = "ssl.needclientauth";
+       static final String SSL_WANTCLIENTAUTH = "ssl.wantclientauth";
+       static final String SSL_PROTOCOL = "ssl.protocol";
+       static final String SSL_ALGORITHM = "ssl.algorithm";
+       static final String SSL_KEYSTORETYPE = "ssl.keystoretype";
+
+       // Argeo
+       static final String SSL_TRUSTSTORE = "ssl.truststore";
+       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+}
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/CmsJettyCustomizer.java
new file mode 100644 (file)
index 0000000..7be23fc
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.equinox.jetty;
+
+import java.util.Dictionary;
+
+import javax.servlet.ServletContext;
+import javax.websocket.DeploymentException;
+import javax.websocket.server.ServerContainer;
+
+import org.eclipse.equinox.http.jetty.JettyCustomizer;
+import org.eclipse.jetty.server.ConnectionFactory;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer;
+import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Customises the Jetty HTTP server. */
+public class CmsJettyCustomizer extends JettyCustomizer {
+       static final String SSL_TRUSTSTORE = "ssl.truststore";
+       static final String SSL_TRUSTSTOREPASSWORD = "ssl.truststorepassword";
+       static final String SSL_TRUSTSTORETYPE = "ssl.truststoretype";
+
+       private BundleContext bc = FrameworkUtil.getBundle(CmsJettyCustomizer.class).getBundleContext();
+
+       public final static String WEBSOCKET_ENABLED = "argeo.websocket.enabled";
+
+       @Override
+       public Object customizeContext(Object context, Dictionary<String, ?> settings) {
+               // WebSocket
+               Object webSocketEnabled = settings.get(WEBSOCKET_ENABLED);
+               if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) {
+                       ServletContextHandler servletContextHandler = (ServletContextHandler) context;
+                       JavaxWebSocketServletContainerInitializer.configure(servletContextHandler, new Configurator() {
+
+                               @Override
+                               public void accept(ServletContext servletContext, ServerContainer serverContainer)
+                                               throws DeploymentException {
+                                       bc.registerService(javax.websocket.server.ServerContainer.class, serverContainer, null);
+                               }
+                       });
+               }
+               return super.customizeContext(context, settings);
+
+       }
+
+       @Override
+       public Object customizeHttpsConnector(Object connector, Dictionary<String, ?> settings) {
+               ServerConnector httpsConnector = (ServerConnector) connector;
+               if (httpsConnector != null)
+                       for (ConnectionFactory connectionFactory : httpsConnector.getConnectionFactories()) {
+                               if (connectionFactory instanceof SslConnectionFactory) {
+                                       SslContextFactory.Server sslContextFactory = ((SslConnectionFactory) connectionFactory)
+                                                       .getSslContextFactory();
+                                       sslContextFactory.setTrustStorePath((String) settings.get(SSL_TRUSTSTORE));
+                                       sslContextFactory.setTrustStoreType((String) settings.get(SSL_TRUSTSTORETYPE));
+                                       sslContextFactory.setTrustStorePassword((String) settings.get(SSL_TRUSTSTOREPASSWORD));
+                               }
+                       }
+               return super.customizeHttpsConnector(connector, settings);
+       }
+
+}
diff --git a/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java b/osgi/equinox/org.argeo.cms.lib.equinox/src/org/argeo/equinox/jetty/package-info.java
new file mode 100644 (file)
index 0000000..41c8ce9
--- /dev/null
@@ -0,0 +1,2 @@
+/** Equinox Jetty extensions. */
+package org.argeo.equinox.jetty;
\ No newline at end of file
diff --git a/rap/org.argeo.cms.e4.rap/.classpath b/rap/org.argeo.cms.e4.rap/.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/rap/org.argeo.cms.e4.rap/.project b/rap/org.argeo.cms.e4.rap/.project
deleted file mode 100644 (file)
index 40c9e01..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.e4.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/rap/org.argeo.cms.e4.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml b/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml
deleted file mode 100644 (file)
index 1f688ba..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" name="CMS Admin RAP">
-   <implementation class="org.argeo.cms.e4.rap.CmsE4AdminApp"/>
-   <service>
-      <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
-      <property name="contextName" type="String" value="cms"/>
-   </service>
-</scr:component>
diff --git a/rap/org.argeo.cms.e4.rap/bnd.bnd b/rap/org.argeo.cms.e4.rap/bnd.bnd
deleted file mode 100644 (file)
index 5bbe4bc..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-Bundle-ActivationPolicy: lazy
-Service-Component: OSGI-INF/cms-admin-rap.xml
-
-Import-Package: org.eclipse.swt,\
-org.eclipse.swt.graphics,\
-org.eclipse.e4.ui.workbench,\
-org.eclipse.rap.rwt.client,\
-org.eclipse.nebula.widgets.richtext;resolution:=optional,\
-*
diff --git a/rap/org.argeo.cms.e4.rap/build.properties b/rap/org.argeo.cms.e4.rap/build.properties
deleted file mode 100644 (file)
index c58ea21..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java
deleted file mode 100644 (file)
index 5fe22ae..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Base class for CMS RAP applications. */
-public abstract class AbstractRapE4App implements ApplicationConfiguration {
-       private String e4Xmi;
-       private String path;
-       private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
-       private Map<String, String> baseProperties = new HashMap<String, String>();
-
-       private BundleContext bundleContext;
-       public final static String CONTEXT_NAME_PROPERTY = "contextName";
-       private String contextName;
-
-       /**
-        * To be overridden in order to add multiple entry points, directly or using
-        * {@link #addE4EntryPoint(Application, String, String, Map)}.
-        */
-       protected void addEntryPoints(Application application) {
-       }
-
-       public void configure(Application application) {
-               application.setExceptionHandler(new ExceptionHandler() {
-
-                       @Override
-                       public void handleException(Throwable throwable) {
-                               CmsFeedback.show("Unexpected RWT exception", throwable);
-                       }
-               });
-
-               if (e4Xmi != null) {// backward compatibility
-                       addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
-               } else {
-                       addEntryPoints(application);
-               }
-       }
-
-       protected Map<String, String> getBaseProperties() {
-               return baseProperties;
-       }
-
-//     protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
-//             CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
-//             application.addEntryPoint(path, entryPointFactory, properties);
-//             application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-//     }
-
-       protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
-               E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
-               CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
-               application.addEntryPoint(path, entryPointFactory, properties);
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-       }
-
-       /**
-        * To be overridden for further configuration.
-        * 
-        * @see E4ApplicationConfig
-        */
-       protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
-               return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
-       }
-
-       @Deprecated
-       public void setPageTitle(String pageTitle) {
-               if (pageTitle != null)
-                       baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
-       }
-
-       /** Returns a new map used to customise and entry point. */
-       public Map<String, String> customise(String pageTitle) {
-               Map<String, String> custom = new HashMap<>(getBaseProperties());
-               if (pageTitle != null)
-                       custom.put(WebClient.PAGE_TITLE, pageTitle);
-               return custom;
-       }
-
-       @Deprecated
-       public void setE4Xmi(String e4Xmi) {
-               this.e4Xmi = e4Xmi;
-       }
-
-       @Deprecated
-       public void setPath(String path) {
-               this.path = path;
-       }
-
-       public void setLifeCycleUri(String lifeCycleUri) {
-               this.lifeCycleUri = lifeCycleUri;
-       }
-
-       protected BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       protected void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-       public String getContextName() {
-               return contextName;
-       }
-
-       public void setContextName(String contextName) {
-               this.contextName = contextName;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               this.bundleContext = bundleContext;
-               for (String key : properties.keySet()) {
-                       Object value = properties.get(key);
-                       if (value != null)
-                               baseProperties.put(key, value.toString());
-               }
-
-               if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
-                       assert properties.get(CONTEXT_NAME_PROPERTY) != null;
-                       contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
-               } else {
-                       contextName = "<unknown context>";
-               }
-       }
-
-       public void destroy(Map<String, Object> properties) {
-
-       }
-}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java
deleted file mode 100644 (file)
index 66be1e8..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import org.eclipse.rap.rwt.application.Application;
-
-/**
- * Access to canonical views of the core CMS concepts, useful for devleopers and
- * operators.
- */
-public class CmsE4AdminApp extends AbstractRapE4App {
-       @Override
-       protected void addEntryPoints(Application application) {
-               addE4EntryPoint(application, "/devops", "org.argeo.cms.e4/e4xmi/cms-devops.e4xmi",
-                               customise("Argeo CMS DevOps"));
-               addE4EntryPoint(application, "/ego", "org.argeo.cms.e4/e4xmi/cms-ego.e4xmi", customise("Argeo CMS Ego"));
-       }
-
-}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java
deleted file mode 100644 (file)
index a5a3234..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.security.PrivilegedAction;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.e4.E4EntryPointFactory;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-
-public class CmsE4EntryPointFactory extends E4EntryPointFactory {
-       public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
-       public CmsE4EntryPointFactory(E4ApplicationConfig config) {
-               super(config);
-       }
-
-       public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
-               super(defaultConfig(e4Xmi, lifeCycleUri));
-       }
-
-       public CmsE4EntryPointFactory(String e4Xmi) {
-               this(e4Xmi, DEFAULT_LIFECYCLE_URI);
-       }
-
-       public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
-               E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
-               return config;
-       }
-
-       @Override
-       public EntryPoint create() {
-               EntryPoint ep = createEntryPoint();
-               EntryPoint authEp = new EntryPoint() {
-
-                       @Override
-                       public int createUI() {
-                               Subject subject = new Subject();
-                               return Subject.doAs(subject, new PrivilegedAction<Integer>() {
-
-                                       @Override
-                                       public Integer run() {
-                                               // SPNEGO
-                                               // HttpServletRequest request = RWT.getRequest();
-                                               // String authorization = request.getHeader(HEADER_AUTHORIZATION);
-                                               // if (authorization == null || !authorization.startsWith("Negotiate")) {
-                                               // HttpServletResponse response = RWT.getResponse();
-                                               // response.setStatus(401);
-                                               // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
-                                               // response.setDateHeader("Date", System.currentTimeMillis());
-                                               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60
-                                               // * 1000));
-                                               // response.setHeader("Accept-Ranges", "bytes");
-                                               // response.setHeader("Connection", "Keep-Alive");
-                                               // response.setHeader("Keep-Alive", "timeout=5, max=97");
-                                               // // response.setContentType("text/html; charset=UTF-8");
-                                               // }
-
-                                               JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-                                               Integer exitCode = ep.createUI();
-                                               jsExecutor.execute("location.reload()");
-                                               return exitCode;
-                                       }
-
-                               });
-                       }
-               };
-               return authEp;
-       }
-
-       protected EntryPoint createEntryPoint() {
-               return super.create();
-       }
-}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
deleted file mode 100644 (file)
index 95be53d..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.security.AccessController;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.SimpleImageManager;
-import org.eclipse.e4.core.services.events.IEventBroker;
-import org.eclipse.e4.ui.workbench.UIEvents;
-import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
-import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventHandler;
-
-@SuppressWarnings("restriction")
-public class CmsLoginLifecycle implements CmsView {
-       private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class);
-
-       private UxContext uxContext;
-       private CmsImageManager imageManager;
-
-       private LoginContext loginContext;
-       private BrowserNavigation browserNavigation;
-
-       private String state = null;
-       private String uid;
-
-       @PostContextCreate
-       boolean login(final IEventBroker eventBroker) {
-               uid = UUID.randomUUID().toString();
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
-                               private static final long serialVersionUID = -3668136623771902865L;
-
-                               @Override
-                               public void navigated(BrowserNavigationEvent event) {
-                                       state = event.getState();
-                                       if (uxContext != null)// is logged in
-                                               stateChanged();
-                               }
-                       });
-
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               Display display = Display.getCurrent();
-//             UiContext.setData(CmsView.KEY, this);
-               // FIXME get CMS context
-               CmsLoginShell loginShell = new CmsLoginShell(this, null);
-               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
-               loginShell.setSubject(subject);
-               try {
-                       // try pre-auth
-                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell);
-                       loginContext.login();
-               } catch (LoginException e) {
-                       loginShell.createUi();
-                       loginShell.open();
-
-                       while (!loginShell.getShell().isDisposed()) {
-                               if (!display.readAndDispatch())
-                                       display.sleep();
-                       }
-               }
-               if (CurrentUser.getUsername(getSubject()) == null)
-                       return false;
-               uxContext = new SimpleSwtUxContext();
-               imageManager = new SimpleImageManager();
-
-               eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
-                       @Override
-                       public void handleEvent(Event event) {
-                               startupComplete();
-                               eventBroker.unsubscribe(this);
-                       }
-               });
-
-               // lcs.changeApplicationLocale(Locale.FRENCH);
-               return true;
-       }
-
-       @PreSave
-       void destroy() {
-               // logout();
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               browserNavigation.pushState(state, state);
-       }
-
-       @Override
-       public void authChange(LoginContext loginContext) {
-               if (loginContext == null)
-                       throw new IllegalArgumentException("Login context cannot be null");
-               // logout previous login context
-               // if (this.loginContext != null)
-               // try {
-               // this.loginContext.logout();
-               // } catch (LoginException e1) {
-               // System.err.println("Could not log out: " + e1);
-               // }
-               this.loginContext = loginContext;
-       }
-
-       @Override
-       public void logout() {
-               if (loginContext == null)
-                       throw new IllegalStateException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       throw new IllegalStateException("Cannot log out", e);
-               }
-       }
-
-       @Override
-       public void exception(Throwable e) {
-               String msg = "Unexpected exception in Eclipse 4 RAP";
-               log.error(msg, e);
-               CmsFeedback.show(msg, e);
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       // CALLBACKS
-       protected void startupComplete() {
-       }
-
-       protected void stateChanged() {
-
-       }
-
-       // GETTERS
-       protected BrowserNavigation getBrowserNavigation() {
-               return browserNavigation;
-       }
-
-       protected String getState() {
-               return state;
-       }
-
-}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java
deleted file mode 100644 (file)
index 1bca333..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.util.Enumeration;
-
-import org.apache.commons.io.FilenameUtils;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.rap.rwt.application.Application;
-import org.osgi.framework.Bundle;
-
-/** Simple RAP app which loads all e4xmi files. */
-public class SimpleRapE4App extends AbstractRapE4App {
-       private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class);
-
-       private String baseE4xmi = "/e4xmi";
-
-       @Override
-       protected void addEntryPoints(Application application) {
-               Bundle bundle = getBundleContext().getBundle();
-               Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
-               while (paths.hasMoreElements()) {
-                       String p = paths.nextElement();
-                       if (p.endsWith(".e4xmi")) {
-                               String e4xmiPath = bundle.getSymbolicName() + '/' + p;
-                               String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
-                               addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
-                               if (log.isDebugEnabled())
-                                       log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
-                       }
-               }
-       }
-
-}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java
deleted file mode 100644 (file)
index 1122f19..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse 4 RAP specific extensions. */
-package org.argeo.cms.e4.rap;
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/.classpath b/rap/org.argeo.cms.ui.rap/.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/rap/org.argeo.cms.ui.rap/.project b/rap/org.argeo.cms.ui.rap/.project
deleted file mode 100644 (file)
index 1a37a67..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ui.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rap/org.argeo.cms.ui.rap/META-INF/.gitignore b/rap/org.argeo.cms.ui.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rap/org.argeo.cms.ui.rap/bnd.bnd b/rap/org.argeo.cms.ui.rap/bnd.bnd
deleted file mode 100644 (file)
index e3e71b3..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Import-Package:\
-org.eclipse.swt,\
-org.argeo.eclipse.ui,\
-javax.jcr.nodetype,\
-javax.jcr.security,\
-org.eclipse.swt.graphics,\
-org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\
-org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\
-javax.servlet.*;version="[3,5)",\
-*
-
diff --git a/rap/org.argeo.cms.ui.rap/build.properties b/rap/org.argeo.cms.ui.rap/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/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java
deleted file mode 100644 (file)
index 01ebb23..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.Selected;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsPane;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-
-public class AppUi implements CmsUiProvider, Branding {
-       private final CmsScriptApp app;
-
-       private CmsUiProvider ui;
-       private String createUi;
-       private Object impl;
-       private String script;
-       // private Branding branding = new Branding();
-
-       private EntryPointFactory factory;
-
-       // Branding
-       private String themeId;
-       private String additionalHeaders;
-       private String bodyHtml;
-       private String pageTitle;
-       private String pageOverflow;
-       private String favicon;
-
-       public AppUi(CmsScriptApp app) {
-               this.app = app;
-       }
-
-       public AppUi(CmsScriptApp app, String scriptPath) {
-               this.app = app;
-               this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC),
-                               app.getScriptEngine(), scriptPath);
-       }
-
-       public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) {
-               this.app = app;
-               this.ui = uiProvider;
-       }
-
-       public AppUi(CmsScriptApp app, EntryPointFactory factory) {
-               this.app = app;
-               this.factory = factory;
-       }
-
-       public void apply(Repository repository, Application application, Branding appBranding, String path) {
-               Map<String, String> factoryProperties = new HashMap<>();
-               if (appBranding != null)
-                       appBranding.applyBranding(factoryProperties);
-               applyBranding(factoryProperties);
-               if (factory != null) {
-                       application.addEntryPoint("/" + path, factory, factoryProperties);
-               } else {
-                       EntryPointFactory entryPointFactory = new EntryPointFactory() {
-                               @Override
-                               public EntryPoint create() {
-                                       SimpleErgonomics ergonomics = new SimpleErgonomics(repository, CmsConstants.SYS_WORKSPACE,
-                                                       "/home/root/argeo:keyring", AppUi.this, factoryProperties);
-//                                     CmsUiProvider header = app.getHeader();
-//                                     if (header != null)
-//                                             ergonomics.setHeader(header);
-                                       app.applySides(ergonomics);
-                                       Integer headerHeight = app.getHeaderHeight();
-                                       if (headerHeight != null)
-                                               ergonomics.setHeaderHeight(headerHeight);
-                                       return ergonomics;
-                               }
-                       };
-                       application.addEntryPoint("/" + path, entryPointFactory, factoryProperties);
-               }
-       }
-
-       public void setUi(CmsUiProvider uiProvider) {
-               this.ui = uiProvider;
-       }
-
-       public void applyBranding(Map<String, String> properties) {
-               if (themeId != null)
-                       properties.put(WebClient.THEME_ID, themeId);
-               if (additionalHeaders != null)
-                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
-               if (bodyHtml != null)
-                       properties.put(WebClient.BODY_HTML, bodyHtml);
-               if (pageTitle != null)
-                       properties.put(WebClient.PAGE_TITLE, pageTitle);
-               if (pageOverflow != null)
-                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
-               if (favicon != null)
-                       properties.put(WebClient.FAVICON, favicon);
-       }
-
-       // public Branding getBranding() {
-       // return branding;
-       // }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               CmsPane cmsPane = new CmsPane(parent, SWT.NONE);
-
-               if (false) {
-                       // QA
-                       CmsSwtUtils.style(cmsPane.getQaArea(), "qa");
-                       Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT);
-                       CmsSwtUtils.style(reload, "qa");
-                       reload.setText("Reload");
-                       reload.addSelectionListener(new Selected() {
-                               private static final long serialVersionUID = 1L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       new Thread() {
-                                               @Override
-                                               public void run() {
-                                                       app.reload();
-                                               }
-                                       }.start();
-                                       RWT.getClient().getService(JavaScriptExecutor.class)
-                                                       .execute("setTimeout('location.reload()',1000)");
-                               }
-                       });
-
-                       // Support
-                       CmsSwtUtils.style(cmsPane.getSupportArea(), "support");
-                       Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE);
-                       CmsSwtUtils.style(msg, "support");
-                       msg.setText("UNSUPPORTED DEVELOPMENT VERSION");
-               }
-
-               if (ui != null) {
-                       ui.createUi(cmsPane.getMainArea(), context);
-               }
-               if (createUi != null) {
-                       Invocable invocable = (Invocable) app.getScriptEngine();
-                       try {
-                               invocable.invokeFunction(createUi, cmsPane.getMainArea(), context);
-
-                       } catch (NoSuchMethodException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       } catch (ScriptException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       }
-               }
-               if (impl != null) {
-                       Invocable invocable = (Invocable) app.getScriptEngine();
-                       try {
-                               invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context);
-
-                       } catch (NoSuchMethodException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       } catch (ScriptException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       }
-               }
-
-               // Invocable invocable = (Invocable) app.getScriptEngine();
-               // try {
-               // invocable.invokeMethod(AppUi.this, "initUi", parent, context);
-               //
-               // } catch (NoSuchMethodException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // } catch (ScriptException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
-
-               return null;
-       }
-
-       public void setCreateUi(String createUi) {
-               this.createUi = createUi;
-       }
-
-       public void setImpl(Object impl) {
-               this.impl = impl;
-       }
-
-       public Object getImpl() {
-               return impl;
-       }
-
-       public String getScript() {
-               return script;
-       }
-
-       public void setScript(String script) {
-               this.script = script;
-       }
-
-       // Branding
-       public String getThemeId() {
-               return themeId;
-       }
-
-       public void setThemeId(String themeId) {
-               this.themeId = themeId;
-       }
-
-       public String getAdditionalHeaders() {
-               return additionalHeaders;
-       }
-
-       public void setAdditionalHeaders(String additionalHeaders) {
-               this.additionalHeaders = additionalHeaders;
-       }
-
-       public String getBodyHtml() {
-               return bodyHtml;
-       }
-
-       public void setBodyHtml(String bodyHtml) {
-               this.bodyHtml = bodyHtml;
-       }
-
-       public String getPageTitle() {
-               return pageTitle;
-       }
-
-       public void setPageTitle(String pageTitle) {
-               this.pageTitle = pageTitle;
-       }
-
-       public String getPageOverflow() {
-               return pageOverflow;
-       }
-
-       public void setPageOverflow(String pageOverflow) {
-               this.pageOverflow = pageOverflow;
-       }
-
-       public String getFavicon() {
-               return favicon;
-       }
-
-       public void setFavicon(String favicon) {
-               this.favicon = favicon;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java
deleted file mode 100644 (file)
index f72338e..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.util.Map;
-
-public interface Branding {
-       public void applyBranding(Map<String, String> properties);
-
-       public String getThemeId();
-
-       public String getAdditionalHeaders();
-
-       public String getBodyHtml();
-
-       public String getPageTitle();
-
-       public String getPageOverflow();
-
-       public String getFavicon();
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java
deleted file mode 100644 (file)
index edf558e..0000000
+++ /dev/null
@@ -1,421 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.script.ScriptEngine;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.web.BundleResourceLoader;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.argeo.cms.web.WebThemeUtils;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-
-public class CmsScriptApp implements Branding {
-       public final static String CONTEXT_NAME = "contextName";
-
-       ServiceRegistration<ApplicationConfiguration> appConfigReg;
-
-       private ScriptEngine scriptEngine;
-
-       private final static CmsLog log = CmsLog.getLog(CmsScriptApp.class);
-
-       private String webPath;
-       private String repo = "(cn=node)";
-
-       // private Branding branding = new Branding();
-       private CmsTheme theme;
-
-       private List<String> resources = new ArrayList<>();
-
-       private Map<String, AppUi> ui = new HashMap<>();
-
-       private CmsUiProvider header;
-       private Integer headerHeight = null;
-       private CmsUiProvider lead;
-       private CmsUiProvider end;
-       private CmsUiProvider footer;
-
-       // Branding
-       private String themeId;
-       private String additionalHeaders;
-       private String bodyHtml;
-       private String pageTitle;
-       private String pageOverflow;
-       private String favicon;
-
-       public CmsScriptApp(ScriptEngine scriptEngine) {
-               super();
-               this.scriptEngine = scriptEngine;
-       }
-
-       public void apply(BundleContext bundleContext, Repository repository, Application application) {
-               BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-               // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
-               application.setExceptionHandler(new CmsExceptionHandler());
-
-               // loading animated gif
-               application.addResource(CmsUiConstants.LOADING_IMAGE, createResourceLoader(CmsUiConstants.LOADING_IMAGE));
-               // empty image
-               application.addResource(CmsUiConstants.NO_IMAGE, createResourceLoader(CmsUiConstants.NO_IMAGE));
-
-               for (String resource : resources) {
-                       application.addResource(resource, bundleRL);
-                       if (log.isTraceEnabled())
-                               log.trace("Resource " + resource);
-               }
-
-               if (theme != null) {
-                       WebThemeUtils.apply(application, theme);
-                       String themeHeaders = theme.getHtmlHeaders();
-                       if (themeHeaders != null) {
-                               if (additionalHeaders == null)
-                                       additionalHeaders = themeHeaders;
-                               else
-                                       additionalHeaders = themeHeaders + "\n" + additionalHeaders;
-                       }
-                       themeId = theme.getThemeId();
-               }
-
-               // client JavaScript
-               Bundle appBundle = bundleRL.getBundle();
-               BundleContext bc = appBundle.getBundleContext();
-               HttpService httpService = bc.getService(bc.getServiceReference(HttpService.class));
-               HttpContext httpContext = new BundleHttpContext(bc);
-               Enumeration<URL> themeResources = appBundle.findEntries("/js/", "*", true);
-               if (themeResources != null)
-                       bundleResources: while (themeResources.hasMoreElements()) {
-                               try {
-                                       String name = themeResources.nextElement().getPath();
-                                       if (name.endsWith("/"))
-                                               continue bundleResources;
-                                       String alias = "/" + getWebPath() + name;
-
-                                       httpService.registerResources(alias, name, httpContext);
-                                       if (log.isDebugEnabled())
-                                               log.debug("Mapped " + name + " to alias " + alias);
-
-                               } catch (NamespaceException e) {
-                                       // TODO Auto-generated catch block
-                                       e.printStackTrace();
-                               }
-                       }
-
-               // App UIs
-               for (String appUiName : ui.keySet()) {
-                       AppUi appUi = ui.get(appUiName);
-                       appUi.apply(repository, application, this, appUiName);
-
-               }
-
-       }
-
-       public void applySides(SimpleErgonomics simpleErgonomics) {
-               simpleErgonomics.setHeader(header);
-               simpleErgonomics.setLead(lead);
-               simpleErgonomics.setEnd(end);
-               simpleErgonomics.setFooter(footer);
-       }
-
-       public void register(BundleContext bundleContext, ApplicationConfiguration appConfig) {
-               Hashtable<String, String> props = new Hashtable<>();
-               props.put(CONTEXT_NAME, webPath);
-               appConfigReg = bundleContext.registerService(ApplicationConfiguration.class, appConfig, props);
-       }
-
-       public void reload() {
-               BundleContext bundleContext = appConfigReg.getReference().getBundle().getBundleContext();
-               ApplicationConfiguration appConfig = bundleContext.getService(appConfigReg.getReference());
-               appConfigReg.unregister();
-               register(bundleContext, appConfig);
-
-               // BundleContext bundleContext = (BundleContext)
-               // getScriptEngine().get("bundleContext");
-               // try {
-               // Bundle bundle = bundleContext.getBundle();
-               // bundle.stop();
-               // bundle.start();
-               // } catch (BundleException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
-       }
-
-       private static ResourceLoader createResourceLoader(final String resourceName) {
-               return new ResourceLoader() {
-                       public InputStream getResourceAsStream(String resourceName) throws IOException {
-                               return getClass().getClassLoader().getResourceAsStream(resourceName);
-                       }
-               };
-       }
-
-       public List<String> getResources() {
-               return resources;
-       }
-
-       public AppUi newUi(String name) {
-               if (ui.containsKey(name))
-                       throw new IllegalArgumentException("There is already an UI named " + name);
-               AppUi appUi = new AppUi(this);
-               // appUi.setApp(this);
-               ui.put(name, appUi);
-               return appUi;
-       }
-
-       public void addUi(String name, AppUi appUi) {
-               if (ui.containsKey(name))
-                       throw new IllegalArgumentException("There is already an UI named " + name);
-               // appUi.setApp(this);
-               ui.put(name, appUi);
-       }
-
-       public void applyBranding(Map<String, String> properties) {
-               if (themeId != null)
-                       properties.put(WebClient.THEME_ID, themeId);
-               if (additionalHeaders != null)
-                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
-               if (bodyHtml != null)
-                       properties.put(WebClient.BODY_HTML, bodyHtml);
-               if (pageTitle != null)
-                       properties.put(WebClient.PAGE_TITLE, pageTitle);
-               if (pageOverflow != null)
-                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
-               if (favicon != null)
-                       properties.put(WebClient.FAVICON, favicon);
-       }
-
-       class CmsExceptionHandler implements ExceptionHandler {
-
-               @Override
-               public void handleException(Throwable throwable) {
-                       // TODO be smarter
-                       CmsUiUtils.getCmsView().exception(throwable);
-               }
-
-       }
-
-       // public Branding getBranding() {
-       // return branding;
-       // }
-
-       ScriptEngine getScriptEngine() {
-               return scriptEngine;
-       }
-
-       public static String toJson(Node node) {
-               try {
-                       StringBuilder sb = new StringBuilder();
-                       sb.append('{');
-                       PropertyIterator pit = node.getProperties();
-                       int count = 0;
-                       while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               int type = p.getType();
-                               if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.PATH) {
-                                       Node ref = p.getNode();
-                                       if (count != 0)
-                                               sb.append(',');
-                                       // TODO limit depth?
-                                       sb.append(toJson(ref));
-                                       count++;
-                               } else if (!p.isMultiple()) {
-                                       if (count != 0)
-                                               sb.append(',');
-                                       sb.append('\"').append(p.getName()).append("\":\"").append(p.getString()).append('\"');
-                                       count++;
-                               }
-                       }
-                       sb.append('}');
-                       return sb.toString();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot convert " + node + " to JSON", e);
-               }
-       }
-
-       public void fromJson(Node node, String json) {
-               // TODO
-       }
-
-       public CmsTheme getTheme() {
-               return theme;
-       }
-
-       public void setTheme(CmsTheme theme) {
-               this.theme = theme;
-       }
-
-       public String getWebPath() {
-               return webPath;
-       }
-
-       public void setWebPath(String context) {
-               this.webPath = context;
-       }
-
-       public String getRepo() {
-               return repo;
-       }
-
-       public void setRepo(String repo) {
-               this.repo = repo;
-       }
-
-       public Map<String, AppUi> getUi() {
-               return ui;
-       }
-
-       public void setUi(Map<String, AppUi> ui) {
-               this.ui = ui;
-       }
-
-       // Branding
-       public String getThemeId() {
-               return themeId;
-       }
-
-       public void setThemeId(String themeId) {
-               this.themeId = themeId;
-       }
-
-       public String getAdditionalHeaders() {
-               return additionalHeaders;
-       }
-
-       public void setAdditionalHeaders(String additionalHeaders) {
-               this.additionalHeaders = additionalHeaders;
-       }
-
-       public String getBodyHtml() {
-               return bodyHtml;
-       }
-
-       public void setBodyHtml(String bodyHtml) {
-               this.bodyHtml = bodyHtml;
-       }
-
-       public String getPageTitle() {
-               return pageTitle;
-       }
-
-       public void setPageTitle(String pageTitle) {
-               this.pageTitle = pageTitle;
-       }
-
-       public String getPageOverflow() {
-               return pageOverflow;
-       }
-
-       public void setPageOverflow(String pageOverflow) {
-               this.pageOverflow = pageOverflow;
-       }
-
-       public String getFavicon() {
-               return favicon;
-       }
-
-       public void setFavicon(String favicon) {
-               this.favicon = favicon;
-       }
-
-       public CmsUiProvider getHeader() {
-               return header;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public Integer getHeaderHeight() {
-               return headerHeight;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public CmsUiProvider getLead() {
-               return lead;
-       }
-
-       public void setLead(CmsUiProvider lead) {
-               this.lead = lead;
-       }
-
-       public CmsUiProvider getEnd() {
-               return end;
-       }
-
-       public void setEnd(CmsUiProvider end) {
-               this.end = end;
-       }
-
-       public CmsUiProvider getFooter() {
-               return footer;
-       }
-
-       public void setFooter(CmsUiProvider footer) {
-               this.footer = footer;
-       }
-
-       static class BundleHttpContext implements HttpContext {
-               private BundleContext bundleContext;
-
-               public BundleHttpContext(BundleContext bundleContext) {
-                       super();
-                       this.bundleContext = bundleContext;
-               }
-
-               @Override
-               public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
-                       // TODO Auto-generated method stub
-                       return true;
-               }
-
-               @Override
-               public URL getResource(String name) {
-
-                       return bundleContext.getBundle().getEntry(name);
-               }
-
-               @Override
-               public String getMimeType(String name) {
-                       return null;
-               }
-
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java
deleted file mode 100644 (file)
index 499840a..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.Repository;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.wiring.BundleWiring;
-
-public class CmsScriptRwtApplication implements ApplicationConfiguration {
-       public final static String APP = "APP";
-       public final static String BC = "BC";
-
-       private final CmsLog log = CmsLog.getLog(CmsScriptRwtApplication.class);
-
-       BundleContext bundleContext;
-       Repository repository;
-
-       ScriptEngine engine;
-
-       public void init(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-               ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-               ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
-               try {
-//                     Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
-//                     ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
-//                     engine = scriptEngineManager.getEngineByName("JavaScript");
-//                     if (engine == null) {// Nashorn
-//                             Thread.currentThread().setContextClassLoader(originalCcl);
-//                             scriptEngineManager = new ScriptEngineManager();
-//                             Thread.currentThread().setContextClassLoader(bundleCl);
-//                             engine = scriptEngineManager.getEngineByName("JavaScript");
-//                     }
-                       engine = loadScriptEngine(originalCcl, bundleCl);
-
-                       // Load script
-                       URL appUrl = bundleContext.getBundle().getEntry("cms/app.js");
-                       // System.out.println("Loading " + appUrl);
-                       // System.out.println("Loading " + appUrl.getHost());
-                       // System.out.println("Loading " + appUrl.getPath());
-
-                       CmsScriptApp app = new CmsScriptApp(engine);
-                       engine.put(APP, app);
-                       engine.put(BC, bundleContext);
-                       try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-                               engine.eval(reader);
-                       } catch (IOException | ScriptException e) {
-                               throw new CmsException("Cannot execute " + appUrl, e);
-                       }
-
-                       if (log.isDebugEnabled())
-                               log.debug("CMS script app initialized from " + appUrl);
-
-               } catch (Exception e) {
-                       e.printStackTrace();
-               } finally {
-                       Thread.currentThread().setContextClassLoader(originalCcl);
-               }
-       }
-
-       public void destroy(BundleContext bundleContext) {
-               engine = null;
-       }
-
-       @Override
-       public void configure(Application application) {
-               load(application);
-       }
-
-       void load(Application application) {
-               CmsScriptApp app = getApp();
-               app.apply(bundleContext, repository, application);
-               if (log.isDebugEnabled())
-                       log.debug("CMS script app loaded to " + app.getWebPath());
-       }
-
-       CmsScriptApp getApp() {
-               if (engine == null)
-                       throw new IllegalStateException("CMS script app is not initialized");
-               return (CmsScriptApp) engine.get(APP);
-       }
-
-       void update() {
-
-               try {
-                       bundleContext.getBundle().update();
-               } catch (BundleException e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       private static ScriptEngine loadScriptEngine(ClassLoader originalCcl, ClassLoader bundleCl) {
-               Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
-               ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
-               ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
-               if (engine == null) {// Nashorn
-                       Thread.currentThread().setContextClassLoader(originalCcl);
-                       scriptEngineManager = new ScriptEngineManager();
-                       Thread.currentThread().setContextClassLoader(bundleCl);
-                       engine = scriptEngineManager.getEngineByName("JavaScript");
-               }
-               return engine;
-       }
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java
deleted file mode 100644 (file)
index a550953..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import javax.jcr.Repository;
-
-import org.argeo.api.cms.CmsLog;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class ScriptAppActivator implements BundleActivator {
-       private final static CmsLog log = CmsLog.getLog(ScriptAppActivator.class);
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-               try {
-                       CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication();
-                       appConfig.init(context);
-                       CmsScriptApp app = appConfig.getApp();
-                       ServiceTracker<Repository, Repository> repoSt = new ServiceTracker<Repository, Repository>(context,
-                                       FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) {
-
-                               @Override
-                               public Repository addingService(ServiceReference<Repository> reference) {
-                                       Repository repository = super.addingService(reference);
-                                       appConfig.setRepository(repository);
-                                       CmsScriptApp app = appConfig.getApp();
-                                       app.register(context, appConfig);
-                                       return repository;
-                               }
-
-                       };
-                       repoSt.open();
-               } catch (Exception e) {
-                       log.error("Cannot initialise script bundle " + context.getBundle().getSymbolicName(), e);
-                       throw e;
-               }
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java
deleted file mode 100644 (file)
index 0c870e1..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-
-class ScriptUi implements CmsUiProvider {
-       private final static CmsLog log = CmsLog.getLog(ScriptUi.class);
-
-       private boolean development = true;
-       private ScriptEngine scriptEngine;
-
-       private URL appUrl;
-       // private BundleContext bundleContext;
-       // private String path;
-
-       // private Bindings bindings;
-       // private String script;
-
-       public ScriptUi(BundleContext bundleContext,ScriptEngine scriptEngine, String path) {
-               this.scriptEngine = scriptEngine;
-////           ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
-//             ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-//             ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
-//             try {
-////                   Thread.currentThread().setContextClassLoader(bundleCl);
-////                   scriptEngine = scriptEngineManager.getEngineByName("JavaScript");
-////                   scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext);
-//                     scriptEngine = CmsScriptRwtApplication.loadScriptEngine(originalCcl, bundleCl);
-//
-//             } catch (Exception e) {
-//                     e.printStackTrace();
-//             } finally {
-//                     Thread.currentThread().setContextClassLoader(originalCcl);
-//             }
-               this.appUrl = bundleContext.getBundle().getEntry(path);
-               load();
-       }
-
-       private void load() {
-//             try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-//                     scriptEngine.eval(reader);
-//             } catch (IOException | ScriptException e) {
-//                     log.warn("Cannot execute " + appUrl, e);
-//             }
-
-               try {
-                       scriptEngine.eval("load('" + appUrl + "')");
-               } catch (ScriptException e) {
-                       log.warn("Cannot execute " + appUrl, e);
-               }
-
-       }
-
-       // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws
-       // ScriptException {
-       // super();
-       // this.scriptEngine = scriptEngine;
-       // this.script = script;
-       // bindings = scriptEngine.createBindings();
-       // scriptEngine.eval(script, bindings);
-       // }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               long begin = System.currentTimeMillis();
-               // if (bindings == null) {
-               // bindings = scriptEngine.createBindings();
-               // try {
-               // scriptEngine.eval(script, bindings);
-               // } catch (ScriptException e) {
-               // log.warn("Cannot evaluate script", e);
-               // }
-               // }
-               // Bindings bindings = scriptEngine.createBindings();
-               // bindings.put("parent", parent);
-               // bindings.put("context", context);
-               // URL appUrl = bundleContext.getBundle().getEntry(path);
-               // try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-               // scriptEngine.eval(reader,bindings);
-               // } catch (IOException | ScriptException e) {
-               // log.warn("Cannot execute " + appUrl, e);
-               // }
-
-               if (development)
-                       load();
-
-               Invocable invocable = (Invocable) scriptEngine;
-               try {
-                       invocable.invokeFunction("createUi", parent, context);
-               } catch (NoSuchMethodException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } catch (ScriptException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               }
-
-               long duration = System.currentTimeMillis() - begin;
-               if (log.isTraceEnabled())
-                       log.trace(appUrl + " UI in " + duration + " ms");
-               return null;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js
deleted file mode 100644 (file)
index be9618d..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// CMS
-var ScrolledPage = Java.type('org.argeo.cms.ui.widgets.ScrolledPage');
-
-var CmsScriptApp = Java.type('org.argeo.cms.ui.script.CmsScriptApp');
-var AppUi = Java.type('org.argeo.cms.ui.script.AppUi');
-var Theme = Java.type('org.argeo.cms.ui.script.Theme');
-var ScriptUi = Java.type('org.argeo.cms.ui.script.ScriptUi');
-var CmsUtils = Java.type('org.argeo.cms.ui.util.CmsUiUtils');
-var SimpleCmsHeader = Java.type('org.argeo.cms.ui.util.SimpleCmsHeader');
-var CmsLink = Java.type('org.argeo.cms.ui.util.CmsLink');
-var MenuLink = Java.type('org.argeo.cms.ui.util.MenuLink');
-var UserMenuLink = Java.type('org.argeo.cms.ui.util.UserMenuLink');
-
-// SWT
-var SWT = Java.type('org.eclipse.swt.SWT');
-var Composite = Java.type('org.eclipse.swt.widgets.Composite');
-var Label = Java.type('org.eclipse.swt.widgets.Label');
-var Button = Java.type('org.eclipse.swt.widgets.Button');
-var Text = Java.type('org.eclipse.swt.widgets.Text');
-var Browser = Java.type('org.eclipse.swt.browser.Browser');
-
-var FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
-var GridLayout = Java.type('org.eclipse.swt.layout.GridLayout');
-var RowLayout = Java.type('org.eclipse.swt.layout.RowLayout');
-var FormLayout = Java.type('org.eclipse.swt.layout.FormLayout');
-var GridData = Java.type('org.eclipse.swt.layout.GridData');
-
-function loadNode(node) {
-       var json = CmsScriptApp.toJson(node)
-       var fromJson = JSON.parse(json)
-       return fromJson
-}
-
-function newArea(parent, style, layout) {
-       var control = new Composite(parent, SWT.NONE)
-       control.setLayout(layout)
-       CmsUtils.style(control, style)
-       return control
-}
-
-function newLabel(parent, style, text) {
-       var control = new Label(parent, SWT.WRAP)
-       control.setText(text)
-       CmsUtils.style(control, style)
-       CmsUtils.markup(control)
-       return control
-}
-
-function newButton(parent, style, text) {
-       var control = new Button(parent, SWT.FLAT)
-       control.setText(text)
-       CmsUtils.style(control, style)
-       CmsUtils.markup(control)
-       return control
-}
-
-function newFormLabel(parent, style, text) {
-       return newLabel(parent, style, '<b>' + text + '</b>')
-}
-
-function newText(parent, style, msg) {
-       var control = new Text(parent, SWT.NONE)
-       control.setMessage(msg)
-       CmsUtils.style(control, style)
-       return control
-}
-
-function newScrolledPage(parent) {
-       var scrolled = new ScrolledPage(parent, SWT.NONE)
-       scrolled.setLayoutData(CmsUtils.fillAll())
-       scrolled.setLayout(CmsUtils.noSpaceGridLayout())
-       var page = new Composite(scrolled, SWT.NONE)
-       page.setLayout(CmsUtils.noSpaceGridLayout())
-       page.setBackgroundMode(SWT.INHERIT_NONE)
-       return page
-}
-
-function gridData(control) {
-       var gridData = new GridData()
-       control.setLayoutData(gridData)
-       return gridData
-}
-
-function gridData(control, hAlign, vAlign) {
-       var gridData = new GridData(hAlign, vAlign, false, false)
-       control.setLayoutData(gridData)
-       return gridData
-}
-
-// print(__FILE__, __LINE__, __DIR__)
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java
deleted file mode 100644 (file)
index 7440596..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS user interface scripting. */
-package org.argeo.cms.ui.script;
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java
deleted file mode 100644 (file)
index 947a4d1..0000000
+++ /dev/null
@@ -1,398 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.argeo.util.naming.SharedSecret.X_SHARED_SECRET;
-
-import java.io.IOException;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallback;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.naming.AuthPassword;
-import org.argeo.util.naming.SharedSecret;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Manages history and navigation */
-@Deprecated
-public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implements CmsView {
-       private static final long serialVersionUID = 906558779562569784L;
-
-       private final CmsLog log = CmsLog.getLog(AbstractCmsEntryPoint.class);
-
-       // private final Subject subject;
-       private LoginContext loginContext;
-
-       private final Repository repository;
-       private final String workspace;
-       private final String defaultPath;
-       private final Map<String, String> factoryProperties;
-
-       // Current state
-       private Session session;
-       private Node node;
-       private String nodePath;// useful when changing auth
-       private String state;
-       private Throwable exception;
-
-       // Client services
-       private final JavaScriptExecutor jsExecutor;
-       private final BrowserNavigation browserNavigation;
-
-       public AbstractCmsEntryPoint(Repository repository, String workspace, String defaultPath,
-                       Map<String, String> factoryProperties) {
-               this.repository = repository;
-               this.workspace = workspace;
-               this.defaultPath = defaultPath;
-               this.factoryProperties = new HashMap<String, String>(factoryProperties);
-               // subject = new Subject();
-
-               // Initial login
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
-                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
-                       lc.login();
-               } catch (LoginException e) {
-                       try {
-                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
-                               lc.login();
-                       } catch (LoginException e1) {
-                               throw new CmsException("Cannot log in as anonymous", e1);
-                       }
-               }
-               authChange(lc);
-
-               jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(new CmsNavigationListener());
-       }
-
-       @Override
-       protected Shell createShell(Display display) {
-               Shell shell = super.createShell(display);
-               shell.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_SHELL);
-               display.disposeExec(new Runnable() {
-
-                       @Override
-                       public void run() {
-                               if (log.isTraceEnabled())
-                                       log.trace("Logging out " + session);
-                               JcrUtils.logoutQuietly(session);
-                       }
-               });
-               return shell;
-       }
-
-       @Override
-       protected final void createContents(final Composite parent) {
-               // UiContext.setData(CmsView.KEY, this);
-               CmsSwtUtils.registerCmsView(parent.getShell(), this);
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               try {
-                                       initUi(parent);
-                               } catch (Exception e) {
-                                       throw new CmsException("Cannot create entrypoint contents", e);
-                               }
-                               return null;
-                       }
-               });
-       }
-
-       /** Create UI */
-       protected abstract void initUi(Composite parent);
-
-       /** Recreate UI after navigation or auth change */
-       protected abstract void refresh();
-
-       /**
-        * The node to return when no node was found (for authenticated users and
-        * anonymous)
-        */
-//     private Node getDefaultNode(Session session) throws RepositoryException {
-//             if (!session.hasPermission(defaultPath, "read")) {
-//                     String userId = session.getUserID();
-//                     if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
-//                             // TODO throw a special exception
-//                             throw new CmsException("Login required");
-//                     else
-//                             throw new CmsException("Unauthorized");
-//             }
-//             return session.getNode(defaultPath);
-//     }
-
-       protected String getBaseTitle() {
-               return factoryProperties.get(WebClient.PAGE_TITLE);
-       }
-
-       public void navigateTo(String state) {
-               exception = null;
-               String title = setState(state);
-               doRefresh();
-               if (browserNavigation != null)
-                       browserNavigation.pushState(state, title);
-       }
-
-       // @Override
-       // public synchronized Subject getSubject() {
-       // return subject;
-       // }
-
-       // @Override
-       // public LoginContext getLoginContext() {
-       // return loginContext;
-       // }
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public synchronized void logout() {
-               if (loginContext == null)
-                       throw new CmsException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-                       LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
-                       anonymousLc.login();
-                       authChange(anonymousLc);
-               } catch (LoginException e) {
-                       log.error("Cannot logout", e);
-               }
-       }
-
-       @Override
-       public synchronized void authChange(LoginContext lc) {
-               if (lc == null)
-                       throw new CmsException("Login context cannot be null");
-               // logout previous login context
-               if (this.loginContext != null)
-                       try {
-                               this.loginContext.logout();
-                       } catch (LoginException e1) {
-                               log.warn("Could not log out: " + e1);
-                       }
-               this.loginContext = lc;
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               try {
-                                       JcrUtils.logoutQuietly(session);
-                                       session = repository.login(workspace);
-                                       if (nodePath != null)
-                                               try {
-                                                       node = session.getNode(nodePath);
-                                               } catch (PathNotFoundException e) {
-                                                       navigateTo("~");
-                                               }
-
-                                       // refresh UI
-                                       doRefresh();
-                               } catch (RepositoryException e) {
-                                       throw new CmsException("Cannot perform auth change", e);
-                               }
-                               return null;
-                       }
-
-               });
-       }
-
-       @Override
-       public void exception(final Throwable e) {
-               AbstractCmsEntryPoint.this.exception = e;
-               log.error("Unexpected exception in CMS", e);
-               doRefresh();
-       }
-
-       protected synchronized void doRefresh() {
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               refresh();
-                               return null;
-                       }
-               });
-       }
-
-       /** Sets the state of the entry point and retrieve the related JCR node. */
-       protected synchronized String setState(String newState) {
-               String previousState = this.state;
-
-               String newNodePath = null;
-               String prefix = null;
-               this.state = newState;
-               if (newState.equals("~"))
-                       this.state = "";
-
-               try {
-                       int firstSlash = state.indexOf('/');
-                       if (firstSlash == 0) {
-                               newNodePath = state;
-                               prefix = "";
-                       } else if (firstSlash > 0) {
-                               prefix = state.substring(0, firstSlash);
-                               newNodePath = state.substring(firstSlash);
-                       } else {
-                               newNodePath = defaultPath;
-                               prefix = state;
-
-                       }
-
-                       // auth
-                       int colonIndex = prefix.indexOf('$');
-                       if (colonIndex > 0) {
-                               SharedSecret token = new SharedSecret(new AuthPassword(X_SHARED_SECRET + '$' + prefix)) {
-
-                                       @Override
-                                       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                               super.handle(callbacks);
-                                               // handle HTTP context
-                                               for (Callback callback : callbacks) {
-                                                       if (callback instanceof RemoteAuthCallback) {
-                                                               ((RemoteAuthCallback) callback)
-                                                                               .setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
-                                                               ((RemoteAuthCallback) callback)
-                                                                               .setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
-                                                       }
-                                               }
-                                       }
-                               };
-                               LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, token);
-                               lc.login();
-                               authChange(lc);// sets the node as well
-                               // } else {
-                               // // TODO check consistency
-                               // }
-                       } else {
-                               Node newNode = null;
-                               if (session.nodeExists(newNodePath))
-                                       newNode = session.getNode(newNodePath);
-                               else {
-//                                     throw new CmsException("Data " + newNodePath + " does not exist");
-                                       newNode = null;
-                               }
-                               setNode(newNode);
-                       }
-                       String title = publishMetaData(getNode());
-
-                       if (log.isTraceEnabled())
-                               log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
-
-                       return title;
-               } catch (Exception e) {
-                       log.error("Cannot set state '" + state + "'", e);
-                       if (state.equals("") || newState.equals("~") || newState.equals(previousState))
-                               return "Unrecoverable exception : " + e.getClass().getSimpleName();
-                       if (previousState.equals(""))
-                               previousState = "~";
-                       navigateTo(previousState);
-                       throw new CmsException("Unexpected issue when accessing #" + newState, e);
-               }
-       }
-
-       private String publishMetaData(Node node) throws RepositoryException {
-               // Title
-               String title;
-               if (node != null && node.isNodeType(NodeType.MIX_TITLE) && node.hasProperty(Property.JCR_TITLE))
-                       title = node.getProperty(Property.JCR_TITLE).getString() + " - " + getBaseTitle();
-               else
-                       title = getBaseTitle();
-
-               HttpServletRequest request = UiContext.getHttpRequest();
-               if (request == null)
-                       return null;
-
-               StringBuilder js = new StringBuilder();
-               if (title == null)
-                       title = "";
-               title = title.replace("'", "\\'");// sanitize
-               js.append("document.title = '" + title + "';");
-               jsExecutor.execute(js.toString());
-               return title;
-       }
-
-       // Simply remove some illegal character
-       // private String clean(String stringToClean) {
-       // return stringToClean.replaceAll("'", "").replaceAll("\\n", "")
-       // .replaceAll("\\t", "");
-       // }
-
-       protected synchronized Node getNode() {
-               return node;
-       }
-
-       private synchronized void setNode(Node node) throws RepositoryException {
-               this.node = node;
-               this.nodePath = node == null ? null : node.getPath();
-       }
-
-       protected String getState() {
-               return state;
-       }
-
-       protected Throwable getException() {
-               return exception;
-       }
-
-       protected void resetException() {
-               exception = null;
-       }
-
-       protected Session getSession() {
-               return session;
-       }
-
-       private class CmsNavigationListener implements BrowserNavigationListener {
-               private static final long serialVersionUID = -3591018803430389270L;
-
-               @Override
-               public void navigated(BrowserNavigationEvent event) {
-                       setState(event.getState());
-                       doRefresh();
-               }
-       }
-}
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java
deleted file mode 100644 (file)
index ca93e62..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
-       private final Bundle bundle;
-
-       public BundleResourceLoader(Bundle bundle) {
-               this.bundle = bundle;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               URL res = bundle.getEntry(resourceName);
-               if (res == null) {
-                       res = bundle.getResource(resourceName);
-                       if (res == null)
-                               throw new IllegalArgumentException(
-                                               "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
-               }
-               return res.openStream();
-       }
-
-       public Bundle getBundle() {
-               return bundle;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java
deleted file mode 100644 (file)
index 5de0f91..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.argeo.api.cms.CmsTheme;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
-public class CmsThemeResourceLoader implements ResourceLoader {
-       private final CmsTheme theme;
-
-       public CmsThemeResourceLoader(CmsTheme theme) {
-               super();
-               this.theme = theme;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               return theme.getResourceAsStream(resourceName);
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java
deleted file mode 100644 (file)
index 4008b49..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.cms.web;
-
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAppListener;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.util.LangUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.EventAdmin;
-
-/** An RWT web app integrating with a {@link CmsApp}. */
-public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
-       private final static CmsLog log = CmsLog.getLog(CmsWebApp.class);
-
-       private BundleContext bundleContext;
-       private CmsApp cmsApp;
-       private String cmsAppId;
-       private EventAdmin eventAdmin;
-
-       private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
-
-       private final static String CONTEXT_NAME = "contextName";
-       private String contextName;
-
-       private final static String FAVICON_PNG = "favicon.png";
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               this.bundleContext = bundleContext;
-               contextName = properties.get(CONTEXT_NAME);
-               if (cmsApp != null) {
-                       if (cmsApp.allThemesAvailable())
-                               publishWebApp();
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-               if (cmsApp != null) {
-                       cmsApp.removeCmsAppListener(this);
-                       cmsApp = null;
-               }
-       }
-
-       @Override
-       public void configure(Application application) {
-               // TODO make it configurable?
-               // SWT compatibility is required for:
-               // - Browser.execute()
-               // - blocking dialogs
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-               for (String uiName : cmsApp.getUiNames()) {
-                       CmsTheme theme = cmsApp.getTheme(uiName);
-                       if (theme != null)
-                               WebThemeUtils.apply(application, theme);
-               }
-
-               Map<String, String> properties = new HashMap<>();
-               addEntryPoints(application, properties);
-               application.setExceptionHandler(this);
-       }
-
-       @Override
-       public void handleException(Throwable throwable) {
-               Display display = Display.getCurrent();
-               if (display != null && !display.isDisposed()) {
-                       CmsView cmsView = CmsSwtUtils.getCmsView(display.getActiveShell());
-                       cmsView.exception(throwable);
-               } else {
-                       log.error("Unexpected exception outside an UI thread", throwable);
-               }
-
-       }
-
-       protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
-               for (String uiName : cmsApp.getUiNames()) {
-                       Map<String, String> properties = new HashMap<>(commonProperties);
-                       CmsTheme theme = cmsApp.getTheme(uiName);
-                       if (theme != null) {
-                               properties.put(WebClient.THEME_ID, theme.getThemeId());
-                               properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
-                               properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
-                               Set<String> imagePaths = theme.getImagesPaths();
-                               if (imagePaths.contains(FAVICON_PNG)) {
-                                       properties.put(WebClient.FAVICON, FAVICON_PNG);
-                               }
-                       } else {
-                               properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
-                       }
-                       String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
-                       application.addEntryPoint(entryPointName, () -> {
-                               CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
-                               entryPoint.setEventAdmin(eventAdmin);
-                               return entryPoint;
-                       }, properties);
-                       if (log.isDebugEnabled())
-                               log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
-               }
-//             if (log.isDebugEnabled())
-//                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
-       }
-
-       CmsApp getCmsApp() {
-               return cmsApp;
-       }
-
-       BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
-               this.cmsApp = cmsApp;
-               this.cmsAppId = properties.get(Constants.SERVICE_PID);
-               this.cmsApp.addCmsAppListener(this);
-       }
-
-       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
-               String cmsAppId = properties.get(Constants.SERVICE_PID);
-               if (!cmsAppId.equals(this.cmsAppId))
-                       return;
-               if (this.cmsApp != null) {
-                       this.cmsApp.removeCmsAppListener(this);
-               }
-               if (rwtAppReg != null)
-                       rwtAppReg.unregister();
-               this.cmsApp = null;
-       }
-
-       @Override
-       public void themingUpdated() {
-               if (cmsApp != null && cmsApp.allThemesAvailable())
-                       publishWebApp();
-       }
-
-       protected void publishWebApp() {
-               Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
-               if (rwtAppReg != null)
-                       rwtAppReg.unregister();
-               if (bundleContext != null) {
-                       rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
-                       if (log.isDebugEnabled())
-                               log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
-               }
-       }
-
-       public void setEventAdmin(EventAdmin eventAdmin) {
-               this.eventAdmin = eventAdmin;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
deleted file mode 100644 (file)
index afc07c5..0000000
+++ /dev/null
@@ -1,357 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsUi;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.servlet.ServletHttpRequest;
-import org.argeo.cms.servlet.ServletHttpResponse;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.SWTError;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-
-/** The {@link CmsView} for a {@link CmsWebApp}. */
-@SuppressWarnings("restriction")
-public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener {
-       private static final long serialVersionUID = 7733510691684570402L;
-       private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class);
-
-       private EventAdmin eventAdmin;
-
-       private final CmsWebApp cmsWebApp;
-       private final String uiName;
-
-       private LoginContext loginContext;
-       private String state;
-       private Throwable exception;
-       private UxContext uxContext;
-       private CmsImageManager imageManager;
-
-       private Display display;
-       private CmsUi ui;
-
-       private String uid;
-
-       // Client services
-       // private final JavaScriptExecutor jsExecutor;
-       private final BrowserNavigation browserNavigation;
-
-       /** Experimental OS-like multi windows. */
-       private boolean multipleShells = false;
-
-       public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
-               assert cmsWebApp != null;
-               assert uiName != null;
-               this.cmsWebApp = cmsWebApp;
-               this.uiName = uiName;
-               uid = UUID.randomUUID().toString();
-
-               // Initial login
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
-                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
-                       lc.login();
-               } catch (LoginException e) {
-                       try {
-                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
-                               lc.login();
-                       } catch (LoginException e1) {
-                               throw new IllegalStateException("Cannot log in as anonymous", e1);
-                       }
-               }
-               authChange(lc);
-
-               // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(this);
-       }
-
-       protected void createContents(Composite parent) {
-               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               try {
-                                       uxContext = new SimpleSwtUxContext();
-                                       imageManager = new DefaultImageManager();
-                                       CmsSession cmsSession = getCmsSession();
-                                       if (cmsSession != null) {
-                                               UiContext.setLocale(cmsSession.getLocale());
-                                               LocaleUtils.setThreadLocale(cmsSession.getLocale());
-                                       } else {
-                                               Locale rwtLocale = RWT.getUISession().getLocale();
-                                               LocaleUtils.setThreadLocale(rwtLocale);
-                                       }
-                                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
-                                       display = parent.getDisplay();
-                                       ui = cmsWebApp.getCmsApp().initUi(parent);
-                                       if (ui instanceof Composite)
-                                               ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
-                                       // we need ui to be set before refresh so that CmsView can store UI context data
-                                       // in it.
-                                       cmsWebApp.getCmsApp().refreshUi(ui, null);
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot create entrypoint contents", e);
-                               }
-                               return null;
-                       }
-               });
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       public <T> T doAs(PrivilegedAction<T> action) {
-               return Subject.doAs(getSubject(), action);
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public synchronized void logout() {
-               if (loginContext == null)
-                       throw new IllegalArgumentException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-                       LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
-                       anonymousLc.login();
-                       authChange(anonymousLc);
-               } catch (LoginException e) {
-                       log.error("Cannot logout", e);
-               }
-       }
-
-       @Override
-       public synchronized void authChange(LoginContext lc) {
-               if (lc == null)
-                       throw new IllegalArgumentException("Login context cannot be null");
-               // logout previous login context
-               if (this.loginContext != null)
-                       try {
-                               this.loginContext.logout();
-                       } catch (LoginException e1) {
-                               log.warn("Could not log out: " + e1);
-                       }
-               this.loginContext = lc;
-               doRefresh();
-       }
-
-       @Override
-       public void exception(final Throwable e) {
-               if (e instanceof SWTError) {
-                       SWTError swtError = (SWTError) e;
-                       if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
-                               return;
-               }
-               display.syncExec(() -> {
-//                     CmsFeedback.show("Unexpected exception in CMS", e);
-                       exception = e;
-//             log.error("Unexpected exception in CMS", e);
-                       doRefresh();
-               });
-       }
-
-       protected synchronized void doRefresh() {
-               if (ui != null)
-                       Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                               @Override
-                               public Void run() {
-                                       if (exception != null) {
-                                               // TODO internationalise
-                                               CmsFeedback.show("Unexpected exception", exception);
-                                               exception = null;
-                                               // TODO report
-                                       }
-                                       cmsWebApp.getCmsApp().refreshUi(ui, state);
-                                       return null;
-                               }
-                       });
-       }
-
-       /** Sets the state of the entry point and retrieve the related JCR node. */
-       protected String setState(String newState) {
-               cmsWebApp.getCmsApp().setState(ui, newState);
-               state = newState;
-               return null;
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               exception = null;
-               String title = setState(state);
-               if (title != null)
-                       doRefresh();
-               if (browserNavigation != null)
-                       browserNavigation.pushState(state, title);
-       }
-
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       @Override
-       public void navigated(BrowserNavigationEvent event) {
-               setState(event.getState());
-               // doRefresh();
-       }
-
-       @Override
-       public void sendEvent(String topic, Map<String, Object> properties) {
-               if (properties == null)
-                       properties = new HashMap<>();
-               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
-                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
-                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
-               properties.put(CMS_VIEW_UID_PROPERTY, uid);
-               eventAdmin.sendEvent(new Event(topic, properties));
-       }
-
-       @Override
-       public void stateChanged(String state, String title) {
-               browserNavigation.pushState(state, title);
-       }
-
-       @Override
-       public CmsSession getCmsSession() {
-               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(cmsWebApp.getBundleContext(), getSubject());
-               return cmsSession;
-       }
-
-       @Override
-       public Object getData(String key) {
-               if (ui != null) {
-                       return ui.getData(key);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       @Override
-       public void setData(String key, Object value) {
-               if (ui != null) {
-                       ui.setData(key, value);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       /*
-        * EntryPoint IMPLEMENTATION
-        */
-
-       @Override
-       public int createUI() {
-               Display display = new Display();
-               Shell shell = createShell(display);
-               shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               CmsSwtUtils.registerCmsView(shell, this);
-               createContents(shell);
-               shell.layout();
-//             if (shell.getMaximized()) {
-//                     shell.layout();
-//             } else {
-////                   shell.pack();
-//             }
-               shell.open();
-               if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
-                       eventLoop: while (!shell.isDisposed()) {
-                               try {
-                                       if (!display.readAndDispatch()) {
-                                               display.sleep();
-                                       }
-                               } catch (Throwable e) {
-                                       if (e instanceof SWTError) {
-                                               SWTError swtError = (SWTError) e;
-                                               if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
-                                                       log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
-                                                       continue eventLoop;
-                                               } else {
-                                                       log.error("Unexpected SWT error in event loop, shutting down...", e);
-                                                       break eventLoop;
-                                               }
-                                       } else if (e instanceof ThreadDeath) {
-                                               throw (ThreadDeath) e;
-                                       } else if (e instanceof Error) {
-                                               log.error("Unexpected error in event loop, shutting down...", e);
-                                               break eventLoop;
-                                       } else {
-                                               log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
-                                               continue eventLoop;
-                                       }
-                               }
-                       }
-                       if (!display.isDisposed())
-                               display.dispose();
-               }
-               return 0;
-       }
-
-       protected Shell createShell(Display display) {
-               Shell shell;
-               if (!multipleShells) {
-                       shell = new Shell(display, SWT.NO_TRIM);
-                       shell.setMaximized(true);
-               } else {
-                       shell = new Shell(display, SWT.SHELL_TRIM);
-                       shell.setSize(800, 600);
-               }
-               return shell;
-       }
-
-       public void setEventAdmin(EventAdmin eventAdmin) {
-               this.eventAdmin = eventAdmin;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java
deleted file mode 100644 (file)
index 2eff71e..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.osgi.BundleCmsTheme;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Lightweight web app using only RWT and not the whole Eclipse platform. */
-public class MinimalWebApp implements ApplicationConfiguration {
-
-       private BundleCmsTheme theme;
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
-                       String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
-                       theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
-               }
-       }
-
-       public void destroy() {
-
-       }
-
-       /** To be overridden. Does nothing by default. */
-       protected void addEntryPoints(Application application, Map<String, String> properties) {
-
-       }
-
-       @Override
-       public void configure(Application application) {
-               if (theme != null)
-                       WebThemeUtils.apply(application, theme);
-
-               Map<String, String> properties = new HashMap<>();
-               if (theme != null) {
-                       properties.put(WebClient.THEME_ID, theme.getThemeId());
-                       properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
-               } else {
-                       properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
-               }
-               addEntryPoints(application, properties);
-
-       }
-
-       public void setTheme(BundleCmsTheme theme) {
-               this.theme = theme;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java
deleted file mode 100644 (file)
index f063117..0000000
+++ /dev/null
@@ -1,414 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.jcr.CmsJcrUtils;
-import org.argeo.cms.ui.CmsUiConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.LifeCycleUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.StyleSheetResourceLoader;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-/** A basic generic app based on {@link SimpleErgonomics}. */
-@Deprecated
-public class SimpleApp implements CmsUiConstants, ApplicationConfiguration {
-       private final static CmsLog log = CmsLog.getLog(SimpleApp.class);
-
-       private String contextName = null;
-
-       private Map<String, Map<String, String>> branding = new HashMap<String, Map<String, String>>();
-       private Map<String, List<String>> styleSheets = new HashMap<String, List<String>>();
-
-       private List<String> resources = new ArrayList<String>();
-
-       private BundleContext bundleContext;
-
-       private Repository repository;
-       private String workspace = null;
-       private String jcrBasePath = "/";
-       private List<String> roPrincipals = Arrays.asList(CmsConstants.ROLE_ANONYMOUS, CmsConstants.ROLE_USER);
-       private List<String> rwPrincipals = Arrays.asList(CmsConstants.ROLE_USER);
-
-       private CmsUiProvider header;
-       private Map<String, CmsUiProvider> pages = new LinkedHashMap<String, CmsUiProvider>();
-
-       private Integer headerHeight = 40;
-
-       private ServiceRegistration<ApplicationConfiguration> appReg;
-
-       public void configure(Application application) {
-               try {
-                       BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
-                       application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-                       // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
-                       application.setExceptionHandler(new CmsExceptionHandler());
-
-                       // loading animated gif
-                       application.addResource(LOADING_IMAGE, createResourceLoader(LOADING_IMAGE));
-                       // empty image
-                       application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE));
-
-                       for (String resource : resources) {
-                               application.addResource(resource, bundleRL);
-                               if (log.isTraceEnabled())
-                                       log.trace("Resource " + resource);
-                       }
-
-                       Map<String, String> defaultBranding = null;
-                       if (branding.containsKey("*"))
-                               defaultBranding = branding.get("*");
-                       // String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
-
-                       // entry points
-                       for (String page : pages.keySet()) {
-                               Map<String, String> properties = defaultBranding != null ? new HashMap<String, String>(defaultBranding)
-                                               : new HashMap<String, String>();
-                               if (branding.containsKey(page)) {
-                                       properties.putAll(branding.get(page));
-                               }
-                               // favicon
-                               if (properties.containsKey(WebClient.FAVICON)) {
-                                       String themeId = defaultBranding.get(WebClient.THEME_ID);
-                                       Bundle themeBundle = findThemeBundle(bundleContext, themeId);
-                                       String faviconRelPath = properties.get(WebClient.FAVICON);
-                                       application.addResource(faviconRelPath,
-                                                       new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
-                                       if (log.isTraceEnabled())
-                                               log.trace("Favicon " + faviconRelPath);
-
-                               }
-
-                               // page title
-                               if (!properties.containsKey(WebClient.PAGE_TITLE)) {
-                                       if (page.length() > 0)
-                                               properties.put(WebClient.PAGE_TITLE, Character.toUpperCase(page.charAt(0)) + page.substring(1));
-                               }
-
-                               // default body HTML
-                               if (!properties.containsKey(WebClient.BODY_HTML))
-                                       properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY);
-
-                               //
-                               // ADD ENTRY POINT
-                               //
-                               application.addEntryPoint("/" + page,
-                                               new CmsEntryPointFactory(pages.get(page), repository, workspace, properties), properties);
-                               log.info("Page /" + page);
-                       }
-
-                       // stylesheets and themes
-                       Set<Bundle> themeBundles = new HashSet<>();
-                       for (String themeId : styleSheets.keySet()) {
-                               Bundle themeBundle = findThemeBundle(bundleContext, themeId);
-                               StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
-                                               themeBundle != null ? themeBundle : bundleContext.getBundle());
-                               if (themeBundle != null)
-                                       themeBundles.add(themeBundle);
-                               List<String> cssLst = styleSheets.get(themeId);
-                               if (log.isDebugEnabled())
-                                       log.debug("Theme " + themeId);
-                               for (String css : cssLst) {
-                                       application.addStyleSheet(themeId, css, styleSheetRL);
-                                       if (log.isDebugEnabled())
-                                               log.debug(" CSS " + css);
-                               }
-
-                       }
-                       for (Bundle themeBundle : themeBundles) {
-                               BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.png");
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.gif");
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.jpg");
-                       }
-               } catch (RuntimeException e) {
-                       // Easier access to initialisation errors
-                       log.error("Unexpected exception when configuring RWT application.", e);
-                       throw e;
-               }
-       }
-
-       public void init() throws RepositoryException {
-               Session session = null;
-               try {
-                       session = CmsJcrUtils.openDataAdminSession(repository, workspace);
-                       // session = JcrUtils.loginOrCreateWorkspace(repository, workspace);
-                       VersionManager vm = session.getWorkspace().getVersionManager();
-                       JcrUtils.mkdirs(session, jcrBasePath);
-                       session.save();
-                       if (!vm.isCheckedOut(jcrBasePath))
-                               vm.checkout(jcrBasePath);
-                       for (String principal : rwPrincipals)
-                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_WRITE);
-                       for (String principal : roPrincipals)
-                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_READ);
-
-                       for (String pageName : pages.keySet()) {
-                               try {
-                                       initPage(session, pages.get(pageName));
-                                       session.save();
-                               } catch (Exception e) {
-                                       throw new CmsException("Cannot initialize page " + pageName, e);
-                               }
-                       }
-
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-
-               // publish to OSGi
-               register();
-       }
-
-       protected void initPage(Session adminSession, CmsUiProvider page) throws RepositoryException {
-               if (page instanceof LifeCycleUiProvider)
-                       ((LifeCycleUiProvider) page).init(adminSession);
-       }
-
-       public void destroy() {
-               for (String pageName : pages.keySet()) {
-                       try {
-                               CmsUiProvider page = pages.get(pageName);
-                               if (page instanceof LifeCycleUiProvider)
-                                       ((LifeCycleUiProvider) page).destroy();
-                       } catch (Exception e) {
-                               log.error("Cannot destroy page " + pageName, e);
-                       }
-               }
-       }
-
-       protected void register() {
-               Hashtable<String, String> props = new Hashtable<String, String>();
-               if (contextName != null)
-                       props.put("contextName", contextName);
-               appReg = bundleContext.registerService(ApplicationConfiguration.class, this, props);
-               if (log.isDebugEnabled())
-                       log.debug("Registered " + (contextName == null ? "/" : contextName));
-       }
-
-       protected void unregister() {
-               appReg.unregister();
-               if (log.isDebugEnabled())
-                       log.debug("Unregistered " + (contextName == null ? "/" : contextName));
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public void setPages(Map<String, CmsUiProvider> pages) {
-               this.pages = pages;
-       }
-
-       public void setJcrBasePath(String basePath) {
-               this.jcrBasePath = basePath;
-       }
-
-       public void setRoPrincipals(List<String> roPrincipals) {
-               this.roPrincipals = roPrincipals;
-       }
-
-       public void setRwPrincipals(List<String> rwPrincipals) {
-               this.rwPrincipals = rwPrincipals;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public void setBranding(Map<String, Map<String, String>> branding) {
-               this.branding = branding;
-       }
-
-       public void setStyleSheets(Map<String, List<String>> styleSheets) {
-               this.styleSheets = styleSheets;
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-       public void setResources(List<String> resources) {
-               this.resources = resources;
-       }
-
-       public void setContextName(String contextName) {
-               this.contextName = contextName;
-       }
-
-       private static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL,
-                       String pattern) {
-               Enumeration<URL> themeResources = themeBundle.findEntries("/", pattern, true);
-               if (themeResources == null)
-                       return;
-               while (themeResources.hasMoreElements()) {
-                       String resource = themeResources.nextElement().getPath();
-                       // remove first '/' so that RWT registers it
-                       resource = resource.substring(1);
-                       if (!resource.endsWith("/")) {
-                               application.addResource(resource, themeBRL);
-                               if (log.isTraceEnabled())
-                                       log.trace("Registered " + resource + " from theme " + themeBundle);
-                       }
-
-               }
-
-       }
-
-       private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
-               if (themeId == null)
-                       return null;
-               // TODO optimize
-               // TODO deal with multiple versions
-               Bundle themeBundle = null;
-               if (themeId != null) {
-                       for (Bundle bundle : bundleContext.getBundles())
-                               if (themeId.equals(bundle.getSymbolicName())) {
-                                       themeBundle = bundle;
-                                       break;
-                               }
-               }
-               return themeBundle;
-       }
-
-       class CmsExceptionHandler implements ExceptionHandler {
-
-               @Override
-               public void handleException(Throwable throwable) {
-                       // TODO be smarter
-                       CmsUiUtils.getCmsView().exception(throwable);
-               }
-
-       }
-
-       private class CmsEntryPointFactory implements EntryPointFactory {
-               private final CmsUiProvider page;
-               private final Repository repository;
-               private final String workspace;
-               private final Map<String, String> properties;
-
-               public CmsEntryPointFactory(CmsUiProvider page, Repository repository, String workspace,
-                               Map<String, String> properties) {
-                       this.page = page;
-                       this.repository = repository;
-                       this.workspace = workspace;
-                       this.properties = properties;
-               }
-
-               @Override
-               public EntryPoint create() {
-                       SimpleErgonomics entryPoint = new SimpleErgonomics(repository, workspace, jcrBasePath, page, properties) {
-                               private static final long serialVersionUID = -637940404865527290L;
-
-                               @Override
-                               protected void createAdminArea(Composite parent) {
-                                       Composite adminArea = new Composite(parent, SWT.NONE);
-                                       adminArea.setLayout(new FillLayout());
-                                       Button refresh = new Button(adminArea, SWT.PUSH);
-                                       refresh.setText("Reload App");
-                                       refresh.addSelectionListener(new SelectionAdapter() {
-                                               private static final long serialVersionUID = -7671999525536351366L;
-
-                                               @Override
-                                               public void widgetSelected(SelectionEvent e) {
-                                                       long timeBeforeReload = 1000;
-                                                       RWT.getClient().getService(JavaScriptExecutor.class).execute(
-                                                                       "setTimeout(function() { " + "location.reload();" + "}," + timeBeforeReload + ");");
-                                                       reloadApp();
-                                               }
-                                       });
-                               }
-                       };
-                       // entryPoint.setState("");
-                       entryPoint.setHeader(header);
-                       entryPoint.setHeaderHeight(headerHeight);
-                       // CmsSession.current.set(entryPoint);
-                       return entryPoint;
-               }
-
-               private void reloadApp() {
-                       new Thread("Refresh app") {
-                               @Override
-                               public void run() {
-                                       unregister();
-                                       register();
-                               }
-                       }.start();
-               }
-       }
-
-       private static ResourceLoader createResourceLoader(final String resourceName) {
-               return new ResourceLoader() {
-                       public InputStream getResourceAsStream(String resourceName) throws IOException {
-                               return getClass().getClassLoader().getResourceAsStream(resourceName);
-                       }
-               };
-       }
-
-       // private static ResourceLoader createUrlResourceLoader(final URL url) {
-       // return new ResourceLoader() {
-       // public InputStream getResourceAsStream(String resourceName)
-       // throws IOException {
-       // return url.openStream();
-       // }
-       // };
-       // }
-
-       /*
-        * TEXTS
-        */
-       private static String DEFAULT_LOADING_BODY = "<div"
-                       + " style=\"position: absolute; left: 50%; top: 50%; margin: -32px -32px; width: 64px; height:64px\">"
-                       + "<img src=\"./rwt-resources/" + LOADING_IMAGE
-                       + "\" width=\"32\" height=\"32\" style=\"margin: 16px 16px\"/>" + "</div>";
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java
deleted file mode 100644 (file)
index 26ca370..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-package org.argeo.cms.web;
-
-import java.util.Map;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.swt.CmsStyles;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.cms.ui.util.SystemNotifications;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Simple header/body ergonomics. */
-@Deprecated
-public class SimpleErgonomics extends AbstractCmsEntryPoint {
-       private static final long serialVersionUID = 8743413921359548523L;
-
-       private final static CmsLog log = CmsLog.getLog(SimpleErgonomics.class);
-
-       private boolean uiInitialized = false;
-       private Composite headerArea;
-       private Composite leftArea;
-       private Composite rightArea;
-       private Composite footerArea;
-       private Composite bodyArea;
-       private final CmsUiProvider uiProvider;
-
-       private CmsUiProvider header;
-       private Integer headerHeight = 0;
-       private Integer footerHeight = 0;
-       private CmsUiProvider lead;
-       private CmsUiProvider end;
-       private CmsUiProvider footer;
-
-       private CmsImageManager imageManager = new DefaultImageManager();
-       private UxContext uxContext = null;
-       private String uid;
-
-       public SimpleErgonomics(Repository repository, String workspace, String defaultPath, CmsUiProvider uiProvider,
-                       Map<String, String> factoryProperties) {
-               super(repository, workspace, defaultPath, factoryProperties);
-               this.uiProvider = uiProvider;
-       }
-
-       @Override
-       protected void initUi(Composite parent) {
-               uid = UUID.randomUUID().toString();
-               parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
-
-               uxContext = new SimpleSwtUxContext();
-               if (!getUxContext().isMasterData())
-                       createAdminArea(parent);
-               headerArea = new Composite(parent, SWT.NONE);
-               headerArea.setLayout(new FillLayout());
-               GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
-               headerData.heightHint = headerHeight;
-               headerArea.setLayoutData(headerData);
-
-               // TODO: bi-directional
-               leftArea = new Composite(parent, SWT.NONE);
-               leftArea.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
-               leftArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               bodyArea = new Composite(parent, SWT.NONE);
-               bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
-               bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               // TODO: bi-directional
-               rightArea = new Composite(parent, SWT.NONE);
-               rightArea.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
-               rightArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               footerArea = new Composite(parent, SWT.NONE);
-               // footerArea.setLayout(new FillLayout());
-               GridData footerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
-               footerData.heightHint = footerHeight;
-               footerArea.setLayoutData(footerData);
-
-               uiInitialized = true;
-               refresh();
-       }
-
-       @Override
-       protected void refresh() {
-               if (!uiInitialized)
-                       return;
-               if (getState() == null)
-                       setState("");
-               refreshSides();
-               refreshBody();
-               if (log.isTraceEnabled())
-                       log.trace("UI refreshed " + getNode());
-       }
-
-       protected void createAdminArea(Composite parent) {
-       }
-
-       @Deprecated
-       protected void refreshHeader() {
-               if (header == null)
-                       return;
-
-               for (Control child : headerArea.getChildren())
-                       child.dispose();
-               try {
-                       header.createUi(headerArea, getNode());
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh header", e);
-               }
-               headerArea.layout(true, true);
-       }
-
-       protected void refreshSides() {
-               refresh(headerArea, header, CmsStyles.CMS_HEADER);
-               refresh(leftArea, lead, CmsStyles.CMS_LEAD);
-               refresh(rightArea, end, CmsStyles.CMS_END);
-               refresh(footerArea, footer, CmsStyles.CMS_FOOTER);
-       }
-
-       private void refresh(Composite area, CmsUiProvider uiProvider, String style) {
-               if (uiProvider == null)
-                       return;
-
-               for (Control child : area.getChildren())
-                       child.dispose();
-               CmsSwtUtils.style(area, style);
-               try {
-                       uiProvider.createUi(area, getNode());
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh header", e);
-               }
-               area.layout(true, true);
-       }
-
-       protected void refreshBody() {
-               // Exception
-               Throwable exception = getException();
-               if (exception != null) {
-                       SystemNotifications systemNotifications = new SystemNotifications(bodyArea);
-                       systemNotifications.notifyException(exception);
-                       resetException();
-                       return;
-                       // TODO report
-               }
-
-               // clear
-               for (Control child : bodyArea.getChildren())
-                       child.dispose();
-               bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
-
-               try {
-                       Node node = getNode();
-//                     if (node == null)
-//                             log.error("Context cannot be null");
-//                     else
-                       uiProvider.createUi(bodyArea, node);
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh body", e);
-               }
-
-               bodyArea.layout(true, true);
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public void setImageManager(CmsImageManager imageManager) {
-               this.imageManager = imageManager;
-       }
-
-       public CmsUiProvider getLead() {
-               return lead;
-       }
-
-       public void setLead(CmsUiProvider lead) {
-               this.lead = lead;
-       }
-
-       public CmsUiProvider getEnd() {
-               return end;
-       }
-
-       public void setEnd(CmsUiProvider end) {
-               this.end = end;
-       }
-
-       public CmsUiProvider getFooter() {
-               return footer;
-       }
-
-       public void setFooter(CmsUiProvider footer) {
-               this.footer = footer;
-       }
-
-       public CmsUiProvider getHeader() {
-               return header;
-       }
-
-       public void setFooterHeight(Integer footerHeight) {
-               this.footerHeight = footerHeight;
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java
deleted file mode 100644 (file)
index ea2ebdf..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.argeo.cms.web;
-
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsLog;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** Web specific utilities around theming. */
-public class WebThemeUtils {
-       private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class);
-
-       public static void apply(Application application, CmsTheme theme) {
-               ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
-               resources: for (String path : theme.getImagesPaths()) {
-                       if (path.startsWith("target/"))
-                               continue resources; // skip maven output
-                       application.addResource(path, resourceLoader);
-                       if (log.isTraceEnabled())
-                               log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
-               }
-               for (String path : theme.getRapCssPaths()) {
-                       application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
-                       if (log.isDebugEnabled())
-                               log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
-               }
-       }
-
-}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java
deleted file mode 100644 (file)
index 29165a4..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.argeo.eclipse.ui.jetty;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.application.ApplicationRunner;
-import org.eclipse.rap.rwt.engine.RWTServlet;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** A minimal RWT runner based on embedded Jetty. */
-public class RwtRunner {
-
-       private final Server server;
-       private final ServerConnector serverConnector;
-       private Path tempDir;
-
-       public RwtRunner() {
-               server = new Server(new QueuedThreadPool(10, 1));
-               serverConnector = new ServerConnector(server);
-               serverConnector.setPort(0);
-               server.setConnectors(new Connector[] { serverConnector });
-       }
-
-       protected Control createUi(Composite parent, Object context) {
-               return new Label(parent, SWT.NONE);
-       }
-
-       public void init() {
-               ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-               context.setContextPath("/");
-               server.setHandler(context);
-
-               String entryPoint = "app";
-
-               // rwt-resources requires a file system
-               try {
-                       tempDir = Files.createTempDirectory("argeo-rwtRunner");
-                       context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString()));
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot create temporary directory", e);
-               }
-               context.addEventListener(new ServletContextListener() {
-                       ApplicationRunner applicationRunner;
-
-                       @Override
-                       public void contextInitialized(ServletContextEvent sce) {
-                               applicationRunner = new ApplicationRunner(
-                                               (application) -> application.addEntryPoint("/" + entryPoint, () -> new AbstractEntryPoint() {
-                                                       private static final long serialVersionUID = 5678385921969090733L;
-
-                                                       @Override
-                                                       protected void createContents(Composite parent) {
-                                                               createUi(parent, null);
-                                                       }
-                                               }, null), sce.getServletContext());
-                               applicationRunner.start();
-                       }
-
-                       @Override
-                       public void contextDestroyed(ServletContextEvent sce) {
-                               applicationRunner.stop();
-                       }
-               });
-
-               context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
-
-               // Required to serve rwt-resources. It is important that this is last.
-               ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
-               context.addServlet(holderPwd, "/");
-
-               try {
-                       server.start();
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot start Jetty server", e);
-               }
-       }
-
-       public void destroy() {
-               try {
-                       serverConnector.close();
-                       server.stop();
-                       // TODO delete temp dir
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public Integer getEffectivePort() {
-               return serverConnector.getLocalPort();
-       }
-
-       public void waitFor() throws InterruptedException {
-               server.join();
-       }
-
-       public static void main(String[] args) throws Exception {
-               RwtRunner rwtRunner = new RwtRunner() {
-
-                       @Override
-                       protected Control createUi(Composite parent, Object context) {
-                               Label label = new Label(parent, SWT.NONE);
-                               label.setText("Hello world!");
-                               return label;
-                       }
-               };
-               rwtRunner.init();
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> rwtRunner.destroy(), "Jetty shutdown"));
-
-               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-               System.out.println("App available in " + jvmUptime + " ms, on port " + rwtRunner.getEffectivePort());
-
-               // open browser in app mode
-               Thread.sleep(2000);// wait for RWT to be ready
-               Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app");
-
-               rwtRunner.waitFor();
-       }
-}
diff --git a/rap/org.argeo.swt.specific.rap/.classpath b/rap/org.argeo.swt.specific.rap/.classpath
deleted file mode 100644 (file)
index e03d341..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="con"
-               path="org.eclipse.pde.core.requiredPlugins" />
-       <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
-       <classpathentry kind="output" path="bin" />
-</classpath>
diff --git a/rap/org.argeo.swt.specific.rap/.project b/rap/org.argeo.swt.specific.rap/.project
deleted file mode 100644 (file)
index 53d7976..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.swt.specific.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rap/org.argeo.swt.specific.rap/META-INF/.gitignore b/rap/org.argeo.swt.specific.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rap/org.argeo.swt.specific.rap/bnd.bnd b/rap/org.argeo.swt.specific.rap/bnd.bnd
deleted file mode 100644 (file)
index bcd9b19..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Import-Package: org.eclipse.swt,\
-org.eclipse.jface.dialogs,\
-org.eclipse.swt.events,\
-javax.servlet.http;version="[3,5)",\
-*
diff --git a/rap/org.argeo.swt.specific.rap/build.properties b/rap/org.argeo.swt.specific.rap/build.properties
deleted file mode 100644 (file)
index fd806ca..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-source.. = src/
-output.. = bin/
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
deleted file mode 100644 (file)
index 6100c1a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class CmsFileDialog extends FileDialog {
-       private static final long serialVersionUID = -7540791204102318801L;
-
-       public CmsFileDialog(Shell parent, int style) {
-               super(parent, style);
-       }
-
-       public CmsFileDialog(Shell parent) {
-               super(parent);
-       }
-
-}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
deleted file mode 100644 (file)
index 3f30bde..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-
-public class CmsFileUpload extends FileUpload {
-       private static final long serialVersionUID = 8027963992680980549L;
-
-       public CmsFileUpload(Composite parent, int style) {
-               super(parent, style);
-       }
-
-       @Override
-       public void setText(String text) {
-               super.setText(text);
-       }
-
-       @Override
-       public String getFileName() {
-               return super.getFileName();
-       }
-
-       @Override
-       public String[] getFileNames() {
-               return super.getFileNames();
-       }
-
-       @Override
-       public void addSelectionListener(SelectionListener listener) {
-               super.addSelectionListener(listener);
-       }
-
-}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
deleted file mode 100644 (file)
index a89b921..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.jface.viewers.AbstractTableViewer;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities to bridge differences between RCP and RAP */
-public class EclipseUiSpecificUtils {
-
-       public static void setStyleData(Widget widget, Object data) {
-               widget.setData(RWT.CUSTOM_VARIANT, data);
-       }
-
-       public static Object getStyleData(Widget widget) {
-               return widget.getData(RWT.CUSTOM_VARIANT);
-       }
-
-       public static void setMarkupData(Widget widget) {
-               widget.setData(RWT.MARKUP_ENABLED, true);
-       }
-
-       public static void setMarkupValidationDisabledData(Widget widget) {
-               widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
-       }
-
-       /**
-        * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
-        */
-       public static void enableToolTipSupport(Viewer viewer) {
-               if (viewer instanceof ColumnViewer)
-                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
-       }
-
-       private EclipseUiSpecificUtils() {
-       }
-}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
deleted file mode 100644 (file)
index f9ca816..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.ClientFile;
-import org.eclipse.rap.rwt.client.service.ClientFileUploader;
-import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetAdapter;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Control;
-
-/** Configures a {@link Control} to receive files drop from the client OS. */
-public class FileDropAdapter {
-
-       public void prepareDropTarget(Control control, DropTarget dropTarget) {
-               dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
-               dropTarget.addDropListener(new DropTargetAdapter() {
-                       private static final long serialVersionUID = 5361645765549463168L;
-
-                       @Override
-                       public void dropAccept(DropTargetEvent event) {
-                               if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
-                                       event.detail = DND.DROP_NONE;
-                               }
-                       }
-
-                       @Override
-                       public void drop(DropTargetEvent event) {
-                               handleFileDrop(control, event);
-                       }
-               });
-       }
-
-       public void handleFileDrop(Control control, DropTargetEvent event) {
-               ClientFile[] clientFiles = (ClientFile[]) event.data;
-               ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
-//             DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
-               FileUploadReceiver receiver = new FileUploadReceiver() {
-
-                       @Override
-                       public void receive(InputStream stream, FileDetails details) throws IOException {
-                               control.getDisplay().syncExec(() -> {
-                                       try {
-                                               processUpload(stream, details.getFileName(), details.getContentType());
-                                       } catch (IOException e) {
-                                               throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
-                                       }
-                               });
-                       }
-               };
-               FileUploadHandler handler = new FileUploadHandler(receiver);
-//                 handler.setMaxFileSize( sizeLimit );
-//                 handler.setUploadTimeLimit( timeLimit );
-               service.submit(handler.getUploadUrl(), clientFiles);
-//             for (File file : receiver.getTargetFiles()) {
-//                     paths.add(file.toPath());
-//             }
-       }
-
-       /** Executed in UI thread */
-       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-
-       }
-
-}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java
deleted file mode 100644 (file)
index 72e17a2..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Display;
-
-/** Singleton class providing single sources infos about the UI context. */
-public class UiContext {
-       /** Can be null, thus indicating that we are not in a web context. */
-       public static HttpServletRequest getHttpRequest() {
-               return RWT.getRequest();
-       }
-
-       public static HttpServletResponse getHttpResponse() {
-               return RWT.getResponse();
-       }
-
-       public static Locale getLocale() {
-               if (Display.getCurrent() != null)
-                       return RWT.getUISession().getLocale();
-               else
-                       return Locale.getDefault();
-       }
-
-       public static void setLocale(Locale locale) {
-               if (Display.getCurrent() != null)
-                       RWT.getUISession().setLocale(locale);
-               else
-                       Locale.setDefault(locale);
-       }
-
-       /** Can always be null */
-       @SuppressWarnings("unchecked")
-       public static <T> T getData(String key) {
-               Display display = getDisplay();
-               if (display == null)
-                       return null;
-               return (T) display.getData(key);
-       }
-
-       public static void setData(String key, Object value) {
-               Display display = getDisplay();
-               if (display == null)
-                       throw new IllegalStateException("Not display available");
-               display.setData(key, value);
-       }
-
-       private static Display getDisplay() {
-               return Display.getCurrent();
-       }
-
-       private UiContext() {
-       }
-
-}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java
deleted file mode 100644 (file)
index 4ec451f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
-package org.argeo.eclipse.ui.specific;
\ No newline at end of file
diff --git a/rcp/org.argeo.cms.e4.rcp/.classpath b/rcp/org.argeo.cms.e4.rcp/.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/rcp/org.argeo.cms.e4.rcp/.gitignore b/rcp/org.argeo.cms.e4.rcp/.gitignore
deleted file mode 100644 (file)
index 710cd68..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-/bin/
-/target/
-/exec
diff --git a/rcp/org.argeo.cms.e4.rcp/.project b/rcp/org.argeo.cms.e4.rcp/.project
deleted file mode 100644 (file)
index 64d5619..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.e4.rcp</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644 (file)
index 0c68a61..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
-org.eclipse.jdt.core.compiler.compliance=1.8
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.8
diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs
deleted file mode 100644 (file)
index f29e940..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-eclipse.preferences.version=1
-pluginProject.extensions=false
-resolve.requirebundle=false
diff --git a/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi
deleted file mode 100644 (file)
index 5b250ee..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="ASCII"?>
-<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmi:id="_c4iAgCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.application">
-  <children xsi:type="basic:TrimmedWindow" xmi:id="_hSGBwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.trimmedwindow.argeocompanion" label="Argeo Companion">
-    <children xsi:type="advanced:PerspectiveStack" xmi:id="_nxzQICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspectivestack.0">
-      <children xsi:type="advanced:Perspective" xmi:id="_oI_oICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspective.cmsadmin" label="CMS Admin">
-        <children xsi:type="basic:PartSashContainer" xmi:id="_qc16ECnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partsashcontainer.0" horizontal="true">
-          <children xsi:type="basic:PartStack" xmi:id="_RE87kDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.partstack.1">
-            <children xsi:type="basic:Part" xmi:id="_V1WvgDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.part.files" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files"/>
-            <children xsi:type="basic:Part" xmi:id="_vOqDQCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.part.jcr" containerData="4000" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR"/>
-          </children>
-          <children xsi:type="basic:PartStack" xmi:id="_0eRiwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partstack.0" containerData="6000">
-            <tags>editorArea</tags>
-          </children>
-        </children>
-      </children>
-    </children>
-  </children>
-  <descriptors xmi:id="__9SDsC8JEeq0koquN4xGyg" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" allowMultiple="true" category="editorArea" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
-  <addons xmi:id="_c4iAgSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
-  <addons xmi:id="_c4iAginCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
-  <addons xmi:id="_c4iAgynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
-  <addons xmi:id="_c4iAhCnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
-  <addons xmi:id="_c4iAhSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
-  <addons xmi:id="_c4iAhinCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
-  <addons xmi:id="_c4iAhynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
-</application:Application>
diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties
deleted file mode 100644 (file)
index 0a0da75..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.argeo.init
-
-argeo.osgi.start.3.node=\
-org.argeo.cms,\
-org.argeo.cms.jcr,\
-
-applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi
-lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle
-clearPersistedState=true
-#argeo.cms.desktop.inTray=true
-
-# Remote node:
-#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node
-
-# Logging
-log.org.argeo=DEBUG
-
-argeo.node.useradmin.uris=os:///
-eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application
diff --git a/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/rcp/org.argeo.cms.e4.rcp/bnd.bnd
deleted file mode 100644 (file)
index ff79c80..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true
-
-Require-Bundle: org.eclipse.core.runtime
-
-Import-Package: !org.eclipse.core.runtime,\
-org.eclipse.swt,\
-*
diff --git a/rcp/org.argeo.cms.e4.rcp/build.properties b/rcp/org.argeo.cms.e4.rcp/build.properties
deleted file mode 100644 (file)
index 355413e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               argeo-companion.e4xmi
-source.. = src/
diff --git a/rcp/org.argeo.cms.e4.rcp/log4j.properties b/rcp/org.argeo.cms.e4.rcp/log4j.properties
deleted file mode 100644 (file)
index 13f949f..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-log4j.rootLogger=WARN, development
-
-## Levels
-log4j.logger.org.argeo=DEBUG
-log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
-log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
-
-#log4j.logger.org.springframework.security=DEBUG
-#log4j.logger.org.apache.commons.exec=DEBUG
-#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
-#log4j.logger.org.apache.jackrabbit.remote=DEBUG
-#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
-
-log4j.logger.org.apache.catalina=INFO
-log4j.logger.org.apache.coyote=INFO
-
-log4j.logger.org.apache.directory=INFO
-log4j.logger.org.apache.directory.server=ERROR
-log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
-
-## Appenders
-# console is set to be a ConsoleAppender.
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-
-# console uses PatternLayout.
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
-
-# development appender (slow!)
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n
diff --git a/rcp/org.argeo.cms.e4.rcp/plugin.xml b/rcp/org.argeo.cms.e4.rcp/plugin.xml
deleted file mode 100644 (file)
index 3e6896b..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<?eclipse version="3.4"?>
-<plugin>
-   <extension
-         id="CmsE4Application"
-         name="CMS E4 Application"
-         point="org.eclipse.core.runtime.applications">
-      <application
-            cardinality="singleton-global"
-            thread="main"
-            visible="true">
-         <run class="org.argeo.cms.e4.rcp.CmsE4Application"></run>
-      </application>
-   </extension>
-</plugin>
diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java
deleted file mode 100644 (file)
index a708af1..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-package org.argeo.cms.e4.rcp;
-
-import java.security.PrivilegedExceptionAction;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.argeo.cms.swt.SimpleSwtUxContext;
-import org.argeo.cms.swt.auth.CmsLoginShell;
-import org.eclipse.core.runtime.IConfigurationElement;
-import org.eclipse.core.runtime.IExtension;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.equinox.app.IApplication;
-import org.eclipse.equinox.app.IApplicationContext;
-import org.eclipse.swt.widgets.Display;
-
-public class CmsE4Application implements IApplication, CmsView {
-       private LoginContext loginContext;
-       private IApplication e4Application;
-       private UxContext uxContext;
-       private String uid;
-
-       @Override
-       public Object start(IApplicationContext context) throws Exception {
-               // TODO wait for CMS to be ready
-               Thread.sleep(5000);
-
-               uid = UUID.randomUUID().toString();
-               Subject subject = new Subject();
-               Display display = createDisplay();
-               CmsLoginShell loginShell = new CmsLoginShell(this, null);
-               // TODO customize CmsLoginShell to be smaller and centered
-               loginShell.setSubject(subject);
-               try {
-                       // try pre-auth
-                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell);
-                       loginContext.login();
-               } catch (LoginException e) {
-                       e.printStackTrace();
-                       loginShell.createUi();
-                       loginShell.open();
-
-                       while (!loginShell.getShell().isDisposed()) {
-                               if (!display.readAndDispatch())
-                                       display.sleep();
-                       }
-               }
-               if (CurrentUser.getUsername(getSubject()) == null)
-                       throw new CmsException("Cannot log in");
-
-               // try {
-               // CallbackHandler callbackHandler = new DefaultLoginDialog(
-               // display.getActiveShell());
-               // loginContext = new LoginContext(
-               // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject,
-               // callbackHandler);
-               // } catch (LoginException e1) {
-               // throw new CmsException("Cannot initialize login context", e1);
-               // }
-               //
-               // // login
-               // try {
-               // loginContext.login();
-               // subject = loginContext.getSubject();
-               // } catch (LoginException e) {
-               // e.printStackTrace();
-               // display.dispose();
-               // try {
-               // Thread.sleep(2000);
-               // } catch (InterruptedException e1) {
-               // // silent
-               // }
-               // return null;
-               // }
-
-               uxContext = new SimpleSwtUxContext();
-               // UiContext.setData(CmsView.KEY, this);
-               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
-               e4Application = getApplication(null);
-               Object res = Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
-
-                       @Override
-                       public Object run() throws Exception {
-                               return e4Application.start(context);
-                       }
-
-               });
-               return res;
-       }
-
-       @Override
-       public void stop() {
-               if (e4Application != null)
-                       e4Application.stop();
-       }
-
-       static IApplication getApplication(String[] args) {
-               IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME,
-                               Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application");
-               try {
-                       IConfigurationElement[] elements = extension.getConfigurationElements();
-                       if (elements.length > 0) {
-                               IConfigurationElement[] runs = elements[0].getChildren("run");
-                               if (runs.length > 0) {
-                                       Object runnable;
-                                       runnable = runs[0].createExecutableExtension("class");
-                                       if (runnable instanceof IApplication)
-                                               return (IApplication) runnable;
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot find e4 application", e);
-               }
-               throw new IllegalStateException("Cannot find e4 application");
-       }
-
-       public static Display createDisplay() {
-               Display.setAppName("Argeo CMS RCP");
-
-               // create the display
-               Display newDisplay = Display.getCurrent();
-               if (newDisplay == null) {
-                       newDisplay = new Display();
-               }
-               // Set the priority higher than normal so as to be higher
-               // than the JobManager.
-               Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1));
-               return newDisplay;
-       }
-
-       //
-       // CMS VIEW
-       //
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               // TODO Auto-generated method stub
-
-       }
-
-       @Override
-       public void authChange(LoginContext loginContext) {
-               if (loginContext == null)
-                       throw new CmsException("Login context cannot be null");
-               // logout previous login context
-               // if (this.loginContext != null)
-               // try {
-               // this.loginContext.logout();
-               // } catch (LoginException e1) {
-               // System.err.println("Could not log out: " + e1);
-               // }
-               this.loginContext = loginContext;
-       }
-
-       @Override
-       public void logout() {
-               if (loginContext == null)
-                       throw new CmsException("Login context should not bet null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       throw new CmsException("Cannot log out", e);
-               }
-       }
-
-       @Override
-       public void exception(Throwable e) {
-               // TODO Auto-generated method stub
-
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-}
diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java
deleted file mode 100644 (file)
index 1d38fe7..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.cms.e4.rcp;
-
-import org.eclipse.e4.core.contexts.IEclipseContext;
-import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
-import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
-import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions;
-import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals;
-
-@SuppressWarnings("restriction")
-public class CmsRcpLifeCycle {
-
-       @PostContextCreate
-       void postContextCreate(IEclipseContext workbenchContext) {
-       }
-
-       @PreSave
-       void preSave(IEclipseContext workbenchContext) {
-       }
-
-       @ProcessAdditions
-       void processAdditions(IEclipseContext workbenchContext) {
-       }
-
-       @ProcessRemovals
-       void processRemovals(IEclipseContext workbenchContext) {
-       }
-}
diff --git a/rcp/org.argeo.cms.ui.rcp/.classpath b/rcp/org.argeo.cms.ui.rcp/.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/rcp/org.argeo.cms.ui.rcp/.gitignore b/rcp/org.argeo.cms.ui.rcp/.gitignore
deleted file mode 100644 (file)
index 09e3bc9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/bin/
-/target/
diff --git a/rcp/org.argeo.cms.ui.rcp/.project b/rcp/org.argeo.cms.ui.rcp/.project
deleted file mode 100644 (file)
index c9c2a44..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ui.rcp</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml
deleted file mode 100644 (file)
index 6da9ae8..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="CMS RCP App">
-   <implementation class="org.argeo.cms.ui.rcp.CmsRcpApp"/>
-   <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic"/>
-   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
-</scr:component>
diff --git a/rcp/org.argeo.cms.ui.rcp/bnd.bnd b/rcp/org.argeo.cms.ui.rcp/bnd.bnd
deleted file mode 100644 (file)
index 72b00d2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-
-Service-Component: OSGI-INF/cmsRcpApp.xml
-
-Import-Package:\
-org.argeo.cms.auth,\
-org.eclipse.swt,\
-org.eclipse.swt.graphics,\
-org.w3c.css.sac,\
-*
diff --git a/rcp/org.argeo.cms.ui.rcp/build.properties b/rcp/org.argeo.cms.ui.rcp/build.properties
deleted file mode 100644 (file)
index 6210e84..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/
-source.. = src/
diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java
deleted file mode 100644 (file)
index 8614d70..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-package org.argeo.cms.ui.rcp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.argeo.api.cms.CmsApp;
-import org.argeo.api.cms.CmsAuth;
-import org.argeo.api.cms.CmsImageManager;
-import org.argeo.api.cms.CmsSession;
-import org.argeo.api.cms.CmsTheme;
-import org.argeo.api.cms.CmsUi;
-import org.argeo.api.cms.CmsView;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.UxContext;
-import org.argeo.cms.osgi.CmsOsgiUtils;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.e4.ui.css.core.engine.CSSEngine;
-import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
-import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-
-/** Runs a {@link CmsApp} as an SWT desktop application. */
-@SuppressWarnings("restriction")
-public class CmsRcpApp implements CmsView {
-       private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class);
-
-       private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext();
-
-       private Display display;
-       private Shell shell;
-       private CmsApp cmsApp;
-       private CmsUiThread uiThread;
-
-       // CMS View
-       private String uid;
-       private LoginContext loginContext;
-
-       private EventAdmin eventAdmin;
-
-       private CSSEngine cssEngine;
-
-       private CmsUi ui;
-       // TODO make it configurable
-       private String uiName = "desktop";
-
-       public CmsRcpApp() {
-               uid = UUID.randomUUID().toString();
-       }
-
-       public void init(Map<String, String> properties) {
-               try {
-                       Thread.sleep(5000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-               uiThread = new CmsUiThread();
-               uiThread.start();
-
-       }
-
-       public void destroy(Map<String, String> properties) {
-               if (!shell.isDisposed())
-                       shell.dispose();
-               try {
-                       uiThread.join();
-               } catch (InterruptedException e) {
-                       // silent
-               } finally {
-                       uiThread = null;
-               }
-       }
-
-       class CmsUiThread extends Thread {
-
-               public CmsUiThread() {
-                       super("CMS UI");
-               }
-
-               @Override
-               public void run() {
-                       display = new Display();
-                       shell = new Shell(display);
-                       shell.setText("Argeo CMS");
-                       Composite parent = shell;
-                       parent.setLayout(new GridLayout());
-                       CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
-
-//                     Subject subject = new Subject();
-//                     CmsLoginShell loginShell = new CmsLoginShell(CmsRcpApp.this);
-//                     loginShell.setSubject(subject);
-                       try {
-                               // try pre-auth
-//                             loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject, loginShell);
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER);
-                               loginContext.login();
-                       } catch (LoginException e) {
-                               throw new IllegalStateException("Could not log in.", e);
-//                             loginShell.createUi();
-//                             loginShell.open();
-//
-//                             while (!loginShell.getShell().isDisposed()) {
-//                                     if (!display.readAndDispatch())
-//                                             display.sleep();
-//                             }
-                       }
-                       if (log.isDebugEnabled())
-                               log.debug("Logged in to desktop: " + loginContext.getSubject());
-
-                       Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
-
-                               // TODO factorise with web app
-                               parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
-                               ui = cmsApp.initUi(parent);
-                               if (ui instanceof Composite)
-                                       ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
-                               // ui.setLayoutData(CmsUiUtils.fillAll());
-                               // we need ui to be set before refresh so that CmsView can store UI context data
-                               // in it.
-                               cmsApp.refreshUi(ui, null);
-
-                               // Styling
-                               CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
-                               if (theme != null) {
-                                       cssEngine = new CSSSWTEngineImpl(display);
-                                       for (String path : theme.getSwtCssPaths()) {
-                                               try (InputStream in = theme.loadPath(path)) {
-                                                       cssEngine.parseStyleSheet(in);
-                                               } catch (IOException e) {
-                                                       throw new IllegalStateException("Cannot load stylesheet " + path, e);
-                                               }
-                                       }
-                                       cssEngine.setErrorHandler(new CSSErrorHandler() {
-                                               public void error(Exception e) {
-                                                       log.error("SWT styling error: ", e);
-                                               }
-                                       });
-                                       applyStyles(shell);
-                               }
-                               shell.layout(true, true);
-
-                               shell.open();
-                               while (!shell.isDisposed()) {
-                                       if (!display.readAndDispatch())
-                                               display.sleep();
-                               }
-                               display.dispose();
-                               return null;
-                       });
-               }
-
-       }
-
-       /*
-        * CMS VIEW
-        */
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void authChange(LoginContext loginContext) {
-       }
-
-       @Override
-       public void logout() {
-               if (loginContext != null)
-                       try {
-                               loginContext.logout();
-                       } catch (LoginException e) {
-                               log.error("Cannot log out", e);
-                       }
-       }
-
-       @Override
-       public void exception(Throwable e) {
-               log.error("Unexpected exception in CMS RCP", e);
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public CmsSession getCmsSession() {
-               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, getSubject());
-               return cmsSession;
-       }
-
-       @Override
-       public Object getData(String key) {
-               if (ui != null) {
-                       return ui.getData(key);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       @Override
-       public void setData(String key, Object value) {
-               if (ui != null) {
-                       ui.setData(key, value);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return false;
-       }
-
-       @Override
-       public void applyStyles(Object node) {
-               if (cssEngine != null)
-                       cssEngine.applyStyles(node, true);
-       }
-
-       @Override
-       public void sendEvent(String topic, Map<String, Object> properties) {
-               if (properties == null)
-                       properties = new HashMap<>();
-               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
-                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
-                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
-               properties.put(CMS_VIEW_UID_PROPERTY, uid);
-               eventAdmin.sendEvent(new Event(topic, properties));
-       }
-
-       public <T> T doAs(PrivilegedAction<T> action) {
-               return Subject.doAs(getSubject(), action);
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       /*
-        * DEPENDENCY INJECTION
-        */
-       public void setCmsApp(CmsApp cmsApp) {
-               this.cmsApp = cmsApp;
-       }
-
-       public void setEventAdmin(EventAdmin eventAdmin) {
-               this.eventAdmin = eventAdmin;
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/.classpath b/rcp/org.argeo.swt.minidesktop/.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/rcp/org.argeo.swt.minidesktop/.gitignore b/rcp/org.argeo.swt.minidesktop/.gitignore
deleted file mode 100644 (file)
index 97adb72..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-/bin/
-/target/
-*.log
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.minidesktop/.project b/rcp/org.argeo.swt.minidesktop/.project
deleted file mode 100644 (file)
index b6c2c1a..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.swt.minidesktop</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore b/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rcp/org.argeo.swt.minidesktop/bnd.bnd b/rcp/org.argeo.swt.minidesktop/bnd.bnd
deleted file mode 100644 (file)
index f3c13be..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-Import-Package: org.eclipse.swt,\
-*
diff --git a/rcp/org.argeo.swt.minidesktop/build.properties b/rcp/org.argeo.swt.minidesktop/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/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java
deleted file mode 100644 (file)
index 406382b..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.browser.Browser;
-import org.eclipse.swt.browser.LocationAdapter;
-import org.eclipse.swt.browser.LocationEvent;
-import org.eclipse.swt.events.KeyAdapter;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-/** A very minimalistic web browser based on {@link Browser}. */
-public class MiniBrowser {
-       private static Point defaultShellSize = new Point(800, 480);
-
-       private Browser browser;
-       private Text addressT;
-
-       private final boolean fullscreen;
-       private final boolean appMode;
-
-       public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) {
-               this.fullscreen = fullscreen;
-               this.appMode = appMode;
-               createUi(composite);
-               setUrl(url);
-       }
-
-       public Control createUi(Composite parent) {
-               parent.setLayout(noSpaceGridLayout(new GridLayout()));
-               if (!isAppMode()) {
-                       Control toolBar = createToolBar(parent);
-                       toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               }
-               Control body = createBody(parent);
-               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               return body;
-       }
-
-       protected Control createToolBar(Composite parent) {
-               Composite toolBar = new Composite(parent, SWT.NONE);
-               toolBar.setLayout(new FillLayout());
-               addressT = new Text(toolBar, SWT.SINGLE);
-               addressT.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                               setUrl(addressT.getText().trim());
-                       }
-               });
-               return toolBar;
-       }
-
-       protected Control createBody(Composite parent) {
-               browser = new Browser(parent, SWT.NONE);
-               if (isFullScreen())
-                       browser.addKeyListener(new KeyAdapter() {
-                               @Override
-                               public void keyPressed(KeyEvent e) {
-                                       if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W
-                                               browser.getShell().dispose();
-                                       }
-                               }
-                       });
-               browser.addLocationListener(new LocationAdapter() {
-                       @Override
-                       public void changed(LocationEvent event) {
-                               System.out.println(event);
-                               if (addressT != null)
-                                       addressT.setText(event.location);
-                       }
-
-               });
-               browser.addTitleListener(e -> titleChanged(e.title));
-               browser.addOpenWindowListener((e) -> {
-                       e.browser = openNewBrowserWindow();
-               });
-               return browser;
-       }
-
-       protected Browser openNewBrowserWindow() {
-
-               if (isFullScreen()) {
-                       // TODO manage multiple tabs?
-                       return browser;
-               } else {
-                       Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM);
-                       MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode());
-                       newShell.setSize(defaultShellSize);
-                       newShell.open();
-                       return newMiniBrowser.browser;
-               }
-       }
-
-       protected boolean isFullScreen() {
-               return fullscreen;
-       }
-
-       void setUrl(String url) {
-               if (browser != null && url != null && !url.equals(browser.getUrl()))
-                       browser.setUrl(url.toString());
-       }
-
-       /** Called when URL changed; to be overridden, does nothing by default. */
-       protected void urlChanged(String url) {
-       }
-
-       /** Called when title changed; to be overridden, does nothing by default. */
-       protected void titleChanged(String title) {
-       }
-
-       protected Browser getBrowser() {
-               return browser;
-       }
-
-       protected boolean isAppMode() {
-               return appMode;
-       }
-
-       private static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static void main(String[] args) {
-               List<String> options = Arrays.asList(args);
-               if (options.contains("--help")) {
-                       System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]");
-                       System.out.println("A minimalistic web browser Eclipse SWT Browser integration.");
-                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
-                       System.out.println("  --app        : open without an address bar and a toolbar");
-                       System.out.println("  --help       : print this help and exit");
-                       System.exit(1);
-               }
-               boolean fullscreen = options.contains("--fullscreen");
-               boolean appMode = options.contains("--app");
-               String url = "https://start.duckduckgo.com/";
-               if (options.size() > 0) {
-                       String last = options.get(options.size() - 1);
-                       if (!last.startsWith("--"))
-                               url = last.trim();
-               }
-
-               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
-               Shell shell;
-               if (fullscreen) {
-                       shell = new Shell(display, SWT.NO_TRIM);
-                       shell.setFullScreen(true);
-                       Rectangle bounds = display.getBounds();
-                       shell.setSize(bounds.width, bounds.height);
-               } else {
-                       shell = new Shell(display, SWT.SHELL_TRIM);
-                       shell.setSize(defaultShellSize);
-               }
-
-               new MiniBrowser(shell, url, fullscreen, appMode) {
-
-                       @Override
-                       protected void titleChanged(String title) {
-                               shell.setText(title);
-                       }
-               };
-               shell.open();
-
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java
deleted file mode 100644 (file)
index db5e6f2..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.swt.SWTException;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Display;
-
-/** Icons. */
-public class MiniDesktopImages {
-
-       public final Image homeIcon;
-       public final Image exitIcon;
-       
-       public final Image terminalIcon;
-       public final Image browserIcon;
-       public final Image explorerIcon;
-       public final Image textEditorIcon;
-
-       public final Image folderIcon;
-       public final Image fileIcon;
-
-       public MiniDesktopImages(Display display) {
-               homeIcon = loadImage(display, "nav_home@2x.png");
-               exitIcon = loadImage(display, "delete@2x.png");
-
-               terminalIcon = loadImage(display, "console_view@2x.png");
-               browserIcon = loadImage(display, "external_browser@2x.png");
-               explorerIcon = loadImage(display, "fldr_obj@2x.png");
-               textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png");
-
-               folderIcon = loadImage(display, "fldr_obj@2x.png");
-               fileIcon = loadImage(display, "file_obj@2x.png");
-       }
-
-       static Image loadImage(Display display, String path) {
-               InputStream stream = MiniDesktopImages.class.getResourceAsStream(path);
-               if (stream == null)
-                       throw new IllegalArgumentException("Image " + path + " not found");
-               Image image = null;
-               try {
-                       image = new Image(display, stream);
-               } catch (SWTException ex) {
-               } finally {
-                       try {
-                               stream.close();
-                       } catch (IOException ex) {
-                       }
-               }
-               return image;
-       }
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java
deleted file mode 100644 (file)
index e0f483d..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.lang.management.ManagementFactory;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.CTabFolder;
-import org.eclipse.swt.custom.CTabItem;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.program.Program;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.ToolBar;
-import org.eclipse.swt.widgets.ToolItem;
-
-/** A very minimalistic desktop manager based on Java and Eclipse SWT. */
-public class MiniDesktopManager {
-       private Display display;
-
-       private Shell rootShell;
-       private Shell toolBarShell;
-       private CTabFolder tabFolder;
-       private int maxTabTitleLength = 16;
-
-       private final boolean fullscreen;
-       private final boolean stacking;
-
-       private MiniDesktopImages images;
-
-       public MiniDesktopManager(boolean fullscreen, boolean stacking) {
-               this.fullscreen = fullscreen;
-               this.stacking = stacking;
-       }
-
-       public void init() {
-               Display.setAppName("Mini SWT Desktop");
-               display = Display.getCurrent();
-               if (display != null)
-                       throw new IllegalStateException("Already a display " + display);
-               display = new Display();
-
-               if (display.getTouchEnabled()) {
-                       System.out.println("Touch enabled.");
-               }
-
-               images = new MiniDesktopImages(display);
-
-               int toolBarSize = 48;
-
-               if (isFullscreen()) {
-                       rootShell = new Shell(display, SWT.NO_TRIM);
-                       rootShell.setFullScreen(true);
-                       Rectangle bounds = display.getBounds();
-                       rootShell.setLocation(0, 0);
-                       rootShell.setSize(bounds.width, bounds.height);
-               } else {
-                       rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
-                       Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
-                       rootShell.setSize(shellArea.width, shellArea.height);
-                       rootShell.setText(Display.getAppName());
-                       rootShell.setImage(images.terminalIcon);
-               }
-
-               rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
-               Composite toolBarArea = new Composite(rootShell, SWT.NONE);
-               toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
-
-               ToolBar toolBar;
-               if (isFullscreen()) {
-                       toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
-                       toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
-                       createDock(toolBar);
-                       toolBarShell.pack();
-                       toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
-               } else {
-                       toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
-                       createDock(toolBar);
-                       toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
-               }
-
-               if (isStacking()) {
-                       tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
-                       tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
-                       tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-
-                       Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
-                       tabFolder.setSelectionBackground(selectionBackground);
-
-                       // background
-                       Control background = createBackground(tabFolder);
-                       CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
-                       homeTabItem.setText("Home");
-                       homeTabItem.setImage(images.homeIcon);
-                       homeTabItem.setControl(background);
-                       tabFolder.setFocus();
-               } else {
-                       createBackground(rootShell);
-               }
-
-               rootShell.open();
-               // rootShell.layout(true, true);
-
-               if (toolBarShell != null) {
-                       int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
-                       toolBarShell.setLocation(0, toolBarShellY);
-                       toolBarShell.open();
-               }
-
-               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-               System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
-       }
-
-       protected void createDock(ToolBar toolBar) {
-               // Terminal
-               addToolItem(toolBar, images.terminalIcon, "Terminal", () -> {
-                       String url = System.getProperty("user.home");
-                       AppContext appContext = createAppParent(images.terminalIcon);
-                       new MiniTerminal(appContext.getAppParent(), url) {
-
-                               @Override
-                               protected void exitCalled() {
-                                       if (appContext.shell != null)
-                                               appContext.shell.dispose();
-                                       if (appContext.tabItem != null)
-                                               appContext.tabItem.dispose();
-                               }
-                       };
-                       String title;
-                       try {
-                               title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
-                       } catch (UnknownHostException e) {
-                               title = System.getProperty("user.name") + "@localhost";
-                       }
-                       if (appContext.shell != null)
-                               appContext.shell.setText(title);
-                       if (appContext.tabItem != null) {
-                               appContext.tabItem.setText(tabTitle(title));
-                               appContext.tabItem.setToolTipText(title);
-                       }
-                       openApp(appContext);
-               });
-
-               // Web browser
-               addToolItem(toolBar, images.browserIcon, "Browser", () -> {
-                       String url = "https://start.duckduckgo.com/";
-                       AppContext appContext = createAppParent(images.browserIcon);
-                       new MiniBrowser(appContext.getAppParent(), url, false, false) {
-                               @Override
-                               protected void titleChanged(String title) {
-                                       if (appContext.shell != null)
-                                               appContext.shell.setText(title);
-                                       if (appContext.tabItem != null) {
-                                               appContext.tabItem.setText(tabTitle(title));
-                                               appContext.tabItem.setToolTipText(title);
-                                       }
-                               }
-                       };
-                       openApp(appContext);
-               });
-
-               // File explorer
-               addToolItem(toolBar, images.explorerIcon, "Explorer", () -> {
-                       String url = System.getProperty("user.home");
-                       AppContext appContext = createAppParent(images.explorerIcon);
-                       new MiniExplorer(appContext.getAppParent(), url) {
-
-                               @Override
-                               protected void pathChanged(Path path) {
-                                       if (appContext.shell != null)
-                                               appContext.shell.setText(path.toString());
-                                       if (appContext.tabItem != null) {
-                                               appContext.tabItem.setText(path.getFileName().toString());
-                                               appContext.tabItem.setToolTipText(path.toString());
-                                       }
-                               }
-                       };
-                       openApp(appContext);
-               });
-
-               // Separator
-               new ToolItem(toolBar, SWT.SEPARATOR);
-
-               // Exit
-               addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose());
-
-               toolBar.pack();
-       }
-
-       protected String tabTitle(String title) {
-               return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title;
-       }
-
-       protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) {
-               ToolItem searchI = new ToolItem(toolBar, SWT.PUSH);
-               searchI.setImage(icon);
-               searchI.setToolTipText(name);
-               searchI.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               action.run();
-                       }
-
-               });
-       }
-
-       protected AppContext createAppParent(Image icon) {
-               if (isStacking()) {
-                       Composite appParent = new Composite(tabFolder, SWT.CLOSE);
-                       appParent.setLayout(noSpaceGridLayout(new GridLayout()));
-                       CTabItem item = new CTabItem(tabFolder, SWT.CLOSE);
-                       item.setImage(icon);
-                       item.setControl(appParent);
-                       return new AppContext(item);
-               } else {
-                       Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM)
-                                       : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM);
-                       shell.setImage(icon);
-                       return new AppContext(shell);
-               }
-       }
-
-       protected void openApp(AppContext appContext) {
-               if (appContext.shell != null) {
-                       Shell shell = (Shell) appContext.shell;
-                       shell.open();
-                       shell.setSize(new Point(800, 480));
-               }
-               if (appContext.tabItem != null) {
-                       tabFolder.setFocus();
-                       tabFolder.setSelection(appContext.tabItem);
-               }
-       }
-
-       protected Control createBackground(Composite parent) {
-               Composite backgroundArea = new Composite(parent, SWT.NONE);
-               backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               initBackground(backgroundArea);
-               return backgroundArea;
-       }
-
-       protected void initBackground(Composite backgroundArea) {
-               MiniHomePart homePart = new MiniHomePart() {
-
-                       @Override
-                       protected void fillAppsToolBar(ToolBar toolBar) {
-                               createDock(toolBar);
-                       }
-               };
-               homePart.createUiPart(backgroundArea, null);
-       }
-
-       public void run() {
-               while (!rootShell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-       public void dispose() {
-               if (!rootShell.isDisposed())
-                       rootShell.dispose();
-       }
-
-       protected boolean isFullscreen() {
-               return fullscreen;
-       }
-
-       protected boolean isStacking() {
-               return stacking;
-       }
-
-       protected Image getIconForExt(String ext) {
-               Program program = Program.findProgram(ext);
-               if (program == null)
-                       return display.getSystemImage(SWT.ICON_INFORMATION);
-
-               ImageData iconData = program.getImageData();
-               if (iconData == null) {
-                       return display.getSystemImage(SWT.ICON_INFORMATION);
-               } else {
-                       return new Image(display, iconData);
-               }
-
-       }
-
-       private static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static void main(String[] args) {
-               List<String> options = Arrays.asList(args);
-               if (options.contains("--help")) {
-                       System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]");
-                       System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT.");
-                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
-                       System.out.println("  --stacking   : open apps as tabs (default is to create new windows)");
-                       System.out.println("  --help       : print this help and exit");
-                       System.exit(1);
-               }
-               boolean fullscreen = options.contains("--fullscreen");
-               boolean stacking = options.contains("--stacking");
-
-               MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
-               desktopManager.init();
-               desktopManager.run();
-               desktopManager.dispose();
-               System.exit(0);
-       }
-
-       class AppContext {
-               private Shell shell;
-               private CTabItem tabItem;
-
-               public AppContext(Shell shell) {
-                       this.shell = shell;
-               }
-
-               public AppContext(CTabItem tabItem) {
-                       this.tabItem = tabItem;
-               }
-
-               Composite getAppParent() {
-                       if (shell != null)
-                               return shell;
-                       if (tabItem != null)
-                               return (Composite) tabItem.getControl();
-                       throw new IllegalStateException();
-               }
-       }
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java
deleted file mode 100644 (file)
index 1395c02..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.program.Program;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Text;
-
-public class MiniExplorer {
-       private Path path;
-       private Text addressT;
-       private Table browser;
-
-       private boolean showHidden = false;
-
-       public MiniExplorer(Composite parent, String url) {
-               this(parent);
-               setUrl(url);
-       }
-
-       public MiniExplorer(Composite parent) {
-               parent.setLayout(noSpaceGridLayout(new GridLayout()));
-
-               Composite toolBar = new Composite(parent, SWT.NONE);
-               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               toolBar.setLayout(new FillLayout());
-               addressT = new Text(toolBar, SWT.SINGLE);
-               addressT.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                               setUrl(addressT.getText().trim());
-                       }
-               });
-               browser = createTable(parent, this.path);
-
-       }
-
-       public void setPath(Path url) {
-               this.path = url;
-               if (addressT != null)
-                       addressT.setText(url.toString());
-               if (browser != null) {
-                       Composite parent = browser.getParent();
-                       browser.dispose();
-                       browser = createTable(parent, this.path);
-                       parent.layout(true, true);
-               }
-               pathChanged(url);
-       }
-
-       protected void pathChanged(Path path) {
-
-       }
-
-       protected Table createTable(Composite parent, Path path) {
-               Table table = new Table(parent, SWT.BORDER);
-               table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               table.addMouseListener(new MouseAdapter() {
-
-                       @Override
-                       public void mouseDoubleClick(MouseEvent e) {
-                               Point pt = new Point(e.x, e.y);
-                               TableItem item = table.getItem(pt);
-                               Path path = (Path) item.getData();
-                               if (Files.isDirectory(path)) {
-                                       setPath(path);
-                               } else {
-                                       Program.launch(path.toString());
-                               }
-                       }
-               });
-
-               if (path != null) {
-                       if (path.getParent() != null) {
-                               TableItem parentTI = new TableItem(table, SWT.NONE);
-                               parentTI.setText("..");
-                               parentTI.setData(path.getParent());
-                       }
-
-                       try {
-                               // directories
-                               DirectoryStream<Path> ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p));
-                               ds.forEach(p -> {
-                                       TableItem ti = new TableItem(table, SWT.NONE);
-                                       ti.setText(p.getFileName().toString() + "/");
-                                       ti.setData(p);
-                               });
-                               // files
-                               ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p));
-                               ds.forEach(p -> {
-                                       TableItem ti = new TableItem(table, SWT.NONE);
-                                       ti.setText(p.getFileName().toString());
-                                       ti.setData(p);
-                               });
-                       } catch (IOException e1) {
-                               // TODO Auto-generated catch block
-                               e1.printStackTrace();
-                       }
-               }
-               return table;
-       }
-
-       protected boolean isShown(Path path) {
-               if (showHidden)
-                       return true;
-               try {
-                       return !Files.isHidden(path);
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot check " + path, e);
-               }
-       }
-
-       public void setUrl(String url) {
-               setPath(Paths.get(url));
-       }
-
-       private static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static void main(String[] args) {
-               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
-               Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
-               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
-               new MiniExplorer(shell, url) {
-
-                       @Override
-                       protected void pathChanged(Path path) {
-                               shell.setText(path.toString());
-                       }
-
-               };
-
-               shell.open();
-               shell.setSize(new Point(800, 480));
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java
deleted file mode 100644 (file)
index 877f643..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Enumeration;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Group;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.ProgressBar;
-import org.eclipse.swt.widgets.ToolBar;
-
-/** A start page displaying network information and resources. */
-public class MiniHomePart {
-
-       public Control createUiPart(Composite parent, Object context) {
-               parent.setLayout(new GridLayout(2, false));
-               Display display = parent.getDisplay();
-
-               // Apps
-               Group appsGroup = new Group(parent, SWT.NONE);
-               appsGroup.setText("Apps");
-               appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1));
-               ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT);
-               fillAppsToolBar(appsToolBar);
-
-               // Host
-               Group hostGroup = new Group(parent, SWT.NONE);
-               hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
-               hostGroup.setText("Host");
-               hostGroup.setLayout(new GridLayout(2, false));
-               label(hostGroup, "Hostname: ");
-               try {
-                       InetAddress defaultAddr = InetAddress.getLocalHost();
-                       String hostname = defaultAddr.getHostName();
-                       label(hostGroup, hostname);
-                       label(hostGroup, "Address: ");
-                       label(hostGroup, defaultAddr.getHostAddress());
-               } catch (UnknownHostException e) {
-                       label(hostGroup, e.getMessage());
-               }
-
-               Enumeration<NetworkInterface> netInterfaces = null;
-               try {
-                       netInterfaces = NetworkInterface.getNetworkInterfaces();
-               } catch (SocketException e) {
-                       label(hostGroup, "Interfaces: ");
-                       label(hostGroup, e.getMessage());
-               }
-               if (netInterfaces != null)
-                       while (netInterfaces.hasMoreElements()) {
-                               NetworkInterface netInterface = netInterfaces.nextElement();
-                               byte[] hardwareAddress = null;
-                               try {
-                                       hardwareAddress = netInterface.getHardwareAddress();
-                                       if (hardwareAddress != null) {
-                                               label(hostGroup, convertHardwareAddress(hardwareAddress));
-                                               label(hostGroup, netInterface.getName());
-                                               for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
-                                                       label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress()));
-                                                       label(hostGroup, Short.toString(addr.getNetworkPrefixLength()));
-                                               }
-                                       }
-                               } catch (SocketException e) {
-                                       label(hostGroup, e.getMessage());
-                               }
-                       }
-
-               // Resources
-               Group resGroup = new Group(parent, SWT.NONE);
-               resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
-               resGroup.setText("Resources");
-               resGroup.setLayout(new GridLayout(3, false));
-
-               Runtime runtime = Runtime.getRuntime();
-
-               String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB";
-               label(resGroup, "Max Java memory: ");
-               label(resGroup, maxMemoryStr);
-               label(resGroup, "Java version: " + Runtime.version().toString());
-
-               label(resGroup, "Usable Java memory: ");
-               Label totalMemory = label(resGroup, maxMemoryStr);
-               ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH);
-               totalOnMax.setMaximum(100);
-               label(resGroup, "Used Java memory: ");
-               Label usedMemory = label(resGroup, maxMemoryStr);
-               ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH);
-               totalOnMax.setMaximum(100);
-               new Thread() {
-                       @Override
-                       public void run() {
-                               while (!totalOnMax.isDisposed()) {
-                                       display.asyncExec(() -> {
-                                               if (totalOnMax.isDisposed())
-                                                       return;
-                                               totalOnMax.setSelection(javaTotalOnMaxPerct(runtime));
-                                               usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime));
-                                               totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB");
-                                               usedMemory.setText(
-                                                               Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB");
-                                       });
-                                       try {
-                                               Thread.sleep(1000);
-                                       } catch (InterruptedException e) {
-                                               return;
-                                       }
-                               }
-                       }
-               }.start();
-               return parent;
-       }
-
-       protected void fillAppsToolBar(ToolBar toolBar) {
-
-       }
-
-       protected int javaUsedOnTotalPerct(Runtime runtime) {
-               return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory());
-       }
-
-       protected int javaTotalOnMaxPerct(Runtime runtime) {
-               return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory());
-       }
-
-       protected Label label(Composite parent, String text) {
-               Label label = new Label(parent, SWT.WRAP);
-               label.setText(text);
-               return label;
-       }
-
-       protected String cleanHostAddress(String hostAddress) {
-               // remove % from Ipv6 addresses
-               int index = hostAddress.indexOf('%');
-               if (index > 0)
-                       return hostAddress.substring(0, index);
-               else
-                       return hostAddress;
-       }
-
-       protected String convertHardwareAddress(byte[] hardwareAddress) {
-               if (hardwareAddress == null)
-                       return "";
-               // from https://stackoverflow.com/a/2797498/7878010
-               StringBuilder sb = new StringBuilder(18);
-               for (byte b : hardwareAddress) {
-                       if (sb.length() > 0)
-                               sb.append(':');
-                       sb.append(String.format("%02x", b).toUpperCase());
-               }
-               return sb.toString();
-       }
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java
deleted file mode 100644 (file)
index 86ff53f..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.ImageLoader;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class MiniImageViewer implements PaintListener {
-       private URL url;
-       private Canvas area;
-
-       private Image image;
-
-       public MiniImageViewer(Composite parent, int style) {
-               parent.setLayout(new GridLayout());
-
-               Composite toolBar = new Composite(parent, SWT.NONE);
-               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               toolBar.setLayout(new RowLayout());
-               Button load = new Button(toolBar, SWT.FLAT);
-               load.setText("\u2191");// up arrow
-               load.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               FileDialog fileDialog = new FileDialog(area.getShell());
-                               String path = fileDialog.open();
-                               if (path != null) {
-                                       setUrl(path);
-                               }
-                       }
-
-               });
-
-               area = new Canvas(parent, SWT.NONE);
-               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               area.addPaintListener(this);
-       }
-
-       protected void load(URL url) {
-               try {
-                       ImageLoader imageLoader = new ImageLoader();
-                       ImageData[] data = imageLoader.load(url.openStream());
-                       image = new Image(area.getDisplay(), data[0]);
-               } catch (IOException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               }
-       }
-
-       @Override
-       public void paintControl(PaintEvent e) {
-               e.gc.drawImage(image, 0, 0);
-
-       }
-
-       protected Path url2path(URL url) {
-               try {
-                       Path path = Paths.get(url.toURI());
-                       return path;
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
-               }
-       }
-
-       public void setUrl(URL url) {
-               this.url = url;
-               if (area != null)
-                       load(this.url);
-       }
-
-       public void setUrl(String url) {
-               try {
-                       setUrl(new URL(url));
-               } catch (MalformedURLException e) {
-                       // try with http
-                       try {
-                               setUrl(new URL("file://" + url));
-                               return;
-                       } catch (MalformedURLException e1) {
-                               // nevermind...
-                       }
-                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
-               Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
-               MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE);
-               String url = args.length > 0 ? args[0] : "";
-               if (!url.trim().equals("")) {
-                       miniBrowser.setUrl(url);
-                       shell.setText(url);
-               } else {
-                       shell.setText("*");
-               }
-
-               shell.open();
-               shell.setSize(new Point(800, 480));
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java
deleted file mode 100644 (file)
index 196ad0c..0000000
+++ /dev/null
@@ -1,342 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.StringTokenizer;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.KeyEvent;
-import org.eclipse.swt.events.KeyListener;
-import org.eclipse.swt.events.PaintEvent;
-import org.eclipse.swt.events.PaintListener;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.GC;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Caret;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public class MiniTerminal implements KeyListener, PaintListener {
-
-       private Canvas area;
-       private Caret caret;
-
-       private StringBuffer buf = new StringBuffer("");
-       private StringBuffer userInput = new StringBuffer("");
-       private List<String> history = new ArrayList<>();
-
-       private Point charExtent = null;
-       private int charsPerLine = 0;
-       private String[] lines = new String[0];
-       private List<String> logicalLines = new ArrayList<>();
-
-       private Font mono;
-       private Charset charset;
-
-       private Path currentDir;
-       private Path homeDir;
-       private String host = "localhost";
-       private String username;
-
-       // Sub process
-       private Process process;
-       private boolean running = false;
-       private OutputStream stdIn = null;
-
-       private Thread readOut;
-
-       public MiniTerminal(Composite parent, String url) {
-               this(parent);
-               setPath(url);
-       }
-
-       public MiniTerminal(Composite parent) {
-               charset = StandardCharsets.UTF_8;
-
-               Display display = parent.getDisplay();
-               // Linux-specific
-               mono = new Font(display, "Monospace", 10, SWT.NONE);
-
-               parent.setLayout(new GridLayout());
-               area = new Canvas(parent, SWT.NONE);
-               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               caret = new Caret(area, SWT.NONE);
-               area.setCaret(caret);
-
-               area.addKeyListener(this);
-               area.addPaintListener(this);
-
-               username = System.getProperty("user.name");
-               try {
-                       host = InetAddress.getLocalHost().getHostName();
-                       if (host.indexOf('.') > 0)
-                               host = host.substring(0, host.indexOf('.'));
-               } catch (UnknownHostException e) {
-                       host = "localhost";
-               }
-               homeDir = Paths.get(System.getProperty("user.home"));
-               currentDir = homeDir;
-
-               buf = new StringBuffer(prompt());
-       }
-
-       @Override
-       public void keyPressed(KeyEvent e) {
-       }
-
-       @Override
-       public void keyReleased(KeyEvent e) {
-               if (e.keyLocation != 0)
-                       return;// weird characters
-               // System.out.println(e.character);
-               if (e.keyCode == 0xd) {// return
-                       markLogicalLine();
-                       if (!running)
-                               processUserInput();
-                       // buf.append(prompt());
-               } else if (e.keyCode == 0x8) {// delete
-                       if (userInput.length() == 0)
-                               return;
-                       userInput.setLength(userInput.length() - 1);
-                       if (!running && buf.length() > 0)
-                               buf.setLength(buf.length() - 1);
-               } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {// Ctrl+C
-                       if (process != null)
-                               process.destroy();
-               } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {// Ctrl+\
-                       if (process != null) {
-                               process.destroyForcibly();
-                       }
-               } else {
-                       // if (!running)
-                       buf.append(e.character);
-                       userInput.append(e.character);
-               }
-
-               if (area.isDisposed())
-                       return;
-               area.redraw();
-               // System.out.println("Append " + e);
-
-               if (running) {
-                       if (stdIn != null) {
-                               try {
-                                       stdIn.write(Character.toString(e.character).getBytes(charset));
-                               } catch (IOException e1) {
-                                       // TODO Auto-generated catch block
-                                       e1.printStackTrace();
-                               }
-                       }
-               }
-       }
-
-       protected String prompt() {
-               String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
-               String end = username.equals("root") ? "]# " : "]$ ";
-               return "[" + username + "@" + host + " " + fileName + end;
-       }
-
-       private void displayPrompt() {
-               buf.append(prompt() + userInput);
-       }
-
-       protected void markLogicalLine() {
-               String str = buf.toString().trim();
-               logicalLines.add(str);
-               buf = new StringBuffer("");
-       }
-
-       private void processUserInput() {
-               String cmd = userInput.toString();
-               userInput = new StringBuffer("");
-               processUserInput(cmd);
-               history.add(cmd);
-       }
-
-       protected void processUserInput(String input) {
-               try {
-                       StringTokenizer st = new StringTokenizer(input);
-                       List<String> args = new ArrayList<>();
-                       while (st.hasMoreTokens())
-                               args.add(st.nextToken());
-                       if (args.size() == 0) {
-                               displayPrompt();
-                               return;
-                       }
-
-                       // change directory
-                       if (args.get(0).equals("cd")) {
-                               if (args.size() == 1) {
-                                       setPath(homeDir);
-                               } else {
-                                       Path newPath = currentDir.resolve(args.get(1));
-                                       if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
-                                               println(newPath + ": No such file or directory");
-                                               return;
-                                       }
-                                       setPath(newPath);
-                               }
-                               displayPrompt();
-                               return;
-                       }
-                       // show current directory
-                       else if (args.get(0).equals("pwd")) {
-                               println(currentDir);
-                               displayPrompt();
-                               return;
-                       }
-                       // exit
-                       else if (args.get(0).equals("exit")) {
-                               println("logout");
-                               exitCalled();
-                               return;
-                       }
-
-                       ProcessBuilder pb = new ProcessBuilder(args);
-                       pb.redirectErrorStream(true);
-                       pb.directory(currentDir.toFile());
-//                     Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile());
-                       process = pb.start();
-
-                       stdIn = process.getOutputStream();
-                       readOut = new Thread("MiniTerminal read out") {
-                               @Override
-                               public void run() {
-                                       running = true;
-                                       try (BufferedReader in = new BufferedReader(
-                                                       new InputStreamReader(process.getInputStream(), charset))) {
-                                               String line = null;
-                                               while ((line = in.readLine()) != null) {
-                                                       println(line);
-                                               }
-                                       } catch (IOException e) {
-                                               println(e.getMessage());
-                                       }
-                                       stdIn = null;
-                                       displayPrompt();
-                                       running = false;
-                                       readOut = null;
-                                       process = null;
-                               }
-                       };
-                       readOut.start();
-               } catch (IOException e) {
-                       println(e.getMessage());
-                       displayPrompt();
-               }
-       }
-
-       protected int linesForLogicalLine(char[] line) {
-               return line.length / charsPerLine + 1;
-       }
-
-       protected void println(Object line) {
-               buf.append(line);
-               markLogicalLine();
-       }
-
-       protected void refreshLines(int charPerLine, int nbrOfLines) {
-               if (lines.length != nbrOfLines) {
-                       lines = new String[nbrOfLines];
-                       Arrays.fill(lines, null);
-               }
-               if (this.charsPerLine != charPerLine)
-                       this.charsPerLine = charPerLine;
-
-               int currentLine = nbrOfLines - 1;
-               // current line
-               if (buf.length() > 0) {
-                       lines[currentLine] = buf.toString();
-               } else {
-                       lines[currentLine] = "";
-               }
-               currentLine--;
-
-               logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
-                       char[] logicalLine = logicalLines.get(i).toCharArray();
-                       int linesNeeded = linesForLogicalLine(logicalLine);
-                       for (int j = linesNeeded - 1; j >= 0; j--) {
-                               int from = j * charPerLine;
-                               int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
-                               lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
-//                             System.out.println("Set line " + currentLine + " to : " + lines[currentLine]);
-                               currentLine--;
-                               if (currentLine < 0)
-                                       break logicalLines;
-                       }
-               }
-       }
-
-       @Override
-       public void paintControl(PaintEvent e) {
-               GC gc = e.gc;
-               gc.setFont(mono);
-               if (charExtent == null)
-                       charExtent = gc.textExtent("a");
-
-               Point areaSize = area.getSize();
-               int charPerLine = areaSize.x / charExtent.x;
-               int nbrOfLines = areaSize.y / charExtent.y;
-               refreshLines(charPerLine, nbrOfLines);
-
-               for (int i = 0; i < lines.length; i++) {
-                       String line = lines[i];
-                       if (line != null)
-                               gc.drawString(line, 0, i * charExtent.y);
-               }
-//             String toDraw = buf.toString();
-//             gc.drawString(toDraw, 0, 0);
-//             area.setCaret(caret);
-       }
-
-       protected void exitCalled() {
-
-       }
-
-       public void setPath(String path) {
-               this.currentDir = Paths.get(path);
-       }
-
-       public void setPath(Path path) {
-               this.currentDir = path;
-       }
-
-       public static void main(String[] args) {
-               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
-               Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
-               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
-               new MiniTerminal(shell, url) {
-
-                       @Override
-                       protected void exitCalled() {
-                               shell.dispose();
-                               System.exit(0);
-                       }
-               };
-
-               shell.open();
-               shell.setSize(new Point(800, 480));
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java
deleted file mode 100644 (file)
index 91cd19e..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.argeo.minidesktop;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.layout.RowLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-import org.eclipse.swt.widgets.Text;
-
-public class MiniTextEditor {
-       private URL url;
-       private Text text;
-
-       public MiniTextEditor(Composite parent, int style) {
-               parent.setLayout(new GridLayout());
-
-               Composite toolBar = new Composite(parent, SWT.NONE);
-               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               toolBar.setLayout(new RowLayout());
-               Button load = new Button(toolBar, SWT.FLAT);
-               load.setText("\u2191");// up arrow
-               load.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               FileDialog fileDialog = new FileDialog(text.getShell());
-                               String path = fileDialog.open();
-                               if (path != null) {
-                                       setUrl(path);
-                               }
-                       }
-
-               });
-
-               Button save = new Button(toolBar, SWT.FLAT);
-               save.setText("\u2193");// down arrow
-               // save.setText("\u1F609");// emoji
-               save.addSelectionListener(new SelectionAdapter() {
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               save(url);
-                       }
-
-               });
-
-               text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
-               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-       }
-
-       protected void load(URL url) {
-               text.setText("");
-               // TODO deal with encoding and binary data
-               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
-                       String line = null;
-                       while ((line = in.readLine()) != null) {
-                               text.append(line + "\n");
-                       }
-                       text.setEditable(true);
-               } catch (IOException e) {
-                       if (e instanceof FileNotFoundException) {
-                               Path path = url2path(url);
-                               try {
-                                       Files.createFile(path);
-                                       load(url);
-                                       return;
-                               } catch (IOException e1) {
-                                       e = e1;
-                               }
-                       }
-                       text.setText(e.getMessage());
-                       text.setEditable(false);
-                       e.printStackTrace();
-                       // throw new IllegalStateException("Cannot load " + url, e);
-               }
-       }
-
-       protected Path url2path(URL url) {
-               try {
-                       Path path = Paths.get(url.toURI());
-                       return path;
-               } catch (URISyntaxException e) {
-                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
-               }
-       }
-
-       protected void save(URL url) {
-               if (!url.getProtocol().equals("file"))
-                       throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write");
-               Path path = url2path(url);
-               try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) {
-                       out.write(text.getText());
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot save " + url + " to " + path, e);
-               }
-       }
-
-       public void setUrl(URL url) {
-               this.url = url;
-               if (text != null)
-                       load(url);
-       }
-
-       public void setUrl(String url) {
-               try {
-                       setUrl(new URL(url));
-               } catch (MalformedURLException e) {
-                       // try with http
-                       try {
-                               setUrl(new URL("file://" + url));
-                               return;
-                       } catch (MalformedURLException e1) {
-                               // nevermind...
-                       }
-                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
-               Shell shell = new Shell(display, SWT.SHELL_TRIM);
-
-               MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE);
-               String url = args.length > 0 ? args[0] : "";
-               if (!url.trim().equals("")) {
-                       miniBrowser.setUrl(url);
-                       shell.setText(url);
-               } else {
-                       shell.setText("*");
-               }
-
-               shell.open();
-               shell.setSize(new Point(800, 480));
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch())
-                               display.sleep();
-               }
-       }
-
-}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png
deleted file mode 100644 (file)
index 55c614d..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png
deleted file mode 100644 (file)
index 54ecae2..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png
deleted file mode 100644 (file)
index eb2fc72..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png
deleted file mode 100644 (file)
index 06d337a..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png
deleted file mode 100644 (file)
index 31e671c..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png
deleted file mode 100644 (file)
index adb7c2c..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png
deleted file mode 100644 (file)
index 4c9a16c..0000000
Binary files a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png and /dev/null differ
diff --git a/rcp/org.argeo.swt.specific.rcp/.classpath b/rcp/org.argeo.swt.specific.rcp/.classpath
deleted file mode 100644 (file)
index 457b115..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="con"
-               path="org.eclipse.pde.core.requiredPlugins" />
-       <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />
-       <classpathentry kind="output" path="bin" />
-</classpath>
diff --git a/rcp/org.argeo.swt.specific.rcp/.gitignore b/rcp/org.argeo.swt.specific.rcp/.gitignore
deleted file mode 100644 (file)
index 5e77890..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-/target/
-/bin/
-*.log
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/.project b/rcp/org.argeo.swt.specific.rcp/.project
deleted file mode 100644 (file)
index c79ee3f..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.swt.specific.rcp</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore b/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/rcp/org.argeo.swt.specific.rcp/bnd.bnd
deleted file mode 100644 (file)
index bb88efd..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-Import-Package: \
-!java.*,\
-org.apache.commons.io,\
-org.eclipse.core.commands,\
-!org.eclipse.core.runtime,\
-!org.eclipse.ui.plugin,\
-org.eclipse.swt,\
-javax.servlet.http;version="[3,5)",\
-javax.servlet;version="[3,5)",\
-*
-
-Export-Package: org.argeo.*,\
-org.eclipse.rap.fileupload.*;version="3.10",\
-org.eclipse.rap.rwt.*;version="3.10"
-
-# Was !org.eclipse.core.commands,\ why ?
-
-#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
-#Bundle-ActivationPolicy: lazy
-#Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/build.properties b/rcp/org.argeo.swt.specific.rcp/build.properties
deleted file mode 100644 (file)
index c6b651a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java
deleted file mode 100644 (file)
index 0d9ce48..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.eclipse.ui.rcp.internal.rwt;
-
-import org.eclipse.rap.rwt.client.Client;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.client.service.ClientService;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-
-public class RcpClient implements Client {
-
-       @Override
-       public <T extends ClientService> T getService(Class<T> type) {
-               if (type.isAssignableFrom(JavaScriptExecutor.class))
-                       return (T) javaScriptExecutor;
-               else if (type.isAssignableFrom(BrowserNavigation.class))
-                       return (T) browserNavigation;
-               else
-                       return null;
-       }
-
-       private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() {
-
-               @Override
-               public void execute(String code) {
-                       // TODO Auto-generated method stub
-
-               }
-       };
-       private BrowserNavigation browserNavigation = new BrowserNavigation() {
-
-               @Override
-               public void pushState(String state, String title) {
-                       // TODO Auto-generated method stub
-
-               }
-
-               @Override
-               public void addBrowserNavigationListener(
-                               BrowserNavigationListener listener) {
-                       // TODO Auto-generated method stub
-
-               }
-       };
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java
deleted file mode 100644 (file)
index 91109a9..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.eclipse.ui.rcp.internal.rwt;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.apache.commons.io.IOUtils;
-import org.eclipse.rap.rwt.service.ResourceManager;
-
-public class RcpResourceManager implements ResourceManager {
-       private Map<String, byte[]> register = Collections
-                       .synchronizedMap(new TreeMap<String, byte[]>());
-
-       @Override
-       public void register(String name, InputStream in) {
-               try {
-                       register.put(name, IOUtils.toByteArray(in));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot register " + name, e);
-               }
-       }
-
-       @Override
-       public boolean unregister(String name) {
-               return register.remove(name) != null;
-       }
-
-       @Override
-       public InputStream getRegisteredContent(String name) {
-               return new ByteArrayInputStream(register.get(name));
-       }
-
-       @Override
-       public String getLocation(String name) {
-               return name;
-       }
-
-       @Override
-       public boolean isRegistered(String name) {
-               return register.containsKey(name);
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
deleted file mode 100644 (file)
index 0c5d346..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class CmsFileDialog extends FileDialog {
-       public CmsFileDialog(Shell parent, int style) {
-               super(parent, style);
-       }
-
-       public CmsFileDialog(Shell parent) {
-               super(parent);
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
deleted file mode 100644 (file)
index 638859a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-
-public class CmsFileUpload extends FileUpload {
-       public CmsFileUpload(Composite parent, int style) {
-               super(parent, style);
-       }
-
-       @Override
-       public void setText(String text) {
-               super.setText(text);
-       }
-
-       @Override
-       public String getFileName() {
-               return super.getFileName();
-       }
-
-       @Override
-       public String[] getFileNames() {
-               return super.getFileNames();
-       }
-
-       @Override
-       public void addSelectionListener(SelectionListener listener) {
-               super.addSelectionListener(listener);
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java
deleted file mode 100644 (file)
index fbb4fbf..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-/** RCP specific {@link NLS} to be extended */
-public class DefaultNLS {// extends NLS {
-//     public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
-//
-//     public DefaultNLS() {
-//             this(DEFAULT_BUNDLE_LOCATION);
-//     }
-//
-//     public DefaultNLS(String bundleName) {
-//             NLS.initializeMessages(bundleName, getClass());
-//     }
-}
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java
deleted file mode 100644 (file)
index ac862d7..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-/** Constants which are specific to RWT.*/
-public interface EclipseUiConstants {
-       final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
-       final static String MARKUP_SUPPORT = null;
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
deleted file mode 100644 (file)
index d1acbcf..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities to bridge differences between RCP and RAP */
-public class EclipseUiSpecificUtils {
-       private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
-
-       public static void setStyleData(Widget widget, Object data) {
-               widget.setData(CSS_CLASS, data);
-       }
-
-       public static Object getStyleData(Widget widget) {
-               return widget.getData(CSS_CLASS);
-       }
-
-       public static void setMarkupData(Widget widget) {
-               // does nothing
-       }
-
-       public static void setMarkupValidationDisabledData(Widget widget) {
-               // does nothing
-       }
-
-       /**
-        * TootlTip support is supported for {@link ColumnViewer} in RCP
-        * 
-        * @see ColumnViewerToolTipSupport#enableFor(Viewer)
-        */
-       public static void enableToolTipSupport(Viewer viewer) {
-               if (viewer instanceof ColumnViewer)
-                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
-       }
-
-       private EclipseUiSpecificUtils() {
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
deleted file mode 100644 (file)
index 524447e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetAdapter;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.FileTransfer;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Control;
-
-public class FileDropAdapter {
-
-       public void prepareDropTarget(Control control, DropTarget dropTarget) {
-               dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
-               dropTarget.addDropListener(new DropTargetAdapter() {
-                       @Override
-                       public void dropAccept(DropTargetEvent event) {
-                               if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
-                                       event.detail = DND.DROP_NONE;
-                               }
-                       }
-
-                       @Override
-                       public void drop(DropTargetEvent event) {
-                               handleFileDrop(control, event);
-                       }
-               });
-       }
-
-       public void handleFileDrop(Control control, DropTargetEvent event) {
-               String fileList[] = null;
-               FileTransfer ft = FileTransfer.getInstance();
-               if (ft.isSupportedType(event.currentDataType)) {
-                       fileList = (String[]) event.data;
-               }
-               System.out.println(Arrays.toString(fileList));
-       }
-
-       /** Executed in UI thread */
-       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java
deleted file mode 100644 (file)
index 20163cf..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.swt.widgets.Display;
-
-/** Singleton class providing single sources infos about the UI context. */
-public class UiContext {
-
-       public static HttpServletRequest getHttpRequest() {
-               return null;
-       }
-
-       public static HttpServletResponse getHttpResponse() {
-               return null;
-       }
-
-       public static Locale getLocale() {
-               return Locale.getDefault();
-       }
-
-       public static void setLocale(Locale locale) {
-               Locale.setDefault(locale);
-       }
-
-       /** Can always be null */
-       @SuppressWarnings("unchecked")
-       public static <T> T getData(String key) {
-               Display display = getDisplay();
-               if (display == null)
-                       return null;
-               return (T) display.getData(key);
-       }
-
-       public static void setData(String key, Object value) {
-               Display display = getDisplay();
-               if (display == null)
-                       throw new IllegalStateException("Not display available");
-               display.setData(key, value);
-       }
-
-       private static Display getDisplay() {
-               return Display.getCurrent();
-       }
-
-       private UiContext() {
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java
deleted file mode 100644 (file)
index fbb36dd..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.eclipse.rap.fileupload;
-
-public interface FileDetails {
-       String getContentType();
-
-       long getContentLength();
-
-       String getFileName();
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java
deleted file mode 100644 (file)
index a745280..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.eclipse.rap.fileupload;
-
-import java.util.EventObject;
-
-public abstract class FileUploadEvent extends EventObject {
-
-       private static final long serialVersionUID = 1L;
-
-       protected FileUploadEvent(FileUploadHandler source) {
-               super(source);
-       }
-
-       public abstract FileDetails[] getFileDetails();
-
-       public abstract long getContentLength();
-
-       public abstract long getBytesRead();
-
-       public abstract Exception getException();
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java
deleted file mode 100644 (file)
index 7d89300..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011, 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-/**
- * A file upload handler is used to accept file uploads from a client. After
- * creating a file upload handler, the server will accept file uploads to the
- * URL returned by <code>getUploadUrl()</code>. Upload listeners can be attached
- * to react on progress. When the upload has finished, a FileUploadHandler has
- * to be disposed of by calling its <code>dispose()</code> method.
- *
- * @noextend This class is not intended to be subclassed by clients.
- */
-public class FileUploadHandler {
-
-       public FileUploadHandler(FileUploadReceiver fileUploadReceiver) {
-       }
-
-       public void dispose() {
-
-       }
-
-       public void addUploadListener(FileUploadListener listener) {
-
-       }
-
-       public String getUploadUrl() {
-               return null;
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java
deleted file mode 100644 (file)
index b59fd39..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011, 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-import org.eclipse.swt.widgets.Display;
-
-
-/**
- * Listener to react on progress and completion of a file upload.
- * <p>
- * <strong>Note:</strong> This listener will be called from a different thread than the UI thread.
- * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI.
- * </p>
- *
- * @see FileUploadEvent
- */
-public interface FileUploadListener {
-
-  /**
-   * Called when new information about an in-progress upload is available.
-   *
-   * @param event event object that contains information about the uploaded file
-   * @see FileUploadEvent#getBytesRead()
-   */
-  void uploadProgress( FileUploadEvent event );
-
-  /**
-   * Called when a file upload has finished successfully.
-   *
-   * @param event event object that contains information about the uploaded file
-   * @see FileUploadEvent
-   */
-  void uploadFinished( FileUploadEvent event );
-
-  /**
-   * Called when a file upload failed.
-   *
-   * @param event event object that contains information about the uploaded file
-   * @see FileUploadEvent#getErrorMessage()
-   */
-  void uploadFailed( FileUploadEvent event );
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java
deleted file mode 100644 (file)
index 3f4cf47..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011, 2013 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.fileupload;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-
-/**
- * Instances of this interface are responsible for reading and processing the data from a file
- * upload.
- */
-public abstract class FileUploadReceiver {
-
-  /**
-   * Reads and processes all data from the provided input stream.
-   *
-   * @param stream the stream to read from
-   * @param details the details of the uploaded file like file name, content-type and size
-   * @throws IOException if an input / output error occurs
-   */
-  public abstract void receive( InputStream stream, FileDetails details ) throws IOException;
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java
deleted file mode 100644 (file)
index 1688594..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.eclipse.rap.rwt;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient;
-import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager;
-import org.eclipse.rap.rwt.client.Client;
-import org.eclipse.rap.rwt.service.ResourceManager;
-
-public class RWT {
-       public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT";
-       public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED";
-       public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED";
-       public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT";
-       public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS";
-       public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS";
-       public final static String DEFAULT_THEME_ID  = "argeo-rcp:DEFAULT_THEME_ID";
-
-       public final static int HYPERLINK = 0;
-
-       private static Locale locale = Locale.getDefault();
-       private static RcpClient client = new RcpClient();
-       private static ResourceManager resourceManager = new RcpResourceManager();
-       static {
-
-       }
-
-       public static Locale getLocale() {
-               return locale;
-       }
-
-       public static HttpServletRequest getRequest() {
-               return null;
-       }
-
-       public static ResourceManager getResourceManager() {
-               return resourceManager;
-       }
-
-       public static Client getClient() {
-               return client;
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java
deleted file mode 100644 (file)
index 6e30aa6..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.eclipse.rap.rwt;
-
-public class SingletonUtil {
-       public static <T> T getSessionInstance(Class<T> clss) {
-               return null;
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java
deleted file mode 100644 (file)
index 980a818..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-public abstract class AbstractEntryPoint implements EntryPoint {
-       private Display display;
-       private Shell shell;
-
-       protected Shell createShell(Display display) {
-               return new Shell(display);
-       }
-
-       protected void createContents(Composite parent) {
-
-       }
-
-       public int createUI() {
-               display = new Display();
-               shell = createShell(display);
-               shell.setLayout(new GridLayout(1, false));
-               createContents(shell);
-               if (shell.getMaximized()) {
-                       shell.layout();
-               } else {
-                       shell.pack();
-               }
-               shell.open();
-               while (!shell.isDisposed()) {
-                       if (!display.readAndDispatch()) {
-                               display.sleep();
-                       }
-               }
-               display.dispose();
-               return 0;
-       }
-
-       protected Shell getShell() {
-               return shell;
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java
deleted file mode 100644 (file)
index 6cb5f29..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-import java.util.Map;
-
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-public interface Application {
-       public static enum OperationMode {
-               JEE_COMPATIBILITY, SWT_COMPATIBILITY,
-       }
-
-       void setOperationMode(OperationMode operationMode);
-
-       void addResource(String name, ResourceLoader resourceLoader);
-
-       void setExceptionHandler(ExceptionHandler exceptionHandler);
-
-       void addEntryPoint(String path, EntryPointFactory entryPointFactory,
-                       Map<String, String> properties);
-
-       void addEntryPoint(String path, Class<? extends EntryPoint> entryPoint,
-                       Map<String, String> properties);
-
-       void addStyleSheet(String themeId, String styleSheetLocation,
-                       ResourceLoader resourceLoader);
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java
deleted file mode 100644 (file)
index 961ad70..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-public interface ApplicationConfiguration {
-       void configure(Application application);
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java
deleted file mode 100644 (file)
index c0d559a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-public interface EntryPoint {
-       int createUI();
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java
deleted file mode 100644 (file)
index d5b24d8..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-public interface EntryPointFactory {
-       public EntryPoint create();
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java
deleted file mode 100644 (file)
index 13daf21..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.application;
-
-public interface ExceptionHandler {
-       public void handleException(Throwable throwable);
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java
deleted file mode 100644 (file)
index 934feae..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.eclipse.rap.rwt.client;
-
-import java.io.Serializable;
-
-import org.eclipse.rap.rwt.client.service.ClientService;
-
-public interface Client extends Serializable {
-
-  /**
-   * Returns this client's implementation of a given service, if available.
-   *
-   * @param type the type of the requested service, must be a subtype of ClientService
-   * @return the requested service if provided by this client, otherwise <code>null</code>
-   * @see ClientService
-   */
-  <T extends ClientService> T getService( Class<T> type );
-
-}
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java
deleted file mode 100644 (file)
index 1f19bdd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.eclipse.rap.rwt.client;
-
-public interface WebClient {
-       public final static String FAVICON = "rcp:FAVICON";
-       public final static String PAGE_TITLE = "rcp:PAGE_TITLE";
-       public final static String BODY_HTML = "rcp:BODY_HTML";
-       public final static String THEME_ID = "rcp:THEME_ID";
-       public final static String HEAD_HTML = "rcp:HEAD_HTML";
-       public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW";
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java
deleted file mode 100644 (file)
index ffba4e4..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.eclipse.rap.rwt.client.service;
-
-public interface BrowserNavigation extends ClientService {
-       void pushState(String state, String title);
-
-       void addBrowserNavigationListener(BrowserNavigationListener listener);
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java
deleted file mode 100644 (file)
index 3e1b3eb..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.eclipse.rap.rwt.client.service;
-
-public class BrowserNavigationEvent {
-       private String state;
-
-       public String getState() {
-               return state;
-       }
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java
deleted file mode 100644 (file)
index 8319c03..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.client.service;
-
-public interface BrowserNavigationListener {
-       public void navigated(BrowserNavigationEvent event);
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java
deleted file mode 100644 (file)
index 9f479d1..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.eclipse.rap.rwt.client.service;
-
-import java.io.Serializable;
-
-public interface ClientService extends Serializable {
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
deleted file mode 100644 (file)
index 6c44c72..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.eclipse.rap.rwt.client.service;
-
-public interface JavaScriptExecutor extends ClientService {
-       public void execute( String code );
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java
deleted file mode 100644 (file)
index 9dae811..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2012 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.rwt.client.service;
-
-/**
- * The UrlLauncher service allows loading an URL in an external window, application or save dialog.
- *
- * @since 2.0
- * @noimplement This interface is not intended to be implemented by clients.
- */
-public interface UrlLauncher extends ClientService {
-
-  /**
-   * Opens the given URL.
-   *
-   * Any HTTP URL or relative URL will be opened in a new window.
-   * Modern browser may block any attempt to open new windows, but will usually prompt the user to
-   * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only
-   * to future attempts. It could also trigger a document reload, causing a session restart.
-   *
-   * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client
-   * to have a matching protocol handler registered.
-   *
-   * @param url the URL to open
-   */
-  void openURL( String url );
-
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java
deleted file mode 100644 (file)
index 7e7116c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.eclipse.rap.rwt.service;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public interface ResourceLoader {
-       public InputStream getResourceAsStream(String resourceName)
-                       throws IOException;
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java
deleted file mode 100644 (file)
index c3379ea..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.eclipse.rap.rwt.service;
-
-import java.io.InputStream;
-
-public interface ResourceManager {
-       public void register(String name, InputStream in);
-
-       boolean unregister(String name);
-
-       public InputStream getRegisteredContent(String name);
-
-       public String getLocation(String name);
-
-       public boolean isRegistered(String name);
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java
deleted file mode 100644 (file)
index bed194f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.eclipse.rap.rwt.service;
-
-/** Mock, does nothing as this is irrelevant for RCP. */
-public class ServerPushSession {
-       public void start() {
-
-       }
-
-       public void stop() {
-
-       }
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java
deleted file mode 100644 (file)
index b2a2005..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.eclipse.rap.rwt.widgets;
-
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Widget;
-
-public class DropDown {
-       private boolean visible=false;
-
-       public DropDown(Widget parent, int style) {
-               // FIXME implement a shell
-       }
-
-       public DropDown(Widget parent) {
-               this(parent, SWT.NONE);
-       }
-
-       public void setVisible(boolean visible) {
-               this.visible = visible;
-       }
-
-       public boolean isVisible() {
-               return visible;
-       }
-       
-       public void setItems( String[] items ) {
-               
-       }
-       
-       public void setSelectionIndex( int selection ) {
-               
-       }
-       
-}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java
deleted file mode 100644 (file)
index cbf1449..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.eclipse.rap.rwt.widgets;
-
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-
-public class FileUpload extends Composite {
-
-       public FileUpload(Composite parent, int style) {
-               super(parent, style);
-       }
-
-       public void addSelectionListener(SelectionListener listener) {
-
-       }
-
-       public void submit(String url) {
-
-       }
-
-       public void setImage(Image image) {
-
-       }
-
-       public void setText(String text) {
-
-       }
-
-       public String getFileName() {
-               return null;
-       }
-
-       public String[] getFileNames() {
-               return null;
-       }
-
-}
index 5029b67ae930b82e822407b64d740173fe2d34c0..a1d5c8e4bbee31d104fc1151cac4a8e19f5ef1fa 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 5029b67ae930b82e822407b64d740173fe2d34c0
+Subproject commit a1d5c8e4bbee31d104fc1151cac4a8e19f5ef1fa
diff --git a/sdk/branches/testing.bnd b/sdk/branches/testing.bnd
new file mode 100644 (file)
index 0000000..b94c3b5
--- /dev/null
@@ -0,0 +1,4 @@
+major=2
+minor=1
+micro=106
+qualifier=.next
\ No newline at end of file
diff --git a/sdk/branches/unstable.bnd b/sdk/branches/unstable.bnd
new file mode 100644 (file)
index 0000000..98d7516
--- /dev/null
@@ -0,0 +1,4 @@
+major=2
+minor=3
+micro=14
+qualifier=.next
\ No newline at end of file
index a11ba7e1c82510383fac2274cfffda099d41ff97..1ca557b7e8de5aa955e94f3f32eb33d6c57fa05d 100644 (file)
@@ -1,20 +1,18 @@
-argeo.osgi.start.2.node=\
+argeo.osgi.start.2=\
 org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
+org.apache.felix.scr,\
 org.eclipse.rap.rwt.osgi,\
 org.argeo.init
 
-argeo.osgi.start.3.node=\
-org.argeo.cms
+argeo.osgi.start.3=\
+org.argeo.cms,\
+org.argeo.cms.swt.rap,\
+org.argeo.cms.swt.rcp,\
+org.argeo.cms.ee,\
+org.argeo.cms.lib.sshd,\
+org.argeo.cms.lib.equinox,\
+org.argeo.cms.lib.jetty,\
 
-argeo.osgi.start.4.node=\
-org.argeo.cms.servlet,\
-org.argeo.cms.jcr
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
 
 # Local
 argeo.node.repo.type=h2
@@ -61,9 +59,11 @@ log.org.argeo=DEBUG
 
 # DON'T CHANGE BELOW
 org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+org.osgi.framework.system.packages.extra=\
+com.sun.net.httpserver,\
+com.sun.jndi.ldap,\
 com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
 com.sun.jndi.dns,\
+com.sun.security.jgss,\
 com.sun.nio.file,\
 com.sun.nio.sctp
diff --git a/sdk/cms-rcp.properties b/sdk/cms-rcp.properties
new file mode 100644 (file)
index 0000000..df8363b
--- /dev/null
@@ -0,0 +1,39 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+org.argeo.cms.ui.rcp
+
+
+# Local
+argeo.node.repo.type=h2
+org.osgi.service.http.port=7070
+#org.osgi.service.http.port.secure=7073
+
+argeo.node.useradmin.uris=os:///
+
+#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com
+
+argeo.node.init=../../init
+
+argeo.i18n.locales=en,fr
+argeo.i18n.defaultLocale=en
+
+#tika.config=/home/mbaudier/dev/git/gpl/argeo-suite/sdk/exec/argeo-office-e4-rap/data/indexes/node/tika-config.xml
+
+# Logging
+log.org.argeo=DEBUG
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/deploy/.gitignore b/sdk/deploy/.gitignore
new file mode 100644 (file)
index 0000000..08eb0a0
--- /dev/null
@@ -0,0 +1 @@
+!bin/
\ No newline at end of file
diff --git a/sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open b/sdk/deploy/argeo-cms-rcp/usr/bin/argeo-desktop-open
new file mode 100755 (executable)
index 0000000..17b1e88
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+curl $(cat $XDG_RUNTIME_DIR/argeo.rcp.url)$1
\ No newline at end of file
diff --git a/sdk/deploy/argeo-cms/usr/bin/argeo b/sdk/deploy/argeo-cms/usr/bin/argeo
new file mode 100755 (executable)
index 0000000..636fd47
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+java -Dorg.argeo.api.cli.rootCommand=$0 -jar /usr/share/a2/org.argeo.cms/org.argeo.cms.cli.2.3.jar "$@"
\ No newline at end of file
diff --git a/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring b/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.monitoring
new file mode 100644 (file)
index 0000000..d7275ee
--- /dev/null
@@ -0,0 +1 @@
+-Dcom.sun.management.jmxremote.port=8099 -Dcom.sun.management.jmxremote.rmi.port=8099 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=<hostname>
\ No newline at end of file
diff --git a/sdk/deploy/argeo-init/etc/argeo.user.d/jvm.args b/sdk/deploy/argeo-init/etc/argeo.user.d/jvm.args
new file mode 100644 (file)
index 0000000..e69de29
index 1cd43c9b9117344ab918d8a45400af50b1451e46..2c69636acf74d88856d62a0cd8586594896665aa 100644 (file)
@@ -1,32 +1,42 @@
 [Unit]
 Description=Argeo node %I
-After=network.target
+After=network-online.target
 Wants=postgresql.service
 
 [Service]
 Type=simple
+
+User=daemon
+Group=daemon
+
 StateDirectory=argeo.d/%I
 LogsDirectory=argeo.d/%I
 ConfigurationDirectory=argeo.d/%I
 CacheDirectory=argeo.d/%I
 WorkingDirectory=/var/lib/argeo.d/%I
 
-ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java \
+ExecStart=java \
 -Dosgi.configuration.cascaded=true \
--Dosgi.sharedConfiguration.area=/etc/argeo.d/%I \
+-Dosgi.sharedConfiguration.area=/etc/argeo.d/%I/ \
 -Dosgi.sharedConfiguration.area.readOnly=true \
--Dosgi.configuration.area=/var/lib/argeo.d/%I/state \
--Dosgi.instance.area=/var/lib/argeo.d/%I/data \
--Dargeo.node.repo.indexesBase=/var/cache/argeo.d/%I/indexes \
--Dorg.osgi.framework.bootdelegation=com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp \
+-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \
+-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \
+-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \
 -Declipse.ignoreApp=true \
 -Dosgi.noShutdown=true \
 -Dorg.eclipse.equinox.http.jetty.autostart=false \
 @/etc/argeo.d/jvm.args \
-@/etc/argeo.d/%I/jvm.args \
+@${CONFIGURATION_DIRECTORY}/jvm.args \
 @/usr/share/argeo/jvm.args
+
 # Exit codes of the JVM when SIGTERM or SIGINT have been caught:
 SuccessExitStatus=143 130
 
+CPUAccounting=true
+MemoryAccounting=true
+TasksAccounting=true
+IOAccounting=true
+IPAccounting=true
+
 [Install]
 WantedBy=multi-user.target
diff --git a/sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service b/sdk/deploy/argeo-init/usr/lib/systemd/user/argeo@.service
new file mode 100644 (file)
index 0000000..345685a
--- /dev/null
@@ -0,0 +1,30 @@
+[Unit]
+Description=Argeo user node %I
+
+[Service]
+Type=simple
+StateDirectory=argeo.d/%I
+LogsDirectory=argeo.d/%I
+ConfigurationDirectory=argeo.d/%I
+CacheDirectory=argeo.d/%I
+#WorkingDirectory=
+
+ExecStart=java \
+-Dosgi.configuration.cascaded=true \
+-Dosgi.sharedConfiguration.area=/etc/argeo.user.d/%I/ \
+-Dosgi.sharedConfiguration.area.readOnly=true \
+-Dosgi.configuration.area=${STATE_DIRECTORY}/state/ \
+-Dosgi.instance.area=${STATE_DIRECTORY}/data/ \
+-Dargeo.node.repo.indexesBase=${CACHE_DIRECTORY}/indexes \
+-Declipse.ignoreApp=true \
+-Dosgi.noShutdown=true \
+-Dorg.eclipse.equinox.http.jetty.autostart=false \
+-Djava.library.path=/usr/lib/a2/swt/rcp/org.argeo.tp.swt/ \
+@/etc/argeo.user.d/jvm.args \
+@/etc/argeo.user.d/%I/jvm.args \
+@/usr/share/argeo/jvm.args
+# Exit codes of the JVM when SIGTERM or SIGINT have been caught:
+SuccessExitStatus=143 130
+
+[Install]
+WantedBy=multi-user.target
index 8d57ecb7e1f2b5d96fef41275ae27ee578c20614..2d3190d6f74b652a301f116f19f21da2b235abce 100644 (file)
@@ -1 +1 @@
--cp /usr/share/a2/org.argeo.tp.eclipse.equinox/org.eclipse.osgi.3.17.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.1.jar org.argeo.init.Service
\ No newline at end of file
+-cp /usr/share/a2/osgi/equinox/org.argeo.tp.osgi/org.eclipse.osgi.3.18.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.3.jar org.argeo.init.Service
\ No newline at end of file
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 ffa9073..0000000
+++ /dev/null
@@ -1,12 +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=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..ffa9073
--- /dev/null
@@ -0,0 +1,12 @@
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=userAdmin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=roles,ou=node
+cn: userAdmin
+
diff --git a/swt/org.argeo.cms.e4/.classpath b/swt/org.argeo.cms.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.cms.e4/.project b/swt/org.argeo.cms.e4/.project
new file mode 100644 (file)
index 0000000..0c04069
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.e4</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml
new file mode 100644 (file)
index 0000000..fcd3ae5
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default CallbackHandler">
+   <implementation class="org.argeo.cms.swt.auth.DynamicCallbackHandler"/>
+   <service>
+      <provide interface="javax.security.auth.callback.CallbackHandler"/>
+   </service>
+</scr:component>
diff --git a/swt/org.argeo.cms.e4/bnd.bnd b/swt/org.argeo.cms.e4/bnd.bnd
new file mode 100644 (file)
index 0000000..8839805
--- /dev/null
@@ -0,0 +1,20 @@
+Service-Component: OSGI-INF/defaultCallbackHandler.xml
+Bundle-ActivationPolicy: lazy
+
+Import-Package: \
+org.argeo.api.acr,\
+org.eclipse.swt,\
+org.eclipse.swt.widgets;version="0.0.0",\
+org.eclipse.e4.ui.model.application.ui;resolution:=optional,\
+org.eclipse.e4.ui.model.application;resolution:=optional,\
+org.argeo.cms,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+org.argeo.cms.swt.auth,\
+org.argeo.cms.ux.widgets,\
+javax.servlet.*;version="[3,5)",\
+org.eclipse.*;resolution:=optional,\
+javax.*;resolution:=optional,\
+*
+
diff --git a/swt/org.argeo.cms.e4/build.properties b/swt/org.argeo.cms.e4/build.properties
new file mode 100644 (file)
index 0000000..e46a7ba
--- /dev/null
@@ -0,0 +1,9 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               OSGI-INF/,\
+               .,\
+               OSGI-INF/homeRepository.xml,\
+               OSGI-INF/userAdminWrapper.xml,\
+               OSGI-INF/defaultCallbackHandler.xml,\
+               e4xmi/cms-demo.e4xmi
+source.. = src/
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java
new file mode 100644 (file)
index 0000000..a997de7
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.cms.e4;
+
+import java.util.List;
+
+import org.argeo.cms.swt.CmsException;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.commands.MCommand;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
+import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkUtil;
+
+/** Static utilities simplifying recurring Eclipse 4 patterns. */
+public class CmsE4Utils {
+       /** Open an editor based on its id. */
+       public static void openEditor(EPartService partService, String editorId, String key, String state) {
+               for (MPart part : partService.getParts()) {
+                       String id = part.getPersistedState().get(key);
+                       if (id != null && state.equals(id)) {
+                               partService.showPart(part, PartState.ACTIVATE);
+                               return;
+                       }
+               }
+
+               // new part
+               MPart part = partService.createPart(editorId);
+               if (part == null)
+                       throw new CmsException("No editor found with id " + editorId);
+               part.getPersistedState().put(key, state);
+               partService.showPart(part, PartState.ACTIVATE);
+       }
+
+       /** Dynamically creates an handled menu item from a command ID. */
+       public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app,
+                       String commandId) {
+               MCommand command = findCommand(modelService, app, commandId);
+               if (command == null)
+                       return null;
+               MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class);
+               handledItem.setCommand(command);
+               return handledItem;
+
+       }
+
+       /**
+        * Finds a command by ID.
+        * 
+        * @return the {@link MCommand} or <code>null</code> if not found.
+        */
+       public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) {
+               List<MCommand> cmds = modelService.findElements(app, null, MCommand.class, null);
+               for (MCommand cmd : cmds) {
+                       if (cmd.getElementId().equals(commandId)) {
+                               return cmd;
+                       }
+               }
+               return null;
+       }
+
+       /** Dynamically creates a direct menu item from a class. */
+       public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class<?> clss, String label) {
+               MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
+               dynamicItem.setLabel(label);
+               Bundle bundle = FrameworkUtil.getBundle(clss);
+               dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName());
+               return dynamicItem;
+       }
+
+       /** Singleton. */
+       private CmsE4Utils() {
+       }
+
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java
new file mode 100644 (file)
index 0000000..1e3e75c
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.e4;
+
+import org.argeo.cms.swt.CmsException;
+import org.eclipse.e4.core.contexts.ContextFunction;
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.core.di.IInjector;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */
+public class OsgiFilterContextFunction extends ContextFunction {
+
+       private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext();
+
+       @Override
+       public Object compute(IEclipseContext context, String contextKey) {
+               ServiceReference<?>[] srs;
+               try {
+                       srs = bc.getServiceReferences((String) null, contextKey);
+               } catch (InvalidSyntaxException e) {
+                       throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e);
+               }
+               if (srs == null || srs.length == 0) {
+                       return IInjector.NOT_A_VALUE;
+               } else {
+                       // return the first one
+                       return bc.getService(srs[0]);
+               }
+       }
+
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java
new file mode 100644 (file)
index 0000000..89055d2
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.cms.e4;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * Propagate authentication to an eclipse job. Typically to execute a privileged
+ * action outside the UI thread
+ */
+public abstract class PrivilegedJob extends Job {
+       private final Subject subject;
+
+       public PrivilegedJob(String jobName) {
+               this(jobName, AccessController.getContext());
+       }
+
+       public PrivilegedJob(String jobName,
+                       AccessControlContext accessControlContext) {
+               super(jobName);
+               subject = Subject.getSubject(accessControlContext);
+
+               // Must be called *before* the job is scheduled,
+               // it is required for the progress window to appear
+               setUser(true);
+       }
+
+       @Override
+       protected IStatus run(final IProgressMonitor progressMonitor) {
+               PrivilegedAction<IStatus> privilegedAction = new PrivilegedAction<IStatus>() {
+                       public IStatus run() {
+                               return doRun(progressMonitor);
+                       }
+               };
+               return Subject.doAs(subject, privilegedAction);
+       }
+
+       /**
+        * Implement here what should be executed with default context
+        * authentication
+        */
+       protected abstract IStatus doRun(IProgressMonitor progressMonitor);
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java
new file mode 100644 (file)
index 0000000..66a5ec8
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.cms.e4.addons;
+
+import java.security.AccessController;
+import java.util.Iterator;
+
+import javax.annotation.PostConstruct;
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsException;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.MElementContainer;
+import org.eclipse.e4.ui.model.application.ui.MUIElement;
+import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
+import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow;
+import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
+
+public class AuthAddon {
+       private final static CmsLog log = CmsLog.getLog(AuthAddon.class);
+
+       public final static String AUTH = "auth.";
+
+       @PostConstruct
+       void init(MApplication application) {
+               Iterator<MWindow> windows = application.getChildren().iterator();
+               boolean atLeastOneTopLevelWindowVisible = false;
+               windows: while (windows.hasNext()) {
+                       MWindow window = windows.next();
+                       // main window
+                       boolean windowVisible = process(window);
+                       if (!windowVisible) {
+//                             windows.remove();
+                               continue windows;
+                       }
+                       atLeastOneTopLevelWindowVisible = true;
+                       // trim bars
+                       if (window instanceof MTrimmedWindow) {
+                               Iterator<MTrimBar> trimBars = ((MTrimmedWindow) window).getTrimBars().iterator();
+                               while (trimBars.hasNext()) {
+                                       MTrimBar trimBar = trimBars.next();
+                                       if (!process(trimBar)) {
+                                               trimBars.remove();
+                                       }
+                               }
+                       }
+               }
+
+               if (!atLeastOneTopLevelWindowVisible) {
+                       log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out..");
+                       logout();
+               }
+       }
+
+       protected boolean process(MUIElement element) {
+               for (String tag : element.getTags()) {
+                       if (tag.startsWith(AUTH)) {
+                               String role = tag.substring(AUTH.length(), tag.length());
+                               if (!CurrentUser.isInRole(role)) {
+                                       element.setVisible(false);
+                                       element.setToBeRendered(false);
+                                       return false;
+                               }
+                       }
+               }
+
+               // children
+               if (element instanceof MElementContainer) {
+                       @SuppressWarnings("unchecked")
+                       MElementContainer<? extends MUIElement> container = (MElementContainer<? extends MUIElement>) element;
+                       Iterator<? extends MUIElement> children = container.getChildren().iterator();
+                       while (children.hasNext()) {
+                               MUIElement child = children.next();
+                               boolean visible = process(child);
+                               if (!visible)
+                                       children.remove();
+                       }
+
+                       for (Object child : container.getChildren()) {
+                               if (child instanceof MUIElement) {
+                                       boolean visible = process((MUIElement) child);
+                                       if (!visible)
+                                               container.getChildren().remove(child);
+                               }
+                       }
+               }
+
+               return true;
+       }
+
+       protected void logout() {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               try {
+                       CurrentUser.logoutCmsSession(subject);
+               } catch (Exception e) {
+                       throw new CmsException("Cannot log out", e);
+               }
+               
+               // FIXME make it more generic
+               HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest();
+               if (request != null)
+                       request.getSession().setMaxInactiveInterval(0);
+       }
+
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java
new file mode 100644 (file)
index 0000000..5bc0d69
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.cms.e4.addons;
+
+import java.security.AccessController;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+import javax.security.auth.Subject;
+
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.e4.core.services.nls.ILocaleChangeService;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.ElementMatcher;
+import org.eclipse.swt.SWT;
+
+/** Integrate workbench with the locale provided at log in. */
+public class LocaleAddon {
+       private final static String STYLE_OVERRIDE = "styleOverride";
+
+       // Right to left languages
+       private final static String ARABIC = "ar";
+       private final static String HEBREW = "he";
+
+       @PostConstruct
+       public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               Set<Locale> locales = subject.getPublicCredentials(Locale.class);
+               if (!locales.isEmpty()) {
+                       Locale locale = locales.iterator().next();
+                       localeChangeService.changeApplicationLocale(locale);
+                       UiContext.setLocale(locale);
+
+                       if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) {
+                               List<MWindow> windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE,
+                                               new ElementMatcher(null, null, (String) null));
+                               for (MWindow window : windows) {
+                                       String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE);
+                                       int style = 0;
+                                       if (currentStyle != null) {
+                                               style = Integer.parseInt(currentStyle);
+                                       }
+                                       style = style | SWT.RIGHT_TO_LEFT;
+                                       window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style));
+                               }
+                       }
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java
new file mode 100644 (file)
index 0000000..6367b42
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse 4 addons to integrate with Argeo CMS. */
+package org.argeo.cms.e4.addons;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java
new file mode 100644 (file)
index 0000000..416df7d
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.e4.handlers;
+
+import java.util.Locale;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.services.nls.ILocaleChangeService;
+
+public class ChangeLanguage {
+       @Execute
+       public void execute(ILocaleChangeService localeChangeService) {
+               localeChangeService.changeApplicationLocale(Locale.FRENCH);
+       }
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java
new file mode 100644 (file)
index 0000000..9624c2d
--- /dev/null
@@ -0,0 +1,136 @@
+package org.argeo.cms.e4.handlers;
+
+import static org.argeo.cms.CmsMsg.changePassword;
+import static org.argeo.cms.CmsMsg.currentPassword;
+import static org.argeo.cms.CmsMsg.newPassword;
+import static org.argeo.cms.CmsMsg.passwordChanged;
+import static org.argeo.cms.CmsMsg.repeatNewPassword;
+
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.keyring.CryptoKeyring;
+import org.argeo.api.cms.transaction.WorkTransaction;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.swt.dialogs.CmsMessageDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.core.di.annotations.Optional;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Change the password of the logged-in user. */
+public class ChangePassword {
+       @Inject
+       private UserAdmin userAdmin;
+       @Inject
+       private WorkTransaction userTransaction;
+       @Inject
+       @Optional
+       private CryptoKeyring keyring = null;
+
+       @Execute
+       public void execute() {
+               ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin);
+               if (dialog.open() == CmsDialog.OK) {
+                       new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(),
+                                       CmsMessageDialog.INFORMATION).open();
+               }
+       }
+
+       protected void changePassword(char[] oldPassword, char[] newPassword) {
+               String name = CurrentUser.getUsername();
+               LdapName dn;
+               try {
+                       dn = new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid user dn " + name, e);
+               }
+               User user = (User) userAdmin.getRole(dn.toString());
+               if (!user.hasCredential(null, oldPassword))
+                       throw new IllegalArgumentException("Invalid password");
+               if (Arrays.equals(newPassword, new char[0]))
+                       throw new IllegalArgumentException("New password empty");
+               try {
+                       userTransaction.begin();
+                       user.getCredentials().put(null, newPassword);
+                       if (keyring != null) {
+                               keyring.changePassword(oldPassword, newPassword);
+                               // TODO change secret keys in the CMS session
+                       }
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               e1.printStackTrace();
+                       }
+                       if (e instanceof RuntimeException)
+                               throw (RuntimeException) e;
+                       else
+                               throw new IllegalStateException("Cannot change password", e);
+               }
+       }
+
+       class ChangePasswordDialog extends CmsMessageDialog {
+               private Text oldPassword, newPassword1, newPassword2;
+
+               public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) {
+                       super(parentShell, changePassword.lead(), CONFIRM);
+               }
+
+//             protected Point getInitialSize() {
+//                     return new Point(400, 450);
+//             }
+
+               protected Control createDialogArea(Composite parent) {
+                       Composite dialogarea = (Composite) super.createDialogArea(parent);
+                       dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+                       Composite composite = new Composite(dialogarea, SWT.NONE);
+                       composite.setLayout(new GridLayout(2, false));
+                       composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       oldPassword = createLP(composite, currentPassword.lead());
+                       newPassword1 = createLP(composite, newPassword.lead());
+                       newPassword2 = createLP(composite, repeatNewPassword.lead());
+
+//                     parent.pack();
+                       oldPassword.setFocus();
+                       return composite;
+               }
+
+               @Override
+               protected void okPressed() {
+                       try {
+                               if (!newPassword1.getText().equals(newPassword2.getText()))
+                                       throw new IllegalArgumentException("New passwords are different");
+                               changePassword(oldPassword.getTextChars(), newPassword1.getTextChars());
+                               closeShell(CmsDialog.OK);
+                       } catch (Exception e) {
+                               CmsFeedback.error("Cannot change password", e);
+                       }
+               }
+
+               /** Creates label and password. */
+               protected Text createLP(Composite parent, String label) {
+                       new Label(parent, SWT.NONE).setText(label);
+                       Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
+                       text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       return text;
+               }
+
+       }
+
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java
new file mode 100644 (file)
index 0000000..d11c041
--- /dev/null
@@ -0,0 +1,37 @@
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class CloseAllParts {
+
+       @Execute
+       void execute(EPartService partService) {
+               for (MPart part : partService.getParts()) {
+                       if (part.isCloseable()) {
+                               if (part.isDirty()) {
+                                       if (partService.savePart(part, true)) {
+                                               partService.hidePart(part, true);
+                                       }
+                               } else {
+                                       partService.hidePart(part, true);
+                               }
+                       }
+               }
+       }
+
+       @CanExecute
+       boolean canExecute(EPartService partService) {
+               boolean atLeastOnePart = false;
+               for (MPart part : partService.getParts()) {
+                       if (part.isVisible() && part.isCloseable()) {
+                               atLeastOnePart = true;
+                               break;
+                       }
+               }
+               return atLeastOnePart;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java
new file mode 100644 (file)
index 0000000..cce1802
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.e4.handlers;
+
+import javax.security.auth.Subject;
+
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.util.CurrentSubject;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.workbench.IWorkbench;
+
+public class CloseWorkbench {
+       @Execute
+       public void execute(IWorkbench workbench) {
+               logout();
+               workbench.close();
+       }
+
+       protected void logout() {
+               Subject subject = CurrentSubject.current();
+               try {
+                       CurrentUser.logoutCmsSession(subject);
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot log out", e);
+               }
+       }
+
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java
new file mode 100644 (file)
index 0000000..358494c
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+
+public class DoNothing {
+       @Execute
+       public void execute() {
+
+       }
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java
new file mode 100644 (file)
index 0000000..ac825bb
--- /dev/null
@@ -0,0 +1,29 @@
+
+package org.argeo.cms.e4.handlers;
+
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.e4.ui.di.AboutToHide;
+import org.eclipse.e4.ui.di.AboutToShow;
+import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem;
+import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+
+public class LanguageMenuContribution {
+       @AboutToShow
+       public void aboutToShow(List<MMenuElement> items, EModelService modelService) {
+               MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class);
+               dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")");
+               //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4");
+               //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName());
+               dynamicItem.setEnabled(true);
+               dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage");
+               items.add(dynamicItem);
+       }
+
+       @AboutToHide
+       public void aboutToHide() {
+               
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java
new file mode 100644 (file)
index 0000000..ac544b1
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.cms.e4.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.MApplication;
+import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective;
+import org.eclipse.e4.ui.workbench.modeling.EModelService;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class OpenPerspective {
+       @Inject
+       MApplication application;
+       @Inject
+       EPartService partService;
+       @Inject
+       EModelService modelService;
+
+       @Execute
+       public void execute(@Named("perspectiveId") String perspectiveId) {
+               List<MPerspective> perspectives = modelService.findElements(application, perspectiveId, MPerspective.class,
+                               null);
+               if (perspectives.size() == 0)
+                       return;
+               MPerspective perspective = perspectives.get(0);
+               partService.switchPerspective(perspective);
+       }
+}
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java
new file mode 100644 (file)
index 0000000..3b60abd
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class SaveAllParts {
+
+       @Execute
+       void execute(EPartService partService) {
+               partService.saveAll(false);
+       }
+
+       @CanExecute
+       boolean canExecute(EPartService partService) {
+               return partService.getDirtyParts().size() > 0;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java
new file mode 100644 (file)
index 0000000..73486f3
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.e4.handlers;
+
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+
+public class SavePart {
+       @Execute
+       void execute(EPartService partService, MPart part) {
+               partService.savePart(part, false);
+       }
+
+       @CanExecute
+       boolean canExecute(MPart part) {
+               return part.isDirty();
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java
new file mode 100644 (file)
index 0000000..a44ca90
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic Eclipse 4 handlers. */
+package org.argeo.cms.e4.handlers;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java
new file mode 100644 (file)
index 0000000..233119c
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse 4 user interfaces. */
+package org.argeo.cms.e4;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/.classpath b/swt/org.argeo.cms.swt/.classpath
new file mode 100644 (file)
index 0000000..248abe0
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src" />
+       <classpathentry kind="con"
+               path="org.eclipse.pde.core.requiredPlugins" />
+       <classpathentry kind="con"
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17" />
+       <classpathentry kind="output" path="bin" />
+</classpath>
diff --git a/swt/org.argeo.cms.swt/.project b/swt/org.argeo.cms.swt/.project
new file mode 100644 (file)
index 0000000..8ac021b
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.swt</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.cms.swt/META-INF/.gitignore b/swt/org.argeo.cms.swt/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml b/swt/org.argeo.cms.swt/OSGI-INF/cmsUserApp.xml
new file mode 100644 (file)
index 0000000..4f2a405
--- /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" name="CMS User App">
+   <implementation class="org.argeo.cms.swt.app.CmsUserApp"/>
+   <property name="argeo.cms.app.contextName" type="String" value="cms/user"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsApp"/>
+   </service>
+   <reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+</scr:component>
diff --git a/swt/org.argeo.cms.swt/bnd.bnd b/swt/org.argeo.cms.swt/bnd.bnd
new file mode 100644 (file)
index 0000000..1dfa659
--- /dev/null
@@ -0,0 +1,12 @@
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+org.eclipse.core.commands.common,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Bundle-ActivationPolicy: lazy
+                       
+Service-Component: \
+OSGI-INF/cmsUserApp.xml
+                       
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/build.properties b/swt/org.argeo.cms.swt/build.properties
new file mode 100644 (file)
index 0000000..5f0f21a
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/cmsUserApp.xml
+source.. = src/
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/jface/dialog/CmsWizardDialog.java
new file mode 100644 (file)
index 0000000..ad347e6
--- /dev/null
@@ -0,0 +1,223 @@
+package org.argeo.cms.jface.dialog;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.jface.wizard.IWizardContainer2;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** A wizard dialog based on {@link LightweightDialog}. */
+public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 {
+       private static final long serialVersionUID = -2123153353654812154L;
+
+       private IWizard wizard;
+       private IWizardPage currentPage;
+       private int currentPageIndex;
+
+       private Label titleBar;
+       private Label message;
+       private Composite[] pageBodies;
+       private Composite buttons;
+       private Button back;
+       private Button next;
+       private Button finish;
+
+       public CmsWizardDialog(Shell parentShell, IWizard wizard) {
+               super(parentShell);
+               this.wizard = wizard;
+               wizard.setContainer(this);
+               // create the pages
+               wizard.addPages();
+               currentPage = wizard.getStartingPage();
+               if (currentPage == null)
+                       throw new IllegalArgumentException("At least one wizard page is required");
+       }
+
+       @Override
+       protected Control createDialogArea(Composite parent) {
+               updateWindowTitle();
+
+               Composite messageArea = new Composite(parent, SWT.NONE);
+               messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               {
+                       messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+                       titleBar = new Label(messageArea, SWT.WRAP);
+                       titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
+                       titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
+                       updateTitleBar();
+                       Button cancelButton = new Button(messageArea, SWT.FLAT);
+                       cancelButton.setText(CmsMsg.cancel.lead());
+                       cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
+                       cancelButton.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.CANCEL));
+                       message = new Label(messageArea, SWT.WRAP);
+                       message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
+                       updateMessage();
+               }
+
+               Composite body = new Composite(parent, SWT.BORDER);
+               body.setLayout(new FormLayout());
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               pageBodies = new Composite[wizard.getPageCount()];
+               IWizardPage[] pages = wizard.getPages();
+               for (int i = 0; i < pages.length; i++) {
+                       pageBodies[i] = new Composite(body, SWT.NONE);
+                       pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       setSwitchingFormData(pageBodies[i]);
+                       pages[i].createControl(pageBodies[i]);
+               }
+               showPage(currentPage);
+
+               buttons = new Composite(parent, SWT.NONE);
+               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               {
+                       boolean singlePage = wizard.getPageCount() == 1;
+                       // singlePage = false;// dev
+                       GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
+                       layout.marginWidth = 0;
+                       layout.marginHeight = 0;
+                       buttons.setLayout(layout);
+                       // TODO revert order for right-to-left languages
+
+                       if (!singlePage) {
+                               back = new Button(buttons, SWT.PUSH);
+                               back.setText(CmsMsg.wizardBack.lead());
+                               back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               back.addSelectionListener((Selected) (e) -> backPressed());
+
+                               next = new Button(buttons, SWT.PUSH);
+                               next.setText(CmsMsg.wizardNext.lead());
+                               next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               next.addSelectionListener((Selected) (e) -> nextPressed());
+                       }
+                       finish = new Button(buttons, SWT.PUSH);
+                       finish.setText(CmsMsg.wizardFinish.lead());
+                       finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       finish.addSelectionListener((Selected) (e) -> finishPressed());
+
+                       updateButtons();
+               }
+               return body;
+       }
+
+       @Override
+       public IWizardPage getCurrentPage() {
+               return currentPage;
+       }
+
+       @Override
+       public Shell getShell() {
+               return getForegoundShell();
+       }
+
+       @Override
+       public void showPage(IWizardPage page) {
+               IWizardPage[] pages = wizard.getPages();
+               int index = -1;
+               for (int i = 0; i < pages.length; i++) {
+                       if (page == pages[i]) {
+                               index = i;
+                               break;
+                       }
+               }
+               if (index < 0)
+                       throw new IllegalArgumentException("Cannot find index of wizard page " + page);
+               pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
+
+               // // clear
+               // for (Control c : body.getChildren())
+               // c.dispose();
+               // page.createControl(body);
+               // body.layout(true, true);
+               currentPageIndex = index;
+               currentPage = page;
+       }
+
+       @Override
+       public void updateButtons() {
+               if (back != null)
+                       back.setEnabled(wizard.getPreviousPage(currentPage) != null);
+               if (next != null)
+                       next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
+               if (finish != null) {
+                       finish.setEnabled(wizard.canFinish());
+               }
+       }
+
+       @Override
+       public void updateMessage() {
+               if (currentPage.getMessage() != null)
+                       message.setText(currentPage.getMessage());
+       }
+
+       @Override
+       public void updateTitleBar() {
+               if (currentPage.getTitle() != null)
+                       titleBar.setText(currentPage.getTitle());
+       }
+
+       @Override
+       public void updateWindowTitle() {
+               setTitle(wizard.getWindowTitle());
+       }
+
+       @Override
+       public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
+                       throws InvocationTargetException, InterruptedException {
+               // FIXME it creates a dependency to Eclipse Core Runtime
+               // runnable.run(null);
+       }
+
+       @Override
+       public void updateSize() {
+               // TODO pack?
+       }
+
+       protected boolean onCancel() {
+               return wizard.performCancel();
+       }
+
+       protected void nextPressed() {
+               IWizardPage page = wizard.getNextPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void backPressed() {
+               IWizardPage page = wizard.getPreviousPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void finishPressed() {
+               if (wizard.performFinish())
+                       closeShell(CmsDialog.OK);
+       }
+
+       private static void setSwitchingFormData(Composite composite) {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               composite.setLayoutData(fdLabel);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtCmsView.java
new file mode 100644 (file)
index 0000000..06bb9be
--- /dev/null
@@ -0,0 +1,141 @@
+package org.argeo.cms.swt;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.util.CurrentSubject;
+import org.eclipse.swt.widgets.Display;
+
+public abstract class AbstractSwtCmsView implements CmsView {
+       private final static CmsLog log = CmsLog.getLog(AbstractSwtCmsView.class);
+
+       protected final String uiName;
+
+       protected LoginContext loginContext;
+       protected String state;
+//     protected Throwable exception;
+       protected UxContext uxContext;
+       protected CmsImageManager imageManager;
+
+       protected Display display;
+       protected CmsUi ui;
+
+       protected String uid;
+
+       public AbstractSwtCmsView(String uiName) {
+               this.uiName = uiName;
+       }
+
+       public abstract CmsEventBus getCmsEventBus();
+
+       public abstract CmsApp getCmsApp();
+
+       @Override
+       public void sendEvent(String topic, Map<String, Object> properties) {
+               if (properties == null)
+                       properties = new HashMap<>();
+               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
+                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
+                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
+               properties.put(CMS_VIEW_UID_PROPERTY, uid);
+
+               log.trace(() -> uid + ": send event to " + topic);
+
+               getCmsEventBus().sendEvent(topic, properties);
+               // getCmsApp().onEvent(topic, properties);
+       }
+
+//     public void runAs(Runnable runnable) {
+//             display.asyncExec(() -> doAs(Executors.callable(runnable)));
+//     }
+
+       public <T> T doAs(Callable<T> action) {
+               try {
+                       CompletableFuture<T> result = new CompletableFuture<>();
+                       Runnable toDo = () -> {
+                               log.trace(() -> uid + ": process doAs");
+                               Subject subject = CurrentSubject.current();
+                               T res;
+                               if (subject != null) {
+                                       assert subject == getSubject();
+                                       try {
+                                               res = action.call();
+                                       } catch (Exception e) {
+                                               throw new CompletionException("Failed to execute action for " + subject, e);
+                                       }
+                               } else {
+                                       res = CurrentSubject.callAs(getSubject(), action);
+                               }
+                               result.complete(res);
+                       };
+                       if (Thread.currentThread() == display.getThread())
+                               toDo.run();
+                       else {
+                               display.asyncExec(toDo);
+                               display.wake();
+                       }
+//                             throw new IllegalStateException("Must be called from UI thread");
+                       return result.get();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot execute action ins CMS view " + uid, e);
+               }
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       @Override
+       public CmsImageManager<?, ?> getImageManager() {
+               return imageManager;
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public Object getData(String key) {
+               if (ui != null) {
+                       return ui.getData(key);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+       @Override
+       public void setData(String key, Object value) {
+               if (ui != null) {
+                       ui.setData(key, value);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/AbstractSwtImageManager.java
new file mode 100644 (file)
index 0000000..00a51ef
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.ux.AbstractImageManager;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Manages only public images so far. */
+public abstract class AbstractSwtImageManager<M> extends AbstractImageManager<Control, M> {
+       protected abstract Image getSwtImage(M node);
+
+       protected abstract String noImg(Cms2DSize size);
+
+       public Boolean load(M node, Control control, Cms2DSize preferredSize) {
+               Cms2DSize imageSize = getImageSize(node);
+               Cms2DSize size;
+               String imgTag = null;
+               if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0
+                               || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) {
+                       if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) {
+                               // actual image size if completely known
+                               size = imageSize;
+                       } else {
+                               // no image if not completely known
+                               size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize);
+                               imgTag = noImg(size);
+                       }
+
+               } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) {
+                       // given size if completely provided
+                       size = preferredSize;
+               } else {
+                       // at this stage :
+                       // image is completely known
+                       assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0;
+                       // one and only one of the dimension as been specified
+                       assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0;
+                       size = resizeTo(imageSize, preferredSize);
+               }
+
+               boolean loaded = false;
+               if (control == null)
+                       return loaded;
+
+               if (control instanceof Label) {
+                       if (imgTag == null) {
+                               // IMAGE RETRIEVED HERE
+                               imgTag = getImageTag(node, size);
+                               //
+                               if (imgTag == null)
+                                       imgTag = noImg(size);
+                               else
+                                       loaded = true;
+                       }
+
+                       Label lbl = (Label) control;
+                       lbl.setText(imgTag);
+                       // lbl.setSize(size);
+//             } else if (control instanceof FileUpload) {
+//                     FileUpload lbl = (FileUpload) control;
+//                     lbl.setImage(CmsUiUtils.noImage(size));
+//                     lbl.setSize(new Point(size.getWidth(), size.getHeight()));
+//                     return loaded;
+               } else
+                       loaded = false;
+
+               return loaded;
+       }
+
+       public Cms2DSize getImageSize(M node) {
+               // TODO optimise
+               Image image = getSwtImage(node);
+               return new Cms2DSize(image.getBounds().width, image.getBounds().height);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsException.java
new file mode 100644 (file)
index 0000000..874ea96
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.cms.swt;
+
+/** @deprecated Use standard Java {@link RuntimeException} instead. */
+@Deprecated
+public class CmsException extends RuntimeException {
+       private static final long serialVersionUID = -5341764743356771313L;
+
+       public CmsException(String message) {
+               super(message);
+       }
+
+       public CmsException(String message, Throwable e) {
+               super(message, e);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java
new file mode 100644 (file)
index 0000000..9eba6f6
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms.swt;
+
+/** Styles references in the CSS. */
+@Deprecated
+public interface CmsStyles {
+       // General
+       public final static String CMS_SHELL = "cms_shell";
+       public final static String CMS_MENU_LINK = "cms_menu_link";
+
+       // Header
+       public final static String CMS_HEADER = "cms_header";
+       public final static String CMS_HEADER_LEAD = "cms_header-lead";
+       public final static String CMS_HEADER_CENTER = "cms_header-center";
+       public final static String CMS_HEADER_END = "cms_header-end";
+
+       public final static String CMS_LEAD = "cms_lead";
+       public final static String CMS_END = "cms_end";
+       public final static String CMS_FOOTER = "cms_footer";
+
+       public final static String CMS_USER_MENU = "cms_user_menu";
+       public final static String CMS_USER_MENU_LINK = "cms_user_menu-link";
+       public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item";
+       public final static String CMS_LOGIN_DIALOG = "cms_login_dialog";
+       public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username";
+       public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password";
+
+       // Body
+       public final static String CMS_SCROLLED_AREA = "cms_scrolled_area";
+       public final static String CMS_BODY = "cms_body";
+       public final static String CMS_STATIC_TEXT = "cms_static-text";
+       public final static String CMS_LINK = "cms_link";
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java
new file mode 100644 (file)
index 0000000..7669b15
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.eclipse.swt.graphics.Image;
+
+/** SWT specific {@link CmsTheme}. */
+public interface CmsSwtTheme extends CmsTheme {
+//     /** The image registered at this path, or <code>null</code> if not found. */
+//     Image getImage(String path);
+
+       /**
+        * And icon with this file name (without the extension), with a best effort to
+        * find the appropriate size, or <code>null</code> if not found.
+        * 
+        * @param name          An icon file name without path and extension.
+        * @param preferredSize the preferred size, if <code>null</code>,
+        *                      {@link #getDefaultIconSize()} will be tried.
+        */
+       Image getIcon(String name, Integer preferredSize);
+
+       Image getSmallIcon(CmsIcon icon);
+       
+       Image getBigIcon(CmsIcon icon);
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUi.java
new file mode 100644 (file)
index 0000000..e0f63e4
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+
+/** A basic {@link CmsUi}, based on an SWT {@link Composite}. */
+public class CmsSwtUi extends Composite implements CmsUi {
+
+       private static final long serialVersionUID = -107939076610406448L;
+
+       private CmsView cmsView;
+
+       public CmsSwtUi(Composite parent, int style) {
+               super(parent, style);
+               cmsView = CmsSwtUtils.getCmsView(parent);
+
+               setLayout(new GridLayout());
+       }
+
+       public CmsView getCmsView() {
+               return cmsView;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java
new file mode 100644 (file)
index 0000000..5d96409
--- /dev/null
@@ -0,0 +1,315 @@
+package org.argeo.cms.swt;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Layout;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/** SWT utilities. */
+public class CmsSwtUtils {
+       /*
+        * THEME AND VIEW
+        */
+
+       public static CmsSwtTheme getCmsTheme(Composite parent) {
+               CmsSwtTheme theme = (CmsSwtTheme) parent.getData(CmsTheme.class.getName());
+               if (theme == null) {
+                       // find parent shell
+                       Shell topShell = parent.getShell();
+                       while (topShell.getParent() != null)
+                               topShell = (Shell) topShell.getParent();
+                       theme = (CmsSwtTheme) topShell.getData(CmsTheme.class.getName());
+                       parent.setData(CmsTheme.class.getName(), theme);
+               }
+               return theme;
+       }
+
+       public static void registerCmsTheme(Shell shell, CmsTheme theme) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsTheme.class.getName()) != null) {
+                       CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+                       throw new IllegalArgumentException(
+                                       "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
+               }
+               topShell.setData(CmsTheme.class.getName(), theme);
+       }
+
+       public static CmsView getCmsView(Control parent) {
+               // find parent shell
+               Shell topShell = parent.getShell();
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               return (CmsView) topShell.getData(CmsView.class.getName());
+       }
+
+       public static void registerCmsView(Shell shell, CmsView view) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsView.class.getName()) != null) {
+                       CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
+                       throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
+               }
+               shell.setData(CmsView.class.getName(), view);
+       }
+
+       /*
+        * EVENTS
+        */
+
+       /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
+       public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
+               SelectionListener listener = (Selected) (e) -> {
+                       getCmsView(control.getParent()).sendEvent(topic, properties);
+               };
+               if (control instanceof Button) {
+                       ((Button) control).addSelectionListener(listener);
+               } else
+                       throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
+       }
+
+       /**
+        * Convenience method to sends an event via
+        * {@link CmsView#sendEvent(String, Map)}.
+        */
+       public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(key, value);
+               sendEventOnSelect(control, topic, properties);
+       }
+
+       /*
+        * ICONS
+        */
+       /** Get a small icon from this theme. */
+       public static Image getSmallIcon(CmsTheme theme, CmsIcon icon) {
+               return ((CmsSwtTheme) theme).getSmallIcon(icon);
+       }
+
+       /** Get a big icon from this theme. */
+       public static Image getBigIcon(CmsTheme theme, CmsIcon icon) {
+               return ((CmsSwtTheme) theme).getBigIcon(icon);
+       }
+
+       /*
+        * LAYOUT INDEPENDENT
+        */
+       /** Takes the most space possible, depending on parent layout. */
+       public static void fill(Control control) {
+               Layout parentLayout = control.getParent().getLayout();
+               if (parentLayout == null)
+                       throw new IllegalStateException("Parent layout is not set");
+               if (parentLayout instanceof GridLayout) {
+                       control.setLayoutData(fillAll());
+               } else if (parentLayout instanceof FormLayout) {
+                       control.setLayoutData(coverAll());
+               } else {
+                       throw new IllegalArgumentException("Unsupported parent layout  " + parentLayout.getClass().getName());
+               }
+       }
+
+       /*
+        * GRID LAYOUT
+        */
+       /** A {@link GridLayout} without any spacing and one column. */
+       public static GridLayout noSpaceGridLayout() {
+               return noSpaceGridLayout(new GridLayout());
+       }
+
+       /**
+        * A {@link GridLayout} without any spacing and multiple columns of unequal
+        * width.
+        */
+       public static GridLayout noSpaceGridLayout(int columns) {
+               return noSpaceGridLayout(new GridLayout(columns, false));
+       }
+
+       /** @return the same layout, with spaces removed. */
+       public static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static GridData fillAll() {
+               return new GridData(SWT.FILL, SWT.FILL, true, true);
+       }
+
+       public static GridData fillWidth() {
+               return grabWidth(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+       }
+
+       public static GridData fillHeight() {
+               return grabHeight(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, false, true);
+       }
+
+       /*
+        * ROW LAYOUT
+        */
+       /** @return the same layout, with margins removed. */
+       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
+               rowLayout.marginTop = 0;
+               rowLayout.marginBottom = 0;
+               rowLayout.marginLeft = 0;
+               rowLayout.marginRight = 0;
+               return rowLayout;
+       }
+
+       public static RowLayout noMarginsRowLayout(int type) {
+               return noMarginsRowLayout(new RowLayout(type));
+       }
+
+       public static RowData rowData16px() {
+               return new RowData(16, 16);
+       }
+
+       /*
+        * FORM LAYOUT
+        */
+       public static FormData coverAll() {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               return fdLabel;
+       }
+
+       /*
+        * STYLING
+        */
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, String style) {
+               if (style == null)
+                       return widget;// does nothing
+               EclipseUiSpecificUtils.setStyleData(widget, style);
+               if (widget instanceof Control) {
+                       CmsView cmsView = getCmsView((Control) widget);
+                       if (cmsView != null)
+                               cmsView.applyStyles(widget);
+               }
+               return widget;
+       }
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, CmsStyle style) {
+               return style(widget, style.style());
+       }
+
+       /** Enable markups on widget */
+       public static <T extends Widget> T markup(T widget) {
+               EclipseUiSpecificUtils.setMarkupData(widget);
+               return widget;
+       }
+
+       /** Disable markup validation. */
+       public static <T extends Widget> T disableMarkupValidation(T widget) {
+               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
+               return widget;
+       }
+
+       /**
+        * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
+        * 
+        * @param widget the widget to style and to use in order to display text
+        * @param txt    the object to display via its <code>toString()</code> method.
+        *               This argument should not be null, but if it is null and
+        *               assertions are disabled "<null>" is displayed instead; if
+        *               assertions are enabled the call will fail.
+        * 
+        * @see markup
+        */
+       public static <T extends Widget> T text(T widget, Object txt) {
+               assert txt != null;
+               String str = txt != null ? txt.toString() : "<null>";
+               markup(widget);
+               if (widget instanceof Label)
+                       ((Label) widget).setText(str);
+               else if (widget instanceof Button)
+                       ((Button) widget).setText(str);
+               else if (widget instanceof Text)
+                       ((Text) widget).setText(str);
+               else
+                       throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
+               return widget;
+       }
+
+       /** A {@link Label} with markup activated. */
+       public static Label lbl(Composite parent, Object txt) {
+               return text(new Label(parent, SWT.NONE), txt);
+       }
+
+       /** A read-only {@link Text} whose content can be copy/pasted. */
+       public static Text txt(Composite parent, Object txt) {
+               return text(new Text(parent, SWT.NONE), txt);
+       }
+
+       /** Dispose all children of a Composite */
+       public static void clear(Composite composite) {
+               if (composite.isDisposed())
+                       return;
+               for (Control child : composite.getChildren())
+                       child.dispose();
+       }
+
+       /** Clean reserved URL characters for use in HTTP links. */
+       public static String cleanPathForUrl(String path) {
+               StringTokenizer st = new StringTokenizer(path, "/");
+               StringBuilder sb = new StringBuilder();
+               while (st.hasMoreElements()) {
+                       sb.append('/');
+                       String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
+                       encoded = encoded.replace("+", "%20");
+                       sb.append(encoded);
+
+               }
+               return sb.toString();
+       }
+
+       /** Singleton. */
+       private CmsSwtUtils() {
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java
new file mode 100644 (file)
index 0000000..c664aa3
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+
+/**
+ * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface
+ * in order to use as a short lambda expression in UI code.
+ * {@link MouseListener#mouseDown(MouseEvent)} and
+ * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
+ */
+@FunctionalInterface
+public interface MouseDoubleClick extends MouseListener {
+       @Override
+       void mouseDoubleClick(MouseEvent e);
+
+       @Override
+       default void mouseDown(MouseEvent e) {
+               // does nothing
+       }
+
+       @Override
+       default void mouseUp(MouseEvent e) {
+               // does nothing
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java
new file mode 100644 (file)
index 0000000..baecb00
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+
+/**
+ * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in
+ * order to use as a short lambda expression in UI code.
+ * {@link MouseListener#mouseDoubleClick(MouseEvent)} and
+ * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default.
+ */
+@FunctionalInterface
+public interface MouseDown extends MouseListener {
+       @Override
+       void mouseDown(MouseEvent e);
+
+       @Override
+       default void mouseDoubleClick(MouseEvent e) {
+               // does nothing
+       }
+
+       @Override
+       default void mouseUp(MouseEvent e) {
+               // does nothing
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java
new file mode 100644 (file)
index 0000000..03fbad0
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.cms.swt;
+
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+
+/**
+ * {@link SelectionListener} as a functional interface in order to use as a
+ * short lambda expression in UI code.
+ * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing
+ * by default.
+ */
+@FunctionalInterface
+public interface Selected extends SelectionListener {
+       @Override
+       public void widgetSelected(SelectionEvent e);
+
+       default public void widgetDefaultSelected(SelectionEvent e) {
+               // does nothing
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java
new file mode 100644 (file)
index 0000000..e468c6d
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.cms.swt;
+
+import org.argeo.api.cms.ux.UxContext;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+
+public class SimpleSwtUxContext implements UxContext {
+       private Point size;
+       private Point small = new Point(400, 400);
+
+       public SimpleSwtUxContext() {
+               this(Display.getCurrent().getBounds());
+       }
+
+       public SimpleSwtUxContext(Rectangle rect) {
+               this.size = new Point(rect.width, rect.height);
+       }
+
+       public SimpleSwtUxContext(Point size) {
+               this.size = size;
+       }
+
+       @Override
+       public boolean isPortrait() {
+               return size.x >= size.y;
+       }
+
+       @Override
+       public boolean isLandscape() {
+               return size.x < size.y;
+       }
+
+       @Override
+       public boolean isSquare() {
+               return size.x == size.y;
+       }
+
+       @Override
+       public boolean isSmall() {
+               return size.x <= small.x || size.y <= small.y;
+       }
+
+       @Override
+       public boolean isMasterData() {
+               // TODO make it configurable
+               return true;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/SwtEditablePart.java
new file mode 100644 (file)
index 0000000..f2cceef
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.cms.swt;
+
+import org.argeo.cms.ux.widgets.EditablePart;
+import org.eclipse.swt.widgets.Control;
+
+/** Manages whether an editable or non editable control is shown. */
+public interface SwtEditablePart extends EditablePart {
+       public Control getControl();
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AbstractPageViewer.java
new file mode 100644 (file)
index 0000000..cf05f6f
--- /dev/null
@@ -0,0 +1,337 @@
+package org.argeo.cms.swt.acr;
+
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsEditable;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.cms.swt.widgets.ScrolledPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Widget;
+import org.xml.sax.SAXParseException;
+
+/** Base class for viewers related to a page */
+public abstract class AbstractPageViewer {
+
+       private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class);
+
+       private final boolean readOnly;
+       /** The basis for the layouts, typically a ScrolledPage. */
+       private final Composite page;
+       private final CmsEditable cmsEditable;
+
+       private MouseListener mouseListener;
+       private FocusListener focusListener;
+
+       private SwtEditablePart edited;
+//     private ISelection selection = StructuredSelection.EMPTY;
+
+       private Subject viewerSubject;
+
+       protected AbstractPageViewer(Composite parent, int style, CmsEditable cmsEditable) {
+               // read only at UI level
+               readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+
+               this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable;
+//             if (this.cmsEditable instanceof Observable)
+//                     ((Observable) this.cmsEditable).addObserver(this);
+
+               if (cmsEditable.canEdit()) {
+                       mouseListener = createMouseListener();
+                       focusListener = createFocusListener();
+               }
+               page = findPage(parent);
+//             accessControlContext = AccessController.getContext();
+               viewerSubject = CurrentUser.getCmsSession().getSubject();
+       }
+
+       public abstract Control getControl();
+
+//     /**
+//      * Can be called to simplify the called to isModelInitialized() and initModel()
+//      */
+//     protected void initModelIfNeeded(Node node) {
+//             try {
+//                     if (!isModelInitialized(node))
+//                             if (getCmsEditable().canEdit()) {
+//                                     initModel(node);
+//                                     node.getSession().save();
+//                             }
+//             } catch (RepositoryException e) {
+//                     throw new JcrException("Cannot initialize model", e);
+//             }
+//     }
+//
+//     /** Called if user can edit and model is not initialized */
+//     protected Boolean isModelInitialized(Node node) throws RepositoryException {
+//             return true;
+//     }
+//
+//     /** Called if user can edit and model is not initialized */
+//     protected void initModel(Node node) throws RepositoryException {
+//     }
+
+       /** Create (retrieve) the MouseListener to use. */
+       protected MouseListener createMouseListener() {
+               return new MouseAdapter() {
+                       private static final long serialVersionUID = 1L;
+               };
+       }
+
+       /** Create (retrieve) the FocusListener to use. */
+       protected FocusListener createFocusListener() {
+               return new FocusListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void focusLost(FocusEvent event) {
+                       }
+
+                       @Override
+                       public void focusGained(FocusEvent event) {
+                       }
+               };
+       }
+
+       protected Composite findPage(Composite composite) {
+               if (composite instanceof ScrolledPage) {
+                       return (ScrolledPage) composite;
+               } else {
+                       if (composite.getParent() == null)
+                               return composite;
+                       return findPage(composite.getParent());
+               }
+       }
+
+       public void layoutPage() {
+               if (page != null)
+                       page.layout(true, true);
+       }
+
+       protected void showControl(Control control) {
+               if (page != null && (page instanceof ScrolledPage))
+                       ((ScrolledPage) page).showControl(control);
+       }
+
+//     @Override
+//     public void update(Observable o, Object arg) {
+//             if (o == cmsEditable)
+//                     editingStateChanged(cmsEditable);
+//     }
+
+       /** To be overridden in order to provide the actual refresh */
+       protected void refresh(Control control) {
+       }
+
+       /** To be overridden.Save the edited part. */
+       protected void save(SwtEditablePart part) {
+       }
+
+       /** Prepare the edited part */
+       protected void prepare(SwtEditablePart part, Object caretPosition) {
+       }
+
+       /** Notified when the editing state changed. Does nothing, to be overridden */
+       protected void editingStateChanged(CmsEditable cmsEditable) {
+       }
+
+       public void refresh() {
+               // TODO check actual context in order to notice a discrepancy
+               Subject viewerSubject = getViewerSubject();
+               Subject.doAs(viewerSubject, (PrivilegedAction<Void>) () -> {
+                       if (cmsEditable.canEdit() && !readOnly)
+                               mouseListener = createMouseListener();
+                       else
+                               mouseListener = null;
+                       refresh(getControl());
+                       // layout(getControl());
+                       if (!getControl().isDisposed())
+                               layoutPage();
+                       return null;
+               });
+       }
+
+//     @Override
+//     public void setSelection(ISelection selection, boolean reveal) {
+//             this.selection = selection;
+//     }
+
+       protected void updateContent(SwtEditablePart part) {
+       }
+
+       // LOW LEVEL EDITION
+       protected void edit(SwtEditablePart part, Object caretPosition) {
+               if (edited == part)
+                       return;
+
+               if (edited != null && edited != part) {
+                       SwtEditablePart previouslyEdited = edited;
+                       try {
+                               stopEditing(true);
+                       } catch (Exception e) {
+                               notifyEditionException(e);
+                               edit(previouslyEdited, caretPosition);
+                               return;
+                       }
+               }
+
+               part.startEditing();
+               edited = part;
+               updateContent(part);
+               prepare(part, caretPosition);
+               edited.getControl().addFocusListener(new FocusListener() {
+                       private static final long serialVersionUID = 6883521812717097017L;
+
+                       @Override
+                       public void focusLost(FocusEvent event) {
+                               stopEditing(true);
+                       }
+
+                       @Override
+                       public void focusGained(FocusEvent event) {
+                       }
+               });
+
+               layout(part.getControl());
+               showControl(part.getControl());
+       }
+
+       protected void stopEditing(Boolean save) {
+               if (edited instanceof Widget && ((Widget) edited).isDisposed()) {
+                       edited = null;
+                       return;
+               }
+
+               assert edited != null;
+               if (edited == null) {
+                       if (log.isTraceEnabled())
+                               log.warn("Told to stop editing while not editing anything");
+                       return;
+               }
+
+               try {
+                       if (save)
+                               save(edited);
+
+                       edited.stopEditing();
+                       SwtEditablePart editablePart = edited;
+                       Control control = ((SwtEditablePart) edited).getControl();
+                       edited = null;
+                       // TODO make edited state management more robust
+                       updateContent(editablePart);
+                       layout(control);
+               } finally {
+                       edited = null;
+               }
+       }
+
+       // METHODS AVAILABLE TO EXTENDING CLASSES
+       protected void saveEdit() {
+               if (edited != null)
+                       stopEditing(true);
+       }
+
+       protected void cancelEdit() {
+               if (edited != null)
+                       stopEditing(false);
+       }
+
+       /** Layout this controls from the related base page. */
+       public void layout(Control... controls) {
+               page.layout(controls);
+       }
+
+       /**
+        * Find the first {@link SwtEditablePart} in the parents hierarchy of this
+        * control
+        */
+       protected SwtEditablePart findDataParent(Control parent) {
+               if (parent instanceof SwtEditablePart) {
+                       return (SwtEditablePart) parent;
+               }
+               if (parent.getParent() != null)
+                       return findDataParent(parent.getParent());
+               else
+                       throw new IllegalStateException("No data parent found");
+       }
+
+       // UTILITIES
+       /** Check whether the edited part is in a proper state */
+       protected void checkEdited() {
+               if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed())
+                       throw new IllegalStateException("Edited should not be null or disposed at this stage");
+       }
+
+       /** Persist all changes. */
+       protected void persistChanges(ContentSession session) {
+//             session.save();
+//             session.refresh(false);
+               // TODO notify that changes have been persisted
+       }
+
+       /** Convenience method using a Node in order to save the underlying session. */
+       protected void persistChanges(Content anyNode) {
+               persistChanges(((ProvidedContent) anyNode).getSession());
+       }
+
+       /** Notify edition exception */
+       protected void notifyEditionException(Throwable e) {
+               Throwable eToLog = e;
+               if (e instanceof IllegalArgumentException)
+                       if (e.getCause() instanceof SAXParseException)
+                               eToLog = e.getCause();
+               log.error(eToLog.getMessage(), eToLog);
+//             if (log.isTraceEnabled())
+//                     log.trace("Full stack of " + eToLog.getMessage(), e);
+               // TODO Light error notification popup
+       }
+
+       protected Subject getViewerSubject() {
+               return viewerSubject;
+//             Subject res = null;
+//             if (accessControlContext != null) {
+//                     res = Subject.getSubject(accessControlContext);
+//             }
+//             if (res == null)
+//                     throw new IllegalStateException("No subject associated with this viewer");
+//             return res;
+       }
+
+       // GETTERS / SETTERS
+       public boolean isReadOnly() {
+               return readOnly;
+       }
+
+       protected SwtEditablePart getEdited() {
+               return edited;
+       }
+
+       public MouseListener getMouseListener() {
+               return mouseListener;
+       }
+
+       public FocusListener getFocusListener() {
+               return focusListener;
+       }
+
+       public CmsEditable getCmsEditable() {
+               return cmsEditable;
+       }
+
+//     @Override
+//     public ISelection getSelection() {
+//             return selection;
+//     }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/AcrSwtImageManager.java
new file mode 100644 (file)
index 0000000..78a9930
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.swt.acr;
+
+import java.io.InputStream;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.AbstractSwtImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.eclipse.swt.graphics.Image;
+
+public class AcrSwtImageManager extends AbstractSwtImageManager<Content> {
+
+       @Override
+       public String getImageUrl(Content node) {
+               return getDataPathForUrl(node);
+       }
+
+       @Override
+       public String uploadImage(Content context, Content uploadFolder, String fileName, InputStream in,
+                       String contentType) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected Image getSwtImage(Content node) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       protected String noImg(Cms2DSize size) {
+               String dataPath = "";
+               return CmsUxUtils.img(dataPath, size);
+       }
+
+       protected String getDataPathForUrl(Content content) {
+               return CmsSwtUtils.cleanPathForUrl(getDataPath(content));
+       }
+
+       /** A path in the node repository */
+       protected String getDataPath(Content node) {
+               // TODO make it more configurable?
+               StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR);
+               buf.append(node.getPath());
+               return buf.toString();
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/ContentComposite.java
new file mode 100644 (file)
index 0000000..4a35a3b
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.eclipse.swt.widgets.Composite;
+
+/** A composite which can (optionally) manage a content. */
+public class ContentComposite extends Composite {
+       private static final long serialVersionUID = -1447009015451153367L;
+
+       public ContentComposite(Composite parent, int style, Content item) {
+               super(parent, style);
+               if (item != null)
+                       setData(item);
+       }
+
+       public boolean hasContent() {
+               if (getData() == null)
+                       return false;
+               return getData() instanceof Content;
+       }
+
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       @Deprecated
+       public Content getNode() {
+               return getContent();
+       }
+
+       protected ProvidedContent getProvidedContent() {
+               return (ProvidedContent) getContent();
+       }
+
+       public String getSessionLocalId() {
+               return getProvidedContent().getSessionLocalId();
+       }
+
+       protected void itemUpdated() {
+               layout();
+       }
+
+       public void setContent(Content content) {
+               setData(content);
+               itemUpdated();
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/Img.java
new file mode 100644 (file)
index 0000000..eb52fc6
--- /dev/null
@@ -0,0 +1,144 @@
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.EditableImage;
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.eclipse.ui.specific.CmsFileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An image within the Argeo Text framework */
+public class Img extends EditableImage implements SwtSectionPart, ContentPart {
+       private static final long serialVersionUID = 6233572783968188476L;
+
+       private final SwtSection section;
+
+       private final CmsImageManager<Control, Content> imageManager;
+//     private FileUploadHandler currentUploadHandler = null;
+//     private FileUploadListener fileUploadListener;
+
+       public Img(Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       public Img(Composite parent, int swtStyle, Content imgNode) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, null);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       public Img(Composite parent, int swtStyle, Content imgNode, CmsImageManager<Control, Content> imageManager) {
+               this(SwtSection.findSection(parent), parent, swtStyle, imgNode, null, imageManager);
+//             setStyle(TextStyles.TEXT_IMAGE);
+       }
+
+       Img(SwtSection section, Composite parent, int swtStyle, Content imgNode, Cms2DSize preferredImageSize,
+                       CmsImageManager<Control, Content> imageManager) {
+               super(parent, swtStyle, preferredImageSize);
+               this.section = section;
+               this.imageManager = imageManager != null ? imageManager
+                               : (CmsImageManager<Control, Content>) CmsSwtUtils.getCmsView(section).getImageManager();
+//             CmsSwtUtils.style(this, TextStyles.TEXT_IMG);
+               setData(imgNode);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               if (isEditing()) {
+                       return createImageChooser(box, style);
+               } else {
+                       return createLabel(box, style);
+               }
+       }
+
+       @Override
+       public synchronized void stopEditing() {
+               super.stopEditing();
+//             fileUploadListener = null;
+       }
+
+       @Override
+       protected synchronized Boolean load(Control lbl) {
+               Content imgNode = getContent();
+               boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
+               // getParent().layout();
+               return loaded;
+       }
+
+       protected Content getUploadFolder() {
+               return getContent().getParent();
+       }
+
+       protected String getUploadName() {
+               Content node = getContent();
+               // TODO centralise pattern?
+               return NamespaceUtils.toPrefixedName(node.getName()) + '[' + node.getSiblingIndex() + ']';
+       }
+
+       protected CmsImageManager<Control, Content> getImageManager() {
+               return imageManager;
+       }
+
+       protected Control createImageChooser(Composite box, String style) {
+//             JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(),
+//                             imageManager);
+//             if (currentUploadHandler != null)
+//                     currentUploadHandler.dispose();
+//             currentUploadHandler = prepareUpload(receiver);
+//             final ServerPushSession pushSession = new ServerPushSession();
+               final CmsFileUpload fileUpload = new CmsFileUpload(box, SWT.NONE);
+               CmsSwtUtils.style(fileUpload, style);
+               fileUpload.addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = -9158471843941668562L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+//                             pushSession.start();
+//                             fileUpload.submit(currentUploadHandler.getUploadUrl());
+                       }
+               });
+               return fileUpload;
+       }
+
+//     protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) {
+//             final FileUploadHandler uploadHandler = new FileUploadHandler(receiver);
+//             if (fileUploadListener != null)
+//                     uploadHandler.addUploadListener(fileUploadListener);
+//             return uploadHandler;
+//     }
+
+       @Override
+       public SwtSection getSection() {
+               return section;
+       }
+
+//     public void setFileUploadListener(FileUploadListener fileUploadListener) {
+//             this.fileUploadListener = fileUploadListener;
+//             if (currentUploadHandler != null)
+//                     currentUploadHandler.addUploadListener(fileUploadListener);
+//     }
+
+       @Override
+       public Content getContent() {
+               return (Content) getData();
+       }
+
+       @Override
+       public String getPartId() {
+               return ((ProvidedContent) getContent()).getSessionLocalId();
+       }
+
+       @Override
+       public String toString() {
+               return "Img #" + getPartId();
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSection.java
new file mode 100644 (file)
index 0000000..f4f5961
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.swt.acr;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.EditablePart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** A structured UI related to an ACR context. */
+public class SwtSection extends ContentComposite {
+       private static final long serialVersionUID = -5933796173755739207L;
+
+       private final SwtSection parentSection;
+       private Composite sectionHeader;
+       private final Integer relativeDepth;
+
+       public SwtSection(Composite parent, int style, Content node) {
+               this(parent, findSection(parent), style, node);
+       }
+
+       public SwtSection(SwtSection section, int style, Content node) {
+               this(section, section, style, node);
+       }
+
+       public SwtSection(SwtSection section, int style) {
+               this(section, style, null);
+       }
+
+       protected SwtSection(Composite parent, SwtSection parentSection, int style, Content node) {
+               super(parent, style, node);
+               this.parentSection = parentSection;
+               if (parentSection != null && hasContent() && parentSection.hasContent()) {
+                       relativeDepth = getProvidedContent().getDepth() - parentSection.getProvidedContent().getDepth();
+               } else {
+                       relativeDepth = 0;
+               }
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+       }
+
+       public Map<String, SwtSection> getSubSections() {
+               LinkedHashMap<String, SwtSection> result = new LinkedHashMap<String, SwtSection>();
+               for (Control child : getChildren()) {
+                       if (child instanceof Composite) {
+                               collectDirectSubSections((Composite) child, result);
+                       }
+               }
+               return Collections.unmodifiableMap(result);
+       }
+
+       private void collectDirectSubSections(Composite composite, LinkedHashMap<String, SwtSection> subSections) {
+               if (composite == sectionHeader || composite instanceof EditablePart)
+                       return;
+               if (composite instanceof SwtSection) {
+                       SwtSection section = (SwtSection) composite;
+                       subSections.put(section.getProvidedContent().getSessionLocalId(), section);
+                       return;
+               }
+
+               for (Control child : composite.getChildren())
+                       if (child instanceof Composite)
+                               collectDirectSubSections((Composite) child, subSections);
+       }
+
+       public Composite createHeader() {
+               return createHeader(this);
+       }
+
+       public Composite createHeader(Composite parent) {
+               if (sectionHeader != null)
+                       sectionHeader.dispose();
+
+               sectionHeader = new Composite(parent, SWT.NONE);
+               sectionHeader.setLayoutData(CmsSwtUtils.fillWidth());
+               sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               // sectionHeader.moveAbove(null);
+               // layout();
+               return sectionHeader;
+       }
+
+       public Composite getHeader() {
+               if (sectionHeader != null && sectionHeader.isDisposed())
+                       sectionHeader = null;
+               return sectionHeader;
+       }
+
+       // SECTION PARTS
+       public SwtSectionPart getSectionPart(String partId) {
+               for (Control child : getChildren()) {
+                       if (child instanceof SwtSectionPart) {
+                               SwtSectionPart sectionPart = (SwtSectionPart) child;
+                               if (sectionPart.getPartId().equals(partId))
+                                       return sectionPart;
+                       }
+               }
+               return null;
+       }
+
+       public SwtSectionPart nextSectionPart(SwtSectionPart sectionPart) {
+               Control[] children = getChildren();
+               for (int i = 0; i < children.length; i++) {
+                       if (sectionPart == children[i]) {
+                               for (int j = i + 1; j < children.length; j++) {
+                                       if (children[i + 1] instanceof SwtSectionPart) {
+                                               return (SwtSectionPart) children[i + 1];
+                                       }
+                               }
+
+//                             if (i + 1 < children.length) {
+//                                     Composite next = (Composite) children[i + 1];
+//                                     return (SectionPart) next;
+//                             } else {
+//                                     // next section
+//                             }
+                       }
+               }
+               return null;
+       }
+
+       public SwtSectionPart previousSectionPart(SwtSectionPart sectionPart) {
+               Control[] children = getChildren();
+               for (int i = 0; i < children.length; i++) {
+                       if (sectionPart == children[i])
+                               if (i != 0) {
+                                       Composite previous = (Composite) children[i - 1];
+                                       return (SwtSectionPart) previous;
+                               } else {
+                                       // previous section
+                               }
+               }
+               return null;
+       }
+
+       @Override
+       public String toString() {
+               if (parentSection == null)
+                       return "Main section " + getContent();
+               return "Section " + getContent();
+       }
+
+       public SwtSection getParentSection() {
+               return parentSection;
+       }
+
+       public Integer getRelativeDepth() {
+               return relativeDepth;
+       }
+
+       /** Recursively finds the related section in the parents (can be itself) */
+       public static SwtSection findSection(Control control) {
+               if (control == null)
+                       return null;
+               if (control instanceof SwtSection)
+                       return (SwtSection) control;
+               else
+                       return findSection(control.getParent());
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtSectionPart.java
new file mode 100644 (file)
index 0000000..7fbf4bb
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.cms.swt.acr;
+
+import org.argeo.cms.ux.acr.ContentPart;
+import org.argeo.cms.ux.widgets.EditablePart;
+
+/** An editable part dynamically related to a Section */
+public interface SwtSectionPart extends EditablePart, ContentPart {
+       public String getPartId();
+
+       public SwtSection getSection();
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtTabbedArea.java
new file mode 100644 (file)
index 0000000..b65bc3b
--- /dev/null
@@ -0,0 +1,265 @@
+package org.argeo.cms.swt.acr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StackLayout;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** Manages {@link SwtSection} in a tab-like structure. */
+public class SwtTabbedArea extends Composite {
+       private static final long serialVersionUID = 8659669229482033444L;
+
+       private Composite headers;
+       private Composite body;
+
+       private List<SwtSection> sections = new ArrayList<>();
+
+       private ProvidedContent previousNode;
+       private SwtUiProvider previousUiProvider;
+       private SwtUiProvider currentUiProvider;
+
+       private String tabStyle;
+       private String tabSelectedStyle;
+       private String bodyStyle;
+       private Image closeIcon;
+
+       private StackLayout stackLayout;
+
+       private boolean singleTab = false;
+       private String singleTabTitle = null;
+
+       public SwtTabbedArea(Composite parent, int style) {
+               super(parent, SWT.NONE);
+               CmsSwtUtils.style(parent, bodyStyle);
+
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               // TODO manage tabs at bottom or sides
+               headers = new Composite(this, SWT.NONE);
+               headers.setLayoutData(CmsSwtUtils.fillWidth());
+               body = new Composite(this, SWT.NONE);
+               body.setLayoutData(CmsSwtUtils.fillAll());
+               // body.setLayout(new FormLayout());
+               stackLayout = new StackLayout();
+               body.setLayout(stackLayout);
+               emptyState();
+       }
+
+       protected void refreshTabHeaders() {
+               int tabCount = sections.size() > 0 ? sections.size() : 1;
+               for (Control tab : headers.getChildren())
+                       tab.dispose();
+
+               headers.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(tabCount, true)));
+
+               if (sections.size() == 0) {
+                       Composite emptyHeader = new Composite(headers, SWT.NONE);
+                       emptyHeader.setLayoutData(CmsSwtUtils.fillAll());
+                       emptyHeader.setLayout(new GridLayout());
+                       Label lbl = new Label(emptyHeader, SWT.NONE);
+                       lbl.setText("");
+                       lbl.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+
+               }
+
+               SwtSection currentSection = getCurrentSection();
+               for (SwtSection section : sections) {
+                       boolean selected = section == currentSection;
+                       Composite sectionHeader = section.createHeader(headers);
+                       CmsSwtUtils.style(sectionHeader, selected ? tabSelectedStyle : tabStyle);
+                       int headerColumns = singleTab ? 1 : 2;
+                       sectionHeader.setLayout(new GridLayout(headerColumns, false));
+                       sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout(headerColumns));
+                       Button title = new Button(sectionHeader, SWT.FLAT);
+                       CmsSwtUtils.style(title, selected ? tabSelectedStyle : tabStyle);
+                       title.setLayoutData(CmsSwtUtils.fillWidth());
+                       title.addSelectionListener((Selected) (e) -> showTab(tabIndex(section.getContent())));
+                       Content node = section.getContent();
+
+                       // FIXME find a standard way to display titles
+                       String titleStr = node.getName().getLocalPart();
+                       if (singleTab && singleTabTitle != null)
+                               titleStr = singleTabTitle;
+
+                       // TODO internationalise
+                       title.setText(titleStr);
+                       if (!singleTab) {
+                               ToolBar toolBar = new ToolBar(sectionHeader, SWT.NONE);
+                               ToolItem closeItem = new ToolItem(toolBar, SWT.FLAT);
+                               if (closeIcon != null)
+                                       closeItem.setImage(closeIcon);
+                               else
+                                       closeItem.setText("X");
+                               CmsSwtUtils.style(closeItem, selected ? tabSelectedStyle : tabStyle);
+                               closeItem.addSelectionListener((Selected) (e) -> closeTab(section));
+                       }
+               }
+
+       }
+
+       public void view(SwtUiProvider uiProvider, Content context) {
+               if (body.isDisposed())
+                       return;
+               int index = tabIndex(context);
+               if (index >= 0) {
+                       showTab(index);
+                       previousNode = (ProvidedContent) context;
+                       previousUiProvider = uiProvider;
+                       return;
+               }
+               SwtSection section = (SwtSection) body.getChildren()[0];
+               previousNode = (ProvidedContent) section.getContent();
+               if (previousNode == null) {// empty state
+                       previousNode = (ProvidedContent) context;
+                       previousUiProvider = uiProvider;
+               } else {
+                       previousUiProvider = currentUiProvider;
+               }
+               currentUiProvider = uiProvider;
+               section.setContent(context);
+               // section.setLayoutData(CmsUiUtils.coverAll());
+               build(section, uiProvider, context);
+               if (sections.size() == 0)
+                       sections.add(section);
+               refreshTabHeaders();
+               index = tabIndex(context);
+               showTab(index);
+               layout(true, true);
+       }
+
+       public void open(SwtUiProvider uiProvider, Content context) {
+               if (singleTab)
+                       throw new UnsupportedOperationException("Open is not supported in single tab mode.");
+
+               if (previousNode != null
+                               && previousNode.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId())) {
+                       // does nothing
+                       return;
+               }
+               if (sections.size() == 0)
+                       CmsSwtUtils.clear(body);
+               SwtSection currentSection = getCurrentSection();
+               int currentIndex = sections.indexOf(currentSection);
+               SwtSection previousSection = new SwtSection(body, SWT.NONE, context);
+               build(previousSection, previousUiProvider, previousNode);
+               // previousSection.setLayoutData(CmsUiUtils.coverAll());
+               int newIndex = currentIndex + 1;
+               sections.add(currentIndex, previousSection);
+//             sections.add(newIndex, previousSection);
+               showTab(newIndex);
+               refreshTabHeaders();
+               layout(true, true);
+       }
+
+       public void showTab(int index) {
+               SwtSection sectionToShow = sections.get(index);
+               // sectionToShow.moveAbove(null);
+               stackLayout.topControl = sectionToShow;
+               refreshTabHeaders();
+               layout(true, true);
+       }
+
+       protected void build(SwtSection section, SwtUiProvider uiProvider, Content context) {
+               for (Control child : section.getChildren())
+                       child.dispose();
+               CmsSwtUtils.style(section, bodyStyle);
+               section.setContent(context);
+               uiProvider.createUiPart(section, context);
+
+       }
+
+       private int tabIndex(Content context) {
+               for (int i = 0; i < sections.size(); i++) {
+                       SwtSection section = sections.get(i);
+                       if (section.getSessionLocalId().equals(((ProvidedContent) context).getSessionLocalId()))
+                               return i;
+               }
+               return -1;
+       }
+
+       public void closeTab(SwtSection section) {
+               int currentIndex = sections.indexOf(section);
+               int nextIndex = currentIndex == 0 ? 0 : currentIndex - 1;
+               sections.remove(section);
+               section.dispose();
+               if (sections.size() == 0) {
+                       emptyState();
+                       refreshTabHeaders();
+                       layout(true, true);
+                       return;
+               }
+               refreshTabHeaders();
+               showTab(nextIndex);
+       }
+
+       public void closeAllTabs() {
+               for (SwtSection section : sections) {
+                       section.dispose();
+               }
+               sections.clear();
+               emptyState();
+               refreshTabHeaders();
+               layout(true, true);
+       }
+
+       protected void emptyState() {
+               new SwtSection(body, SWT.NONE, null);
+               refreshTabHeaders();
+       }
+
+       public Composite getCurrent() {
+               return getCurrentSection();
+       }
+
+       protected SwtSection getCurrentSection() {
+               return (SwtSection) stackLayout.topControl;
+       }
+
+       public Content getCurrentContext() {
+               SwtSection section = getCurrentSection();
+               if (section != null) {
+                       return section.getContent();
+               } else {
+                       return null;
+               }
+       }
+
+       public void setTabStyle(String tabStyle) {
+               this.tabStyle = tabStyle;
+       }
+
+       public void setTabSelectedStyle(String tabSelectedStyle) {
+               this.tabSelectedStyle = tabSelectedStyle;
+       }
+
+       public void setBodyStyle(String bodyStyle) {
+               this.bodyStyle = bodyStyle;
+       }
+
+       public void setCloseIcon(Image closeIcon) {
+               this.closeIcon = closeIcon;
+       }
+
+       public void setSingleTab(boolean singleTab) {
+               this.singleTab = singleTab;
+       }
+
+       public void setSingleTabTitle(String singleTabTitle) {
+               this.singleTabTitle = singleTabTitle;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/acr/SwtUiProvider.java
new file mode 100644 (file)
index 0000000..4988fc6
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.swt.acr;
+
+import org.argeo.api.acr.Content;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+@FunctionalInterface
+public interface SwtUiProvider {
+       Control createUiPart(Composite parent, Content context);
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/AcrContentTreeView.java
new file mode 100644 (file)
index 0000000..a3d533e
--- /dev/null
@@ -0,0 +1,166 @@
+package org.argeo.cms.swt.app;
+
+import static org.argeo.api.acr.NamespaceUtils.toPrefixedName;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.widgets.SwtTableView;
+import org.argeo.cms.swt.widgets.SwtTreeView;
+import org.argeo.cms.ux.acr.ContentHierarchicalPart;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.DefaultTabularPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.widgets.Composite;
+
+/** A simple ACR browser. */
+public class AcrContentTreeView extends Composite {
+       private static final long serialVersionUID = -3707881216246077323L;
+
+       private Content rootContent;
+
+//     private Content selected;
+
+       public AcrContentTreeView(Composite parent, int style, Content content) {
+               super(parent, style);
+               this.rootContent = content;
+               // this.selected = rootContent;
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               SashForm split = new SashForm(this, SWT.HORIZONTAL);
+               split.setLayoutData(CmsSwtUtils.fillAll());
+
+               ContentHierarchicalPart contentPart = new ContentHierarchicalPart();
+               contentPart.addColumn((model) -> {
+                       try {
+                               return NamespaceUtils.toPrefixedName(model.getName());
+                       } catch (IllegalStateException e) {
+                               return model.getName().toString();
+                       }
+               });
+               contentPart.setInput(rootContent);
+
+               new SwtTreeView<>(split, getStyle(), contentPart);
+
+               Composite area = new Composite(split, SWT.BORDER);
+               area.setLayout(CmsSwtUtils.noSpaceGridLayout(2));
+               split.setWeights(new int[] { 30, 70 });
+
+               // attributes
+               DefaultTabularPart<Content, QName> attributesPart = new DefaultTabularPart<>() {
+
+                       @Override
+                       protected List<QName> asList(Content input) {
+                               return new ArrayList<>(input.keySet());
+                       }
+               };
+
+               attributesPart.addColumn(new Column<QName>() {
+
+                       @Override
+                       public String getText(QName model) {
+                               try {
+                                       return NamespaceUtils.toPrefixedName(model);
+                               } catch (IllegalStateException e) {
+                                       return model.toString();
+                               }
+                       }
+               });
+               attributesPart.addColumn(new Column<QName>() {
+
+                       @Override
+                       public String getText(QName model) {
+                               return attributesPart.getInput().get(model).toString();
+                       }
+
+                       @Override
+                       public int getWidth() {
+                               return 400;
+                       }
+
+               });
+               // attributesPart.setInput(selected);
+
+               SwtTableView<Content, QName> attributeTable = new SwtTableView<>(area, style, attributesPart);
+               attributeTable.setLayoutData(CmsSwtUtils.fillAll());
+
+               // types
+               DefaultTabularPart<Content, QName> typesPart = new DefaultTabularPart<>() {
+
+                       @Override
+                       protected List<QName> asList(Content input) {
+                               return input.getContentClasses();
+                       }
+               };
+               typesPart.addColumn(new Column<QName>() {
+
+                       @Override
+                       public String getText(QName model) {
+                               return toPrefixedName(model);
+                       }
+
+               });
+
+               // typesPart.setInput(selected);
+
+               SwtTableView<Content, QName> typesTable = new SwtTableView<>(area, style, typesPart);
+               typesTable.setLayoutData(CmsSwtUtils.fillAll());
+
+               // controller
+               contentPart.setInput(rootContent);
+               contentPart.onSelected((o) -> {
+                       Content c = (Content) o;
+//                     selected = c;
+                       attributesPart.setInput(c);
+                       typesPart.setInput(c);
+               });
+
+               attributesPart.refresh();
+               typesPart.refresh();
+       }
+
+//     protected void refreshTable() {
+//             for (TableItem item : table.getItems()) {
+//                     item.dispose();
+//             }
+//             for (QName key : selected.keySet()) {
+//                     TableItem item = new TableItem(table, 0);
+//                     item.setText(0, key.toString());
+//                     Object value = selected.get(key);
+//                     item.setText(1, value.toString());
+//             }
+//             table.getColumn(0).pack();
+//             table.getColumn(1).pack();
+//     }
+
+//     public static void main(String[] args) {
+//             Path basePath;
+//             if (args.length > 0) {
+//                     basePath = Paths.get(args[0]);
+//             } else {
+//                     basePath = Paths.get(System.getProperty("user.home"));
+//             }
+//
+//             final Display display = new Display();
+//             final Shell shell = new Shell(display);
+//             shell.setText(basePath.toString());
+//             shell.setLayout(new FillLayout());
+//
+//             FsContentProvider contentSession = new FsContentProvider("/", basePath);
+////           GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
+//
+//             shell.setSize(shell.computeSize(800, 600));
+//             shell.open();
+//             while (!shell.isDisposed()) {
+//                     if (!display.readAndDispatch())
+//                             display.sleep();
+//             }
+//             display.dispose();
+//     }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/app/CmsUserApp.java
new file mode 100644 (file)
index 0000000..add6e9e
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.cms.swt.app;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.AbstractCmsApp;
+import org.argeo.cms.swt.CmsSwtUi;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.auth.CmsLogin;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsUserApp extends AbstractCmsApp {
+       private ContentRepository contentRepository;
+
+       @Override
+       public Set<String> getUiNames() {
+               Set<String> uiNames = new HashSet<>();
+               uiNames.add("login");
+               uiNames.add("data");
+               return uiNames;
+       }
+
+       @Override
+       public CmsUi initUi(Object uiParent) {
+               Composite parent = (Composite) uiParent;
+               String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
+               CmsSwtUi cmsUi = new CmsSwtUi(parent, SWT.NONE);
+               if ("login".equals(uiName)) {
+                       CmsView cmsView = CmsSwtUtils.getCmsView(cmsUi);
+                       CmsLogin cmsLogin = new CmsLogin(cmsView, getCmsContext());
+                       cmsLogin.createUi(cmsUi);
+
+               } else if ("data".equals(uiName)) {
+                       Content rootContent = contentRepository.get().get("/");
+                       AcrContentTreeView view = new AcrContentTreeView(cmsUi, 0, rootContent);
+                       view.setLayoutData(CmsSwtUtils.fillAll());
+
+               }
+               return cmsUi;
+       }
+
+       @Override
+       public void refreshUi(CmsUi cmsUi, String state) {
+       }
+
+       @Override
+       public void setState(CmsUi cmsUi, String state) {
+               // TODO Auto-generated method stub
+
+       }
+
+       public void setContentRepository(ContentRepository contentRepository) {
+               this.contentRepository = contentRepository;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java
new file mode 100644 (file)
index 0000000..b27c6f3
--- /dev/null
@@ -0,0 +1,339 @@
+package org.argeo.cms.swt.auth;
+
+import static org.argeo.cms.CmsMsg.password;
+import static org.argeo.cms.CmsMsg.username;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class CmsLogin implements CmsStyles, CallbackHandler {
+       private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
+
+       private Composite parent;
+       private Text usernameT, passwordT;
+       private Composite credentialsBlock;
+       private final SelectionListener loginSelectionListener;
+
+       private final Locale defaultLocale;
+       private LocaleChoice localeChoice = null;
+
+       private final CmsView cmsView;
+
+       // optional subject to be set explicitly
+       private Subject subject = null;
+
+       private CmsContext cmsContext;
+
+       public CmsLogin(CmsView cmsView, CmsContext cmsContext) {
+               this.cmsView = cmsView;
+               this.cmsContext = cmsContext;
+               if (this.cmsContext != null) {
+                       defaultLocale = this.cmsContext.getDefaultLocale();
+                       List<Locale> locales = this.cmsContext.getLocales();
+                       if (locales != null && locales.size() > 1)
+                               localeChoice = new LocaleChoice(locales, defaultLocale);
+               } else {
+                       defaultLocale = Locale.getDefault();
+               }
+               loginSelectionListener = new SelectionListener() {
+                       private static final long serialVersionUID = -8832133363830973578L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               login();
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                       }
+               };
+       }
+
+       protected boolean isAnonymous() {
+               return cmsView.isAnonymous();
+       }
+
+       public final void createUi(Composite parent) {
+               this.parent = parent;
+               createContents(parent);
+       }
+
+       protected void createContents(Composite parent) {
+               defaultCreateContents(parent);
+       }
+
+       public final void defaultCreateContents(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite credentialsBlock = createCredentialsBlock(parent);
+               if (parent instanceof Shell) {
+                       credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               }
+       }
+
+       public final Composite createCredentialsBlock(Composite parent) {
+               if (isAnonymous()) {
+                       return anonymousUi(parent);
+               } else {
+                       return userUi(parent);
+               }
+       }
+
+       public Composite getCredentialsBlock() {
+               return credentialsBlock;
+       }
+
+       protected Composite userUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+
+               specificUserUi(credentialsBlock);
+
+               Label l = new Label(credentialsBlock, SWT.NONE);
+               CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
+               l.setText(CmsMsg.logout.lead(locale));
+               GridData lData = CmsSwtUtils.fillWidth();
+               lData.widthHint = 120;
+               l.setLayoutData(lData);
+
+               l.addMouseListener(new MouseAdapter() {
+                       private static final long serialVersionUID = 6444395812777413116L;
+
+                       public void mouseDown(MouseEvent e) {
+                               logout();
+                       }
+               });
+               return credentialsBlock;
+       }
+
+       /** To be overridden */
+       protected void specificUserUi(Composite parent) {
+
+       }
+
+       protected Composite anonymousUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               // We need a composite for the traversal
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+               CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
+
+               Integer textWidth = 200;
+               if (parent instanceof Shell)
+                       CmsSwtUtils.style(parent, CMS_USER_MENU);
+               // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
+               usernameT = new Text(credentialsBlock, SWT.BORDER);
+               usernameT.setMessage(username.lead(locale));
+               CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
+               GridData gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               usernameT.setLayoutData(gd);
+
+               // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
+               passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
+               passwordT.setMessage(password.lead(locale));
+               CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
+               gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               passwordT.setLayoutData(gd);
+
+               TraverseListener tl = new TraverseListener() {
+                       private static final long serialVersionUID = -1158892811534971856L;
+
+                       public void keyTraversed(TraverseEvent e) {
+                               if (e.detail == SWT.TRAVERSE_RETURN)
+                                       login();
+                       }
+               };
+               credentialsBlock.addTraverseListener(tl);
+               usernameT.addTraverseListener(tl);
+               passwordT.addTraverseListener(tl);
+               parent.setTabList(new Control[] { credentialsBlock });
+               credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
+
+               // Button
+               Button loginButton = new Button(credentialsBlock, SWT.PUSH);
+               loginButton.setText(CmsMsg.login.lead(locale));
+               loginButton.setLayoutData(CmsSwtUtils.fillWidth());
+               loginButton.addSelectionListener(loginSelectionListener);
+
+               extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
+               if (localeChoice != null)
+                       createLocalesBlock(credentialsBlock);
+               return credentialsBlock;
+       }
+
+       /**
+        * To be overridden in order to provide custom login button and other links.
+        */
+       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
+                       SelectionListener loginSelectionListener) {
+
+       }
+
+       protected void updateLocale(Locale selectedLocale) {
+               // save already entered values
+               String usernameStr = usernameT.getText();
+               char[] pwd = passwordT.getTextChars();
+
+               for (Control child : parent.getChildren())
+                       child.dispose();
+               createContents(parent);
+               if (parent.getParent() != null)
+                       parent.getParent().layout(true, true);
+               else
+                       parent.layout();
+               usernameT.setText(usernameStr);
+               passwordT.setTextChars(pwd);
+       }
+
+       protected Composite createLocalesBlock(final Composite parent) {
+               Composite c = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
+               c.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               c.setLayoutData(CmsSwtUtils.fillAll());
+
+               SelectionListener selectionListener = new SelectionAdapter() {
+                       private static final long serialVersionUID = 4891637813567806762L;
+
+                       public void widgetSelected(SelectionEvent event) {
+                               Button button = (Button) event.widget;
+                               if (button.getSelection()) {
+                                       localeChoice.setSelectedIndex((Integer) event.widget.getData());
+                                       updateLocale(localeChoice.getSelectedLocale());
+                               }
+                       };
+               };
+
+               List<Locale> locales = localeChoice.getLocales();
+               for (Integer i = 0; i < locales.size(); i++) {
+                       Locale locale = locales.get(i);
+                       Button button = new Button(c, SWT.RADIO);
+                       CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
+                       button.setData(i);
+                       button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
+                       // button.addListener(SWT.Selection, listener);
+                       button.addSelectionListener(selectionListener);
+                       if (i == localeChoice.getSelectedIndex())
+                               button.setSelection(true);
+               }
+               return c;
+       }
+
+       protected boolean login() {
+               // TODO use CmsVie in order to retrieve subject?
+               // Subject subject = cmsView.getLoginContext().getSubject();
+               // LoginContext loginContext = cmsView.getLoginContext();
+               try {
+                       //
+                       // LOGIN
+                       //
+                       // loginContext.logout();
+                       LoginContext loginContext;
+                       if (subject == null)
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
+                       else
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
+                       loginContext.login();
+                       cmsView.authChange(loginContext);
+                       cmsContext.getCmsEventBus().sendEvent("cms", Collections.singletonMap("msg", "New login"));
+                       return true;
+               } catch (LoginException e) {
+                       if (log.isTraceEnabled())
+                               log.warn("Login failed: " + e.getMessage(), e);
+                       else
+                               log.warn("Login failed: " + e.getMessage());
+
+                       try {
+                               Thread.sleep(3000);
+                       } catch (InterruptedException e2) {
+                               // silent
+                       }
+                       // ErrorFeedback.show("Login failed", e);
+                       return false;
+               }
+               // catch (LoginException e) {
+               // log.error("Cannot login", e);
+               // return false;
+               // }
+       }
+
+       protected void logout() {
+               cmsView.logout();
+               cmsView.navigateTo("~");
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks) {
+                       if (callback instanceof NameCallback && usernameT != null)
+                               ((NameCallback) callback).setName(usernameT.getText());
+                       else if (callback instanceof PasswordCallback && passwordT != null)
+                               ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
+                       else if (callback instanceof RemoteAuthCallback) {
+                               ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+                               ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+                       } else if (callback instanceof LanguageCallback) {
+                               Locale toUse = null;
+                               if (localeChoice != null)
+                                       toUse = localeChoice.getSelectedLocale();
+                               else if (defaultLocale != null)
+                                       toUse = defaultLocale;
+
+                               if (toUse != null) {
+                                       ((LanguageCallback) callback).setLocale(toUse);
+                                       UiContext.setLocale(toUse);
+                               }
+
+                       }
+               }
+       }
+
+       public void setSubject(Subject subject) {
+               this.subject = subject;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java
new file mode 100644 (file)
index 0000000..39cf82a
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.swt.auth;
+
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class CmsLoginShell extends CmsLogin {
+       private final Shell shell;
+
+       public CmsLoginShell(CmsView cmsView, CmsContext cmsContext) {
+               super(cmsView, cmsContext);
+               shell = createShell();
+//             createUi(shell);
+       }
+
+       /** To be overridden. */
+       protected Shell createShell() {
+               Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
+               shell.setMaximized(true);
+               return shell;
+       }
+
+       /** To be overridden. */
+       public void open() {
+               CmsSwtUtils.style(shell, CMS_USER_MENU);
+               shell.open();
+       }
+
+       @Override
+       protected boolean login() {
+               boolean success = false;
+               try {
+                       success = super.login();
+                       return success;
+               } finally {
+                       if (success)
+                               closeShell();
+                       else {
+                               for (Control child : shell.getChildren())
+                                       child.dispose();
+                               createUi(shell);
+                               shell.layout();
+                               // TODO error message
+                       }
+               }
+       }
+
+       @Override
+       protected void logout() {
+               closeShell();
+               super.logout();
+       }
+
+       protected void closeShell() {
+               if (!shell.isDisposed()) {
+                       shell.close();
+                       shell.dispose();
+               }
+       }
+
+       public Shell getShell() {
+               return shell;
+       }
+
+       public void createUi() {
+               createUi(shell);
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java
new file mode 100644 (file)
index 0000000..495007c
--- /dev/null
@@ -0,0 +1,273 @@
+package org.argeo.cms.swt.auth;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * A composite that can populate itself based on {@link Callback}s. It can be
+ * used directly as a {@link CallbackHandler} or be used by one by calling the
+ * {@link #createCallbackHandlers(Callback[])}. Supported standard
+ * {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link PasswordCallback}</li>
+ * <li>{@link NameCallback}</li>
+ * <li>{@link TextOutputCallback}</li>
+ * </ul>
+ * Supported Argeo {@link Callback}s are:<br>
+ * <ul>
+ * <li>{@link LocaleChoice}</li>
+ * </ul>
+ */
+public class CompositeCallbackHandler extends Composite implements CallbackHandler {
+       private static final long serialVersionUID = -928223893722723777L;
+
+       private boolean wasUsedAlready = false;
+       private boolean isSubmitted = false;
+       private boolean isCanceled = false;
+
+       public CompositeCallbackHandler(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               // reset
+               if (wasUsedAlready && !isSubmitted() && !isCanceled()) {
+                       cancel();
+                       for (Control control : getChildren())
+                               control.dispose();
+                       isSubmitted = false;
+                       isCanceled = false;
+               }
+
+               for (Callback callback : callbacks)
+                       checkCallbackSupported(callback);
+               // create controls synchronously in the UI thread
+               getDisplay().syncExec(new Runnable() {
+
+                       @Override
+                       public void run() {
+                               createCallbackHandlers(callbacks);
+                       }
+               });
+
+               if (!wasUsedAlready)
+                       wasUsedAlready = true;
+
+               // while (!isSubmitted() && !isCanceled()) {
+               // try {
+               // wait(1000l);
+               // } catch (InterruptedException e) {
+               // // silent
+               // }
+               // }
+
+               // cleanCallbacksAfterCancel(callbacks);
+       }
+
+       public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException {
+               if (callback instanceof TextOutputCallback || callback instanceof NameCallback
+                               || callback instanceof PasswordCallback || callback instanceof LocaleChoice) {
+                       return;
+               } else {
+                       throw new UnsupportedCallbackException(callback);
+               }
+       }
+
+       /**
+        * Set writable callbacks to null if the handle is canceled (check is done
+        * by the method)
+        */
+       public void cleanCallbacksAfterCancel(Callback[] callbacks) {
+               if (isCanceled()) {
+                       for (Callback callback : callbacks) {
+                               if (callback instanceof NameCallback) {
+                                       ((NameCallback) callback).setName(null);
+                               } else if (callback instanceof PasswordCallback) {
+                                       PasswordCallback pCallback = (PasswordCallback) callback;
+                                       char[] arr = pCallback.getPassword();
+                                       if (arr != null) {
+                                               Arrays.fill(arr, '*');
+                                               pCallback.setPassword(null);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       public void createCallbackHandlers(Callback[] callbacks) {
+               Composite composite = this;
+               for (int i = 0; i < callbacks.length; i++) {
+                       Callback callback = callbacks[i];
+                       if (callback instanceof TextOutputCallback) {
+                               createLabelTextoutputHandler(composite, (TextOutputCallback) callback);
+                       } else if (callback instanceof NameCallback) {
+                               createNameHandler(composite, (NameCallback) callback);
+                       } else if (callback instanceof PasswordCallback) {
+                               createPasswordHandler(composite, (PasswordCallback) callback);
+                       } else if (callback instanceof LocaleChoice) {
+                               createLocaleHandler(composite, (LocaleChoice) callback);
+                       }
+               }
+       }
+
+       protected Text createNameHandler(Composite composite, final NameCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getPrompt());
+               final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
+               if (callback.getDefaultName() != null) {
+                       // set default value, if provided
+                       text.setText(callback.getDefaultName());
+                       callback.setName(callback.getDefaultName());
+               }
+               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               text.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 7300032545287292973L;
+
+                       public void modifyText(ModifyEvent event) {
+                               callback.setName(text.getText());
+                       }
+               });
+               text.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 1820530045857665111L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               submit();
+                       }
+               });
+
+               text.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = -8698107785092095713L;
+
+                       @Override
+                       public void keyReleased(KeyEvent e) {
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {
+                       }
+               });
+               return text;
+       }
+
+       protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getPrompt());
+               final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER);
+               passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               passwordText.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = -7099363995047686732L;
+
+                       public void modifyText(ModifyEvent event) {
+                               callback.setPassword(passwordText.getTextChars());
+                       }
+               });
+               passwordText.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 1820530045857665111L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               submit();
+                       }
+               });
+               return passwordText;
+       }
+
+       protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) {
+               String[] labels = callback.getSupportedLocalesLabels();
+               if (labels.length == 0)
+                       return null;
+               Label label = new Label(composite, SWT.NONE);
+               label.setText("Language");
+
+               final Combo combo = new Combo(composite, SWT.READ_ONLY);
+               combo.setItems(labels);
+               combo.select(callback.getDefaultIndex());
+               combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               combo.addSelectionListener(new SelectionListener() {
+                       private static final long serialVersionUID = 38678989091946277L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               callback.setSelectedIndex(combo.getSelectionIndex());
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                       }
+               });
+               return combo;
+       }
+
+       protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) {
+               Label label = new Label(composite, SWT.NONE);
+               label.setText(callback.getMessage());
+               GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
+               data.horizontalSpan = 2;
+               label.setLayoutData(data);
+               return label;
+               // TODO: find a way to pass this information
+               // int messageType = callback.getMessageType();
+               // int dialogMessageType = IMessageProvider.NONE;
+               // switch (messageType) {
+               // case TextOutputCallback.INFORMATION:
+               // dialogMessageType = IMessageProvider.INFORMATION;
+               // break;
+               // case TextOutputCallback.WARNING:
+               // dialogMessageType = IMessageProvider.WARNING;
+               // break;
+               // case TextOutputCallback.ERROR:
+               // dialogMessageType = IMessageProvider.ERROR;
+               // break;
+               // }
+               // setMessage(callback.getMessage(), dialogMessageType);
+       }
+
+       synchronized boolean isSubmitted() {
+               return isSubmitted;
+       }
+
+       synchronized boolean isCanceled() {
+               return isCanceled;
+       }
+
+       protected synchronized void submit() {
+               isSubmitted = true;
+               notifyAll();
+       }
+
+       protected synchronized void cancel() {
+               isCanceled = true;
+               notifyAll();
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java
new file mode 100644 (file)
index 0000000..37d88f5
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.swt.auth;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class DynamicCallbackHandler implements CallbackHandler {
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               Shell activeShell = Display.getCurrent().getActiveShell();
+               LightweightDialog dialog = new LightweightDialog(activeShell) {
+
+                       @Override
+                       protected Control createDialogArea(Composite parent) {
+                               CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE);
+                               cch.createCallbackHandlers(callbacks);
+                               return cch;
+                       }
+               };
+               dialog.setBlockOnOpen(true);
+               dialog.open();
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java
new file mode 100644 (file)
index 0000000..3ce5ae5
--- /dev/null
@@ -0,0 +1,86 @@
+package org.argeo.cms.swt.auth;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.callback.LanguageCallback;
+
+import org.argeo.cms.swt.CmsException;
+
+/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */
+@Deprecated
+public class LocaleChoice {
+       private final List<Locale> locales;
+
+       private Integer selectedIndex = null;
+       private final Integer defaultIndex;
+
+       public LocaleChoice(List<Locale> locales, Locale defaultLocale) {
+               Integer defaultIndex = null;
+               this.locales = Collections.unmodifiableList(locales);
+               for (int i = 0; i < locales.size(); i++)
+                       if (locales.get(i).equals(defaultLocale))
+                               defaultIndex = i;
+
+               // based on language only
+               if (defaultIndex == null)
+                       for (int i = 0; i < locales.size(); i++)
+                               if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage()))
+                                       defaultIndex = i;
+
+               if (defaultIndex == null)
+                       throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales);
+               this.defaultIndex = defaultIndex;
+
+               this.selectedIndex = defaultIndex;
+       }
+
+//     /**
+//      * Convenience constructor based on a comma separated list of iso codes (en,
+//      * en_US, fr_CA, etc.). Default selection is default locale.
+//      */
+//     public LocaleChoice(String locales, Locale defaultLocale) {
+//             this(LocaleUtils.asLocaleList(locales), defaultLocale);
+//     }
+
+       public String[] getSupportedLocalesLabels() {
+               String[] labels = new String[locales.size()];
+               for (int i = 0; i < locales.size(); i++) {
+                       Locale locale = locales.get(i);
+                       if (locale.getCountry().equals(""))
+                               labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]";
+                       else
+                               labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") ["
+                                               + locale.getLanguage() + "_" + locale.getCountry() + "]";
+
+               }
+               return labels;
+       }
+
+       public Locale getSelectedLocale() {
+               if (selectedIndex == null)
+                       return null;
+               return locales.get(selectedIndex);
+       }
+
+       public void setSelectedIndex(Integer selectedIndex) {
+               this.selectedIndex = selectedIndex;
+       }
+
+       public Integer getSelectedIndex() {
+               return selectedIndex;
+       }
+
+       public Integer getDefaultIndex() {
+               return defaultIndex;
+       }
+
+       public List<Locale> getLocales() {
+               return locales;
+       }
+
+       public Locale getDefaultLocale() {
+               return locales.get(getDefaultIndex());
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java
new file mode 100644 (file)
index 0000000..b431423
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS authentication widgets, based on SWT. */
+package org.argeo.cms.swt.auth;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java
new file mode 100644 (file)
index 0000000..abb8227
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.cms.swt.dialogs;
+
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.directory.CmsUserManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** Dialog to change a password. */
+public class ChangePasswordDialog extends CmsMessageDialog {
+       private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class);
+
+       private CmsUserManager cmsUserManager;
+       private CmsView cmsView;
+
+       private Callable<Integer> doIt;
+
+       public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) {
+               super(parentShell, message, kind);
+               this.cmsUserManager = cmsUserManager;
+               cmsView = CmsSwtUtils.getCmsView(parentShell);
+       }
+
+       @Override
+       protected Control createInputArea(Composite userSection) {
+               addFormLabel(userSection, CmsMsg.currentPassword.lead());
+               Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+               previousPassword.setLayoutData(CmsSwtUtils.fillWidth());
+               addFormLabel(userSection, CmsMsg.newPassword.lead());
+               Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+               newPassword.setLayoutData(CmsSwtUtils.fillWidth());
+               addFormLabel(userSection, CmsMsg.repeatNewPassword.lead());
+               Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
+               confirmPassword.setLayoutData(CmsSwtUtils.fillWidth());
+
+               doIt = () -> {
+                       if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) {
+                               try {
+                                       cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars());
+                                       return CmsDialog.OK;
+                               } catch (Exception e1) {
+                                       log.error("Could not change password", e1);
+                                       cancel();
+                                       CmsMessageDialog.openError(CmsMsg.invalidPassword.lead());
+                                       return CmsDialog.CANCEL;
+                               }
+                       } else {
+                               cancel();
+                               CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead());
+                               return CmsDialog.CANCEL;
+                       }
+               };
+
+               pack();
+               return previousPassword;
+       }
+
+       @Override
+       protected void okPressed() {
+               Integer returnCode = cmsView.doAs(doIt);
+               if (returnCode.equals(CmsDialog.OK)) {
+                       super.okPressed();
+                       CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead());
+               }
+       }
+
+       private static Label addFormLabel(Composite parent, String label) {
+               Label lbl = new Label(parent, SWT.WRAP);
+               lbl.setText(label);
+//             CmsUiUtils.style(lbl, SuiteStyle.simpleLabel);
+               return lbl;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java
new file mode 100644 (file)
index 0000000..2fed951
--- /dev/null
@@ -0,0 +1,127 @@
+package org.argeo.cms.swt.dialogs;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A dialog feedback based on a {@link LightweightDialog}. */
+public class CmsFeedback extends LightweightDialog {
+       private final static CmsLog log = CmsLog.getLog(CmsFeedback.class);
+
+       private String message;
+       private Throwable exception;
+
+       private Text stack;
+
+       private CmsFeedback(Shell parentShell, String message, Throwable e) {
+               super(parentShell);
+               this.message = message;
+               this.exception = e;
+       }
+
+       public static CmsFeedback error(String message, Throwable e) {
+               // rethrow ThreaDeath in order to make sure that RAP will properly clean
+               // up the UI thread
+               if (e instanceof ThreadDeath)
+                       throw (ThreadDeath) e;
+
+               log.error(message, e);
+               try {
+                       Display display = LightweightDialog.findDisplay();
+
+                       CmsFeedback current = (CmsFeedback) display.getData(CmsFeedback.class.getName());
+                       if (current != null) {// already one open
+                               current.append("");
+                               if (message != null)
+                                       current.append(message);
+                               if (e != null)
+                                       current.append(e);
+                               // FIXME set a limit to the size of the text
+                               return current;
+                       }
+
+                       CmsFeedback cmsFeedback = new CmsFeedback(null, message, e);
+                       cmsFeedback.setBlockOnOpen(false);
+                       cmsFeedback.open();
+                       cmsFeedback.getDisplay().setData(CmsFeedback.class.getName(), cmsFeedback);
+                       return cmsFeedback;
+               } catch (Throwable e1) {
+                       log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e);
+                       return null;
+               }
+       }
+
+       public static CmsFeedback show(String message) {
+               CmsFeedback cmsFeedback = new CmsFeedback(null, message, null);
+               cmsFeedback.open();
+               return cmsFeedback;
+       }
+
+       protected Control createDialogArea(Composite parent) {
+               parent.setLayout(new GridLayout(2, false));
+
+               Label messageLbl = new Label(parent, SWT.WRAP);
+               messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               if (message != null)
+                       messageLbl.setText(message);
+               else if (exception != null)
+                       messageLbl.setText(exception.getLocalizedMessage());
+
+               Button close = new Button(parent, SWT.FLAT);
+               close.setText(CmsMsg.close.lead());
+               close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
+               close.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.OK));
+
+               if (exception != null) {
+                       stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
+                       stack.setEditable(false);
+                       stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+                       append(exception);
+               }
+               return messageLbl;
+       }
+
+       protected Point getInitialSize() {
+               if (exception != null)
+                       return new Point(800, 600);
+               else
+                       return new Point(600, 400);
+       }
+
+       protected void append(String message) {
+               stack.append(message);
+               stack.append("\n");
+       }
+
+       protected void append(Throwable exception) {
+               try (StringWriter sw = new StringWriter()) {
+                       exception.printStackTrace(new PrintWriter(sw));
+                       stack.append(sw.toString());
+               } catch (IOException e) {
+                       // ignore
+               }
+
+       }
+
+       @Override
+       protected void onClose() {
+               getDisplay().setData(CmsFeedback.class.getName(), null);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java
new file mode 100644 (file)
index 0000000..2130882
--- /dev/null
@@ -0,0 +1,168 @@
+package org.argeo.cms.swt.dialogs;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** Base class for dialogs displaying messages or small forms. */
+public class CmsMessageDialog extends LightweightDialog {
+       public final static int NONE = 0;
+       public final static int ERROR = 1;
+       public final static int INFORMATION = 2;
+       public final static int QUESTION = 3;
+       public final static int WARNING = 4;
+       public final static int CONFIRM = 5;
+       public final static int QUESTION_WITH_CANCEL = 6;
+
+       private int kind;
+       private String message;
+
+       public CmsMessageDialog(Shell parentShell, String message, int kind) {
+               super(parentShell);
+               this.kind = kind;
+               this.message = message;
+       }
+
+       protected Control createDialogArea(Composite parent) {
+               parent.setLayout(new GridLayout());
+
+               TraverseListener traverseListener = new TraverseListener() {
+                       private static final long serialVersionUID = -1158892811534971856L;
+
+                       public void keyTraversed(TraverseEvent e) {
+                               if (e.detail == SWT.TRAVERSE_RETURN)
+                                       okPressed();
+                               else if (e.detail == SWT.TRAVERSE_ESCAPE)
+                                       cancelPressed();
+                       }
+               };
+
+               // message
+               Composite body = new Composite(parent, SWT.NONE);
+               body.addTraverseListener(traverseListener);
+               GridLayout bodyGridLayout = new GridLayout();
+               bodyGridLayout.marginHeight = 20;
+               bodyGridLayout.marginWidth = 20;
+               body.setLayout(bodyGridLayout);
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+               if (message != null) {
+                       Label messageLbl = new Label(body, SWT.WRAP);
+                       CmsSwtUtils.markup(messageLbl);
+                       messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+                       messageLbl.setFont(EclipseUiUtils.getBoldFont(parent));
+                       messageLbl.setText(message);
+               }
+
+               // buttons
+               Composite buttons = new Composite(parent, SWT.NONE);
+               buttons.addTraverseListener(traverseListener);
+               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) {
+                       GridLayout layout = new GridLayout(1, true);
+                       layout.marginWidth = 0;
+                       layout.marginHeight = 0;
+                       buttons.setLayout(layout);
+
+                       Button close = new Button(buttons, SWT.FLAT);
+                       close.setText(CmsMsg.close.lead());
+                       close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       close.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.OK));
+                       close.setFocus();
+                       close.addTraverseListener(traverseListener);
+
+                       buttons.setTabList(new Control[] { close });
+               } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) {
+                       Control input = createInputArea(body);
+                       if (input != null) {
+                               input.addTraverseListener(traverseListener);
+                               body.setTabList(new Control[] { input });
+                       }
+                       GridLayout layout = new GridLayout(2, true);
+                       layout.marginWidth = 0;
+                       layout.marginHeight = 0;
+                       buttons.setLayout(layout);
+
+                       Button cancel = new Button(buttons, SWT.FLAT);
+                       cancel.setText(CmsMsg.cancel.lead());
+                       cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       cancel.addSelectionListener((Selected) (e) -> cancelPressed());
+                       cancel.addTraverseListener(traverseListener);
+
+                       Button ok = new Button(buttons, SWT.FLAT);
+                       ok.setText(CmsMsg.ok.lead());
+                       ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       ok.addSelectionListener((Selected) (e) -> okPressed());
+                       ok.addTraverseListener(traverseListener);
+                       if (input == null)
+                               ok.setFocus();
+                       else
+                               input.setFocus();
+
+                       buttons.setTabList(new Control[] { ok, cancel });
+               }
+               // pack();
+               parent.setTabList(new Control[] { body, buttons });
+               return body;
+       }
+
+       protected Control createInputArea(Composite parent) {
+               return null;
+       }
+
+       protected void okPressed() {
+               closeShell(CmsDialog.OK);
+       }
+
+       protected void cancelPressed() {
+               closeShell(CmsDialog.CANCEL);
+       }
+
+       protected void cancel() {
+               closeShell(CmsDialog.CANCEL);
+       }
+
+       protected Point getInitialSize() {
+               return new Point(400, 200);
+       }
+
+       public static boolean open(int kind, Shell parent, String message) {
+               CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind);
+               return dialog.open() == 0;
+       }
+
+       public static boolean openConfirm(String message) {
+               return open(CONFIRM, Display.getCurrent().getActiveShell(), message);
+       }
+
+       public static void openInformation(String message) {
+               open(INFORMATION, Display.getCurrent().getActiveShell(), message);
+       }
+
+       public static boolean openQuestion(String message) {
+               return open(QUESTION, Display.getCurrent().getActiveShell(), message);
+       }
+
+       public static void openWarning(String message) {
+               open(WARNING, Display.getCurrent().getActiveShell(), message);
+       }
+
+       public static void openError(String message) {
+               open(ERROR, Display.getCurrent().getActiveShell(), message);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java
new file mode 100644 (file)
index 0000000..3aec22a
--- /dev/null
@@ -0,0 +1,260 @@
+package org.argeo.cms.swt.dialogs;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Generic lightweight dialog, not based on JFace. */
+public class LightweightDialog {
+       private final static CmsLog log = CmsLog.getLog(LightweightDialog.class);
+
+       private Shell parentShell;
+       private Shell backgroundShell;
+       private Shell foregoundShell;
+
+       private Display display;
+
+       private Integer returnCode = null;
+       private boolean block = true;
+
+       private String title;
+
+       /** Tries to find a display */
+       static Display findDisplay() {
+               try {
+                       Display display = Display.getCurrent();
+                       if (display != null)
+                               return display;
+                       else
+                               return Display.getDefault();
+               } catch (Exception e) {
+                       return Display.getCurrent();
+               }
+       }
+
+       public LightweightDialog(Shell parentShell) {
+               this.parentShell = parentShell;
+       }
+
+       public int open() {
+               display = findDisplay();
+               if (foregoundShell != null)
+                       throw new EclipseUiException("There is already a shell");
+               backgroundShell = new Shell(parentShell, SWT.ON_TOP);
+               backgroundShell.setFullScreen(true);
+               // if (parentShell != null) {
+               // backgroundShell.setBounds(parentShell.getBounds());
+               // } else
+               // backgroundShell.setMaximized(true);
+               backgroundShell.setAlpha(128);
+               backgroundShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
+               foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP);
+               if (title != null)
+                       setTitle(title);
+               foregoundShell.setLayout(new GridLayout());
+               foregoundShell.setSize(getInitialSize());
+               createDialogArea(foregoundShell);
+               // shell.pack();
+               // shell.layout();
+
+               Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : display.getBounds();// RAP
+               Point dialogSize = foregoundShell.getSize();
+               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
+               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
+               foregoundShell.setLocation(x, y);
+
+               foregoundShell.addShellListener(new ShellAdapter() {
+                       private static final long serialVersionUID = -2701270481953688763L;
+
+                       @Override
+                       public void shellDeactivated(ShellEvent e) {
+                               if (hasChildShells())
+                                       return;
+                               if (returnCode == null)// not yet closed
+                                       closeShell(CmsDialog.CANCEL);
+                       }
+
+                       @Override
+                       public void shellClosed(ShellEvent e) {
+                               notifyClose();
+                       }
+
+               });
+
+               backgroundShell.open();
+               foregoundShell.open();
+               // after the foreground shell has been opened
+               backgroundShell.addFocusListener(new FocusListener() {
+                       private static final long serialVersionUID = 3137408447474661070L;
+
+                       @Override
+                       public void focusLost(FocusEvent event) {
+                       }
+
+                       @Override
+                       public void focusGained(FocusEvent event) {
+                               if (hasChildShells())
+                                       return;
+                               if (returnCode == null)// not yet closed
+                                       closeShell(CmsDialog.CANCEL);
+                       }
+               });
+               backgroundShell.addDisposeListener((event) -> onClose());
+
+               if (block) {
+                       block();
+               }
+               if (returnCode == null)
+                       returnCode = CmsDialog.OK;
+               return returnCode;
+       }
+
+       public void block() {
+               try {
+                       runEventLoop(foregoundShell);
+               } catch (ThreadDeath t) {
+                       returnCode = CmsDialog.CANCEL;
+                       if (log.isTraceEnabled())
+                               log.error("Thread death, canceling dialog", t);
+               } catch (Throwable t) {
+                       returnCode = CmsDialog.CANCEL;
+                       log.error("Cannot open blocking lightweight dialog", t);
+               }
+       }
+
+       private boolean hasChildShells() {
+               if (foregoundShell == null)
+                       return false;
+               return foregoundShell.getShells().length != 0;
+       }
+
+       protected void onClose() {
+
+       }
+
+       // public synchronized int openAndWait() {
+       // open();
+       // while (returnCode == null)
+       // try {
+       // wait(100);
+       // } catch (InterruptedException e) {
+       // // silent
+       // }
+       // return returnCode;
+       // }
+
+       private synchronized void notifyClose() {
+               if (returnCode == null)
+                       returnCode = CmsDialog.CANCEL;
+               notifyAll();
+       }
+
+       protected void closeShell(int returnCode) {
+               this.returnCode = returnCode;
+               if (CmsDialog.CANCEL == returnCode)
+                       onCancel();
+               if (foregoundShell != null && !foregoundShell.isDisposed()) {
+                       foregoundShell.close();
+                       foregoundShell.dispose();
+                       foregoundShell = null;
+               }
+
+               if (backgroundShell != null && !backgroundShell.isDisposed()) {
+                       backgroundShell.close();
+                       backgroundShell.dispose();
+               }
+       }
+
+       protected Point getInitialSize() {
+               return new Point(600, 400);
+       }
+
+       protected Control createDialogArea(Composite parent) {
+               Composite dialogarea = new Composite(parent, SWT.NONE);
+               dialogarea.setLayout(new GridLayout());
+               dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               return dialogarea;
+       }
+
+       protected Shell getBackgroundShell() {
+               return backgroundShell;
+       }
+
+       protected Shell getForegoundShell() {
+               return foregoundShell;
+       }
+
+       public void setBlockOnOpen(boolean shouldBlock) {
+               block = shouldBlock;
+       }
+
+       public void pack() {
+               foregoundShell.pack();
+       }
+
+       private void runEventLoop(Shell loopShell) {
+               Display display;
+               if (foregoundShell == null) {
+                       display = Display.getCurrent();
+               } else {
+                       display = loopShell.getDisplay();
+               }
+
+               while (loopShell != null && !loopShell.isDisposed()) {
+                       try {
+                               if (!display.readAndDispatch()) {
+                                       display.sleep();
+                               }
+                       } catch (UnsupportedOperationException e) {
+                               throw e;
+                       } catch (Throwable e) {
+                               handleException(e);
+                       }
+               }
+               if (!display.isDisposed())
+                       display.update();
+       }
+
+       protected void handleException(Throwable t) {
+               if (t instanceof ThreadDeath) {
+                       // Don't catch ThreadDeath as this is a normal occurrence when
+                       // the thread dies
+                       throw (ThreadDeath) t;
+               }
+               // Try to keep running.
+               t.printStackTrace();
+       }
+
+       /** @return false, if the dialog should not be closed. */
+       protected boolean onCancel() {
+               return true;
+       }
+
+       public void setTitle(String title) {
+               this.title = title;
+               if (title != null && getForegoundShell() != null)
+                       getForegoundShell().setText(title);
+       }
+
+       public Integer getReturnCode() {
+               return returnCode;
+       }
+
+       Display getDisplay() {
+               return display;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java
new file mode 100644 (file)
index 0000000..9404b81
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.swt.dialogs;
+
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A dialog asking a for a single value. */
+public class SingleValueDialog extends CmsMessageDialog {
+       private Text valueT;
+       private String value;
+       private String defaultValue;
+
+       public SingleValueDialog(Shell parentShell, String message) {
+               super(parentShell, message, QUESTION);
+       }
+
+       public SingleValueDialog(Shell parentShell, String message, String defaultValue) {
+               super(parentShell, message, QUESTION);
+               this.defaultValue = defaultValue;
+       }
+
+       @Override
+       protected Control createInputArea(Composite parent) {
+               valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE);
+               valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
+               if (defaultValue != null)
+                       valueT.setText(defaultValue);
+               return valueT;
+       }
+
+       @Override
+       protected void okPressed() {
+               value = valueT.getText();
+               super.okPressed();
+       }
+
+       public String getString() {
+               return value;
+       }
+
+       public Long getLong() {
+               return Long.valueOf(getString());
+       }
+
+       public Double getDouble() {
+               return Double.valueOf(getString());
+       }
+
+       public static String ask(String message) {
+               return ask(message, null);
+       }
+
+       public static String ask(String message, String defaultValue) {
+               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue);
+               if (svd.open() == Window.OK)
+                       return svd.getString();
+               else
+                       return null;
+       }
+
+       public static Long askLong(String message) {
+               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
+               if (svd.open() == Window.OK)
+                       return svd.getLong();
+               else
+                       return null;
+       }
+
+       public static Double askDouble(String message) {
+               SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message);
+               if (svd.open() == Window.OK)
+                       return svd.getDouble();
+               else
+                       return null;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java
new file mode 100644 (file)
index 0000000..ac76dba
--- /dev/null
@@ -0,0 +1,2 @@
+/** SWT/JFace dialogs. */
+package org.argeo.cms.swt.dialogs;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java
new file mode 100644 (file)
index 0000000..b3fec78
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.cms.swt.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.osgi.BundleCmsTheme;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+
+/** Centralises some generic {@link CmsSwtTheme} patterns. */
+public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme {
+       private Map<String, ImageData> imageCache = new HashMap<>();
+
+       private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
+
+       protected Image getImage(String path) {
+               if (!imageCache.containsKey(path)) {
+                       try (InputStream in = getResourceAsStream(path)) {
+                               if (in == null)
+                                       return null;
+                               ImageData imageData = new ImageData(in);
+                               imageCache.put(path, imageData);
+                       } catch (IOException e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+               ImageData imageData = imageCache.get(path);
+               Image image = new Image(Display.getCurrent(), imageData);
+               return image;
+       }
+
+       /**
+        * And icon with this file name (without the extension), with a best effort to
+        * find the appropriate size, or <code>null</code> if not found.
+        * 
+        * @param name          An icon file name without path and extension.
+        * @param preferredSize the preferred size, if <code>null</code>,
+        *                      {@link #getSmallIconSize()} will be tried.
+        */
+       public Image getIcon(String name, Integer preferredSize) {
+               if (preferredSize == null)
+                       preferredSize = getSmallIconSize();
+               Map<Integer, String> subCache;
+               if (!iconPaths.containsKey(name))
+                       subCache = new HashMap<>();
+               else
+                       subCache = iconPaths.get(name);
+               Image image = null;
+               if (!subCache.containsKey(preferredSize)) {
+                       Image bestMatchSoFar = null;
+                       paths: for (String p : getImagesPaths()) {
+                               int lastSlash = p.lastIndexOf('/');
+                               String fileName = p;
+                               String ext = "";
+                               if (lastSlash >= 0)
+                                       fileName = p.substring(lastSlash + 1);
+                               int lastDot = fileName.lastIndexOf('.');
+                               if (lastDot >= 0) {
+                                       ext = fileName.substring(lastDot + 1);
+                                       fileName = fileName.substring(0, lastDot);
+                               }
+
+                               if ("svg".equals(ext))
+                                       continue paths;
+
+                               if (fileName.equals(name)) {// matched
+                                       Image img = getImage(p);
+                                       int width = img.getBounds().width;
+                                       if (width == preferredSize) {// perfect match
+                                               subCache.put(preferredSize, p);
+                                               image = img;
+                                               break paths;
+                                       }
+                                       if (bestMatchSoFar == null) {
+                                               bestMatchSoFar = img;
+                                       } else {
+                                               if (Math.abs(width - preferredSize) < Math
+                                                               .abs(bestMatchSoFar.getBounds().width - preferredSize))
+                                                       bestMatchSoFar = img;
+                                       }
+                               }
+                       }
+
+                       if (image == null)
+                               image = bestMatchSoFar;
+               } else {
+                       image = getImage(subCache.get(preferredSize));
+               }
+
+               if (image != null && !iconPaths.containsKey(name))
+                       iconPaths.put(name, subCache);
+
+               return image;
+       }
+
+       @Override
+       public Image getSmallIcon(CmsIcon icon) {
+               return getIcon(icon.name(), getSmallIconSize());
+       }
+
+       @Override
+       public Image getBigIcon(CmsIcon icon) {
+               return getIcon(icon.name(), getBigIconSize());
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleSvgTheme.java
new file mode 100644 (file)
index 0000000..1e52001
--- /dev/null
@@ -0,0 +1,107 @@
+package org.argeo.cms.swt.osgi;
+
+import java.awt.Color;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.ImageTranscoder;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+
+/** Theme which can dynamically create icons from SVG data. */
+public class BundleSvgTheme extends BundleCmsSwtTheme {
+       private final static Logger logger = System.getLogger(BundleSvgTheme.class.getName());
+
+       private Map<String, Map<Integer, Image>> imageCache = Collections.synchronizedMap(new HashMap<>());
+
+       private Map<Integer, ImageTranscoder> transcoders = Collections.synchronizedMap(new HashMap<>());
+
+       @Override
+       public Image getIcon(String name, Integer preferredSize) {
+               String path = "icons/types/svg/" + name + ".svg";
+               return createImageFromSvg(path, preferredSize);
+       }
+
+       protected Image createImageFromSvg(String path, Integer preferredSize) {
+               Image image = null;
+               if (imageCache.containsKey(path)) {
+                       image = imageCache.get(path).get(preferredSize);
+               }
+               if (image != null)
+                       return image;
+               ImageData imageData = loadFromSvg(path, preferredSize);
+               image = new Image(Display.getDefault(), imageData);
+               if (!imageCache.containsKey(path))
+                       imageCache.put(path, Collections.synchronizedMap(new HashMap<>()));
+               imageCache.get(path).put(preferredSize, image);
+               return image;
+       }
+
+       protected ImageData loadFromSvg(String path, int size) {
+               ImageTranscoder transcoder = null;
+               synchronized (this) {
+                       transcoder = transcoders.get(size);
+                       if (transcoder == null) {
+                               transcoder = new PNGTranscoder();
+                               transcoder.addTranscodingHint(PNGTranscoder.KEY_WIDTH, (float) size);
+                               transcoder.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, (float) size);
+                               transcoder.addTranscodingHint(PNGTranscoder.KEY_BACKGROUND_COLOR, new Color(255, 255, 255, 0));
+                               transcoders.put(size, transcoder);
+                       }
+               }
+               ImageData imageData;
+               try (InputStream in = getResourceAsStream(path); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                       if (in == null)
+                               throw new IllegalArgumentException(path + " not found");
+                       TranscoderInput input = new TranscoderInput(in);
+                       TranscoderOutput output = new TranscoderOutput(out);
+                       transcoder.transcode(input, output);
+                       try (InputStream imageIn = new ByteArrayInputStream(out.toByteArray())) {
+                               imageData = new ImageData(imageIn);
+                       }
+                       logger.log(Level.DEBUG, () -> "Generated " + size + "x" + size + " PNG icon from " + path);
+               } catch (IOException | TranscoderException e) {
+                       throw new RuntimeException("Cannot transcode SVG " + path, e);
+               }
+
+               return imageData;
+       }
+
+       @Override
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               super.init(bundleContext, properties);
+
+               // preload all icons
+//             paths: for (String p : getImagesPaths()) {
+//                     if (!p.endsWith(".svg"))
+//                             continue paths;
+//                     createImageFromSvg(p, getDefaultIconSize());
+//             }
+       }
+
+       @Override
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+               Display display = Display.getDefault();
+               if (display != null)
+                       for (String path : imageCache.keySet()) {
+                               for (Image image : imageCache.get(path).values()) {
+                                       display.syncExec(() -> image.dispose());
+                               }
+                       }
+               super.destroy(bundleContext, properties);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/AbstractSwtView.java
new file mode 100644 (file)
index 0000000..e3a2681
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.DataPart;
+import org.argeo.cms.ux.widgets.DataView;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+/** Base class for {@link DataView}s based on an SWT {@link Composite}. */
+public abstract class AbstractSwtView<INPUT, TYPE> extends Composite implements DataView<INPUT, TYPE> {
+       private static final long serialVersionUID = -1999179054267812170L;
+
+       protected DataPart<INPUT, TYPE> dataPart;
+
+       protected final SelectionListener selectionListener;
+
+       @SuppressWarnings("unchecked")
+       public AbstractSwtView(Composite parent, DataPart<INPUT, TYPE> dataPart) {
+               super(parent, SWT.NONE);
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               this.dataPart = dataPart;
+
+               selectionListener = new SelectionListener() {
+
+                       private static final long serialVersionUID = 4334785560035009330L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               if (dataPart.getOnSelected() != null)
+                                       dataPart.getOnSelected().accept((TYPE) e.item.getData());
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               if (dataPart.getOnAction() != null)
+                                       dataPart.getOnAction().accept((TYPE) e.item.getData());
+                       }
+               };
+
+               dataPart.addView(this);
+               addDisposeListener((e) -> dataPart.removeView(this));
+       }
+
+       public abstract void refresh();
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ContextOverlay.java
new file mode 100644 (file)
index 0000000..f7b6443
--- /dev/null
@@ -0,0 +1,113 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Manages a lightweight shell which is related to a {@link Control}, typically
+ * in order to reproduce a dropdown semantic, but with more flexibility.
+ */
+public class ContextOverlay extends ScrolledPage {
+       private static final long serialVersionUID = 6702077429573324009L;
+
+//     private Shell shell;
+       private Control control;
+
+       private int maxHeight = 400;
+
+       public ContextOverlay(Control control, int style) {
+               super(createShell(control, style), SWT.NONE);
+               Shell shell = getShell();
+               setLayoutData(CmsSwtUtils.fillAll());
+               // TODO make autohide configurable?
+               //shell.addShellListener(new AutoHideShellListener());
+               this.control = control;
+               control.addDisposeListener((e) -> {
+                       dispose();
+                       shell.dispose();
+               });
+       }
+
+       private static Composite createShell(Control control, int style) {
+               if (control == null)
+                       throw new IllegalArgumentException("Control cannot be null");
+               if (control.isDisposed())
+                       throw new IllegalArgumentException("Control is disposed");
+               Shell shell = new Shell(control.getShell(), SWT.NO_TRIM);
+               shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite placeholder = new Composite(shell, SWT.BORDER);
+               placeholder.setLayoutData(CmsSwtUtils.fillAll());
+               placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               return placeholder;
+       }
+
+       public void show() {
+               Point relativeControlLocation = control.getLocation();
+               Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y);
+
+               int controlWidth = control.getBounds().width;
+
+               Shell shell = getShell();
+
+               layout(true, true);
+               shell.pack();
+               shell.layout(true, true);
+               int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x;
+               if (shell.getSize().y > maxHeight) {
+                       shell.setSize(targetShellWidth, maxHeight);
+               } else {
+                       shell.setSize(targetShellWidth, shell.getSize().y);
+               }
+
+               int shellHeight = shell.getSize().y;
+               int controlHeight = control.getBounds().height;
+               Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight);
+               int displayHeight = shell.getDisplay().getBounds().height;
+               if (shellLocation.y + shellHeight > displayHeight) {// bottom of page
+                       shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight);
+               }
+               shell.setLocation(shellLocation);
+
+               if (getChildren().length != 0)
+                       shell.open();
+               if (!control.isDisposed())
+                       control.setFocus();
+       }
+
+       public void hide() {
+               getShell().setVisible(false);
+               onHide();
+       }
+
+       public boolean isShellVisible() {
+               if (isDisposed())
+                       return false;
+               return getShell().isVisible();
+       }
+
+       /** to be overridden */
+       protected void onHide() {
+               // does nothing by default.
+       }
+
+       private class AutoHideShellListener extends ShellAdapter {
+               private static final long serialVersionUID = 7743287433907938099L;
+
+               @Override
+               public void shellDeactivated(ShellEvent e) {
+                       try {
+                               Thread.sleep(1000);
+                       } catch (InterruptedException e1) {
+                               // silent
+                       }
+                       if (!control.isDisposed() && !control.isFocusControl())
+                               hide();
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableImage.java
new file mode 100644 (file)
index 0000000..e712e2f
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.AbstractImageManager;
+import org.argeo.cms.ux.CmsUxUtils;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** A stylable and editable image. */
+public abstract class EditableImage extends StyledControl {
+       private static final long serialVersionUID = -5689145523114022890L;
+       private final static CmsLog log = CmsLog.getLog(EditableImage.class);
+
+       private Cms2DSize preferredImageSize;
+       private Boolean loaded = false;
+
+       public EditableImage(Composite parent, int swtStyle) {
+               super(parent, swtStyle);
+       }
+
+       public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) {
+               super(parent, swtStyle);
+               this.preferredImageSize = preferredImageSize;
+       }
+
+       @Override
+       protected void setContainerLayoutData(Composite composite) {
+               // composite.setLayoutData(fillWidth());
+       }
+
+       @Override
+       protected void setControlLayoutData(Control control) {
+               // control.setLayoutData(fillWidth());
+       }
+
+       /** To be overriden. */
+       protected String createImgTag() {
+               return noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y));
+       }
+
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = new Label(box, getStyle());
+               // lbl.setLayoutData(CmsUiUtils.fillWidth());
+               CmsSwtUtils.markup(lbl);
+               CmsSwtUtils.style(lbl, style);
+               if (mouseListener != null)
+                       lbl.addMouseListener(mouseListener);
+               load(lbl);
+               return lbl;
+       }
+
+       /** To be overriden. */
+       protected synchronized Boolean load(Control control) {
+               String imgTag;
+               try {
+                       imgTag = createImgTag();
+               } catch (Exception e) {
+                       // throw new CmsException("Cannot retrieve image", e);
+                       log.error("Cannot retrieve image", e);
+                       imgTag = noImg(preferredImageSize);
+                       loaded = false;
+               }
+
+               if (imgTag == null) {
+                       loaded = false;
+                       imgTag = noImg(preferredImageSize);
+               } else
+                       loaded = true;
+               if (control != null) {
+                       ((Label) control).setText(imgTag);
+                       control.setSize(preferredImageSize != null
+                                       ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight())
+                                       : getSize());
+               } else {
+                       loaded = false;
+               }
+               getParent().layout();
+               return loaded;
+       }
+
+       public void setPreferredSize(Cms2DSize size) {
+               this.preferredImageSize = size;
+               if (!loaded) {
+                       load((Label) getControl());
+               }
+       }
+
+       protected Text createText(Composite box, String style) {
+               Text text = new Text(box, getStyle());
+               CmsSwtUtils.style(text, style);
+               return text;
+       }
+
+       public Cms2DSize getPreferredImageSize() {
+               return preferredImageSize;
+       }
+
+       public static String noImg(Cms2DSize size) {
+//             ResourceManager rm = RWT.getResourceManager();
+//             String noImgPath=rm.getLocation(AbstractImageManager.NO_IMAGE);
+               // FIXME load it via package service
+               String noImgPath = "";
+               return CmsUxUtils.img(noImgPath, size);
+       }
+
+       public static String noImg() {
+               return noImg(AbstractImageManager.NO_IMAGE_SIZE);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/EditableText.java
new file mode 100644 (file)
index 0000000..0612e8f
--- /dev/null
@@ -0,0 +1,135 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable text part displaying styled text. */
+public class EditableText extends StyledControl {
+       private static final long serialVersionUID = -6372283442330912755L;
+
+       private boolean editable = true;
+       private boolean multiLine = true;
+
+       private Color highlightColor;
+       private Composite highlight;
+
+       private boolean useTextAsLabel = false;
+
+       public EditableText(Composite parent, int style) {
+               super(parent, style);
+               editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+               multiLine = !(SWT.SINGLE == (style & SWT.SINGLE));
+               highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+               useTextAsLabel = SWT.FLAT == (style & SWT.FLAT);
+       }
+
+       @Override
+       protected Control createControl(Composite box, String style) {
+               if (isEditing() && getEditable()) {
+                       return createText(box, style, true);
+               } else {
+                       if (useTextAsLabel) {
+                               return createTextLabel(box, style);
+                       } else {
+                               return createLabel(box, style);
+                       }
+               }
+       }
+
+       protected Label createLabel(Composite box, String style) {
+               Label lbl = new Label(box, getStyle() | SWT.WRAP);
+               lbl.setLayoutData(CmsSwtUtils.fillWidth());
+               if (style != null)
+                       CmsSwtUtils.style(lbl, style);
+               CmsSwtUtils.markup(lbl);
+               if (mouseListener != null)
+                       lbl.addMouseListener(mouseListener);
+               return lbl;
+       }
+
+       protected Text createTextLabel(Composite box, String style) {
+               Text lbl = new Text(box, getStyle() | (multiLine ? SWT.MULTI : SWT.SINGLE));
+               lbl.setEditable(false);
+               lbl.setLayoutData(CmsSwtUtils.fillWidth());
+               if (style != null)
+                       CmsSwtUtils.style(lbl, style);
+               CmsSwtUtils.markup(lbl);
+               if (mouseListener != null)
+                       lbl.addMouseListener(mouseListener);
+               return lbl;
+       }
+
+       protected Text createText(Composite box, String style, boolean editable) {
+               highlight = new Composite(box, SWT.NONE);
+               highlight.setBackground(highlightColor);
+               GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
+               highlightGd.widthHint = 5;
+               highlightGd.heightHint = 3;
+               highlight.setLayoutData(highlightGd);
+
+               final Text text = new Text(box, getStyle() | (multiLine ? SWT.MULTI : SWT.SINGLE) | SWT.WRAP);
+               text.setEditable(editable);
+               GridData textLayoutData = CmsSwtUtils.fillWidth();
+               // textLayoutData.heightHint = preferredHeight;
+               text.setLayoutData(textLayoutData);
+               if (style != null)
+                       CmsSwtUtils.style(text, style);
+               text.setFocus();
+               return text;
+       }
+
+       @Override
+       protected void clear(boolean deep) {
+               if (highlight != null)
+                       highlight.dispose();
+               super.clear(deep);
+       }
+
+       public void setText(String text) {
+               Control child = getControl();
+               if (child instanceof Label)
+                       ((Label) child).setText(text);
+               else if (child instanceof Text)
+                       ((Text) child).setText(text);
+       }
+
+       public Text getAsText() {
+               return (Text) getControl();
+       }
+
+       public Label getAsLabel() {
+               return (Label) getControl();
+       }
+
+       public String getText() {
+               Control child = getControl();
+
+               if (child instanceof Label)
+                       return ((Label) child).getText();
+               else if (child instanceof Text)
+                       return ((Text) child).getText();
+               else
+                       throw new IllegalStateException("Unsupported control " + child.getClass());
+       }
+
+       /** @deprecated Use {@link #isEditable()} instead. */
+       @Deprecated
+       public boolean getEditable() {
+               return isEditable();
+       }
+
+       public boolean isEditable() {
+               return editable;
+       }
+
+       public void setUseTextAsLabel(boolean useTextAsLabel) {
+               this.useTextAsLabel = useTextAsLabel;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/ScrolledPage.java
new file mode 100644 (file)
index 0000000..135f4c1
--- /dev/null
@@ -0,0 +1,74 @@
+package org.argeo.cms.swt.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * A composite that can be scrolled vertically. It wraps a
+ * {@link ScrolledComposite} (and is being wrapped by it), simplifying its
+ * configuration.
+ */
+public class ScrolledPage extends Composite {
+       private static final long serialVersionUID = 1593536965663574437L;
+
+       private ScrolledComposite scrolledComposite;
+
+       public ScrolledPage(Composite parent, int style) {
+               this(parent, style, false);
+       }
+
+       public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) {
+               super(createScrolledComposite(parent, alwaysShowScroll), style);
+               scrolledComposite = (ScrolledComposite) getParent();
+               scrolledComposite.setContent(this);
+
+               scrolledComposite.setExpandVertical(true);
+               scrolledComposite.setExpandHorizontal(true);
+               scrolledComposite.addControlListener(new ScrollControlListener());
+       }
+
+       private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) {
+               ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
+               scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll);
+               return scrolledComposite;
+       }
+
+       @Override
+       public void layout(boolean changed, boolean all) {
+               updateScroll();
+               super.layout(changed, all);
+       }
+
+       public void showControl(Control control) {
+               scrolledComposite.showControl(control);
+       }
+
+       protected void updateScroll() {
+               Rectangle r = scrolledComposite.getClientArea();
+               Point preferredSize = computeSize(r.width, SWT.DEFAULT);
+               scrolledComposite.setMinHeight(preferredSize.y);
+       }
+
+       // public ScrolledComposite getScrolledComposite() {
+       // return this.scrolledComposite;
+       // }
+
+       /** Set it on the wrapping scrolled composite */
+       @Override
+       public void setLayoutData(Object layoutData) {
+               scrolledComposite.setLayoutData(layoutData);
+       }
+
+       private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter {
+               private static final long serialVersionUID = -3586986238567483316L;
+
+               public void controlResized(ControlEvent e) {
+                       updateScroll();
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/StyledControl.java
new file mode 100644 (file)
index 0000000..82c04a2
--- /dev/null
@@ -0,0 +1,151 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.api.cms.ux.CmsStyle;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SwtEditablePart;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Editable text part displaying styled text. */
+public abstract class StyledControl extends Composite implements SwtEditablePart {
+       private static final long serialVersionUID = -6372283442330912755L;
+       private Control control;
+
+       private Composite container;
+       private Composite box;
+
+       protected MouseListener mouseListener;
+       protected FocusListener focusListener;
+
+       private Boolean editing = Boolean.FALSE;
+
+       private Composite ancestorToLayout;
+
+       public StyledControl(Composite parent, int swtStyle) {
+               super(parent, swtStyle);
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+       }
+
+       protected abstract Control createControl(Composite box, String style);
+
+       protected Composite createBox() {
+               Composite box = new Composite(container, SWT.INHERIT_DEFAULT);
+               setContainerLayoutData(box);
+               box.setLayout(CmsSwtUtils.noSpaceGridLayout(3));
+               return box;
+       }
+
+       protected Composite createContainer() {
+               Composite container = new Composite(this, SWT.INHERIT_DEFAULT);
+               setContainerLayoutData(container);
+               container.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               return container;
+       }
+
+       @Override
+       public Control getControl() {
+               return control;
+       }
+
+       protected synchronized Boolean isEditing() {
+               return editing;
+       }
+
+       @Override
+       public synchronized void startEditing() {
+               assert !isEditing();
+               editing = true;
+               // int height = control.getSize().y;
+               String style = (String) EclipseUiSpecificUtils.getStyleData(control);
+               clear(false);
+               refreshControl(style);
+
+               // add the focus listener to the newly created edition control
+               if (focusListener != null)
+                       control.addFocusListener(focusListener);
+       }
+
+       @Override
+       public synchronized void stopEditing() {
+               assert isEditing();
+               editing = false;
+               String style = (String) EclipseUiSpecificUtils.getStyleData(control);
+               clear(false);
+               refreshControl(style);
+       }
+
+       protected void refreshControl(String style) {
+               control = createControl(box, style);
+               setControlLayoutData(control);
+               if (ancestorToLayout != null)
+                       ancestorToLayout.layout(true, true);
+               else
+                       getParent().layout(true, true);
+       }
+
+       public void setStyle(CmsStyle style) {
+               setStyle(style.style());
+       }
+
+       public void setStyle(String style) {
+               Object currentStyle = null;
+               if (control != null)
+                       currentStyle = EclipseUiSpecificUtils.getStyleData(control);
+               if (currentStyle != null && currentStyle.equals(style))
+                       return;
+
+               clear(true);
+               refreshControl(style);
+
+               if (style != null) {
+                       CmsSwtUtils.style(box, style + "_box");
+                       CmsSwtUtils.style(container, style + "_container");
+               }
+       }
+
+       /** To be overridden */
+       protected void setControlLayoutData(Control control) {
+               control.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       /** To be overridden */
+       protected void setContainerLayoutData(Composite composite) {
+               composite.setLayoutData(CmsSwtUtils.fillWidth());
+       }
+
+       protected void clear(boolean deep) {
+               if (deep) {
+                       for (Control control : getChildren())
+                               control.dispose();
+                       container = createContainer();
+                       box = createBox();
+               } else {
+                       control.dispose();
+               }
+       }
+
+       public void setMouseListener(MouseListener mouseListener) {
+               if (this.mouseListener != null && control != null)
+                       control.removeMouseListener(this.mouseListener);
+               this.mouseListener = mouseListener;
+               if (control != null && this.mouseListener != null)
+                       control.addMouseListener(mouseListener);
+       }
+
+       public void setFocusListener(FocusListener focusListener) {
+               if (this.focusListener != null && control != null)
+                       control.removeFocusListener(this.focusListener);
+               this.focusListener = focusListener;
+               if (control != null && this.focusListener != null)
+                       control.addFocusListener(focusListener);
+       }
+
+       public void setAncestorToLayout(Composite ancestorToLayout) {
+               this.ancestorToLayout = ancestorToLayout;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormDialog.java
new file mode 100644 (file)
index 0000000..15d531d
--- /dev/null
@@ -0,0 +1,207 @@
+package org.argeo.cms.swt.widgets;
+
+import java.util.List;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.swt.dialogs.LightweightDialog;
+import org.argeo.cms.ux.widgets.CmsDialog;
+import org.argeo.cms.ux.widgets.GuidedForm;
+import org.argeo.cms.ux.widgets.GuidedForm.Page;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/** A wizard dialog based on {@link LightweightDialog}. */
+public class SwtGuidedFormDialog extends LightweightDialog implements GuidedForm.View {
+       private GuidedForm guidedForm;
+       private GuidedForm.Page currentPage;
+       private int currentPageIndex;
+
+       private Label titleBar;
+       private Label message;
+       private Composite[] pageBodies;
+       private Composite buttons;
+       private Button back;
+       private Button next;
+       private Button finish;
+
+       public SwtGuidedFormDialog(Shell parentShell, GuidedForm guidedForm) {
+               super(parentShell);
+               this.guidedForm = guidedForm;
+               guidedForm.setView(this);
+               // create the pages
+               guidedForm.addPages();
+               for (Page page : guidedForm.getPages()) {
+                       if (!(page instanceof SwtGuidedFormPage))
+                               throw new IllegalArgumentException("Pages form must implement " + SwtGuidedFormPage.class);
+               }
+               currentPage = guidedForm.getStartingPage();
+               if (currentPage == null)
+                       throw new IllegalArgumentException("At least one wizard page is required");
+       }
+
+       @Override
+       protected Control createDialogArea(Composite parent) {
+               updateWindowTitle();
+
+               Composite messageArea = new Composite(parent, SWT.NONE);
+               messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               {
+                       messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false)));
+                       titleBar = new Label(messageArea, SWT.WRAP);
+                       titleBar.setFont(EclipseUiUtils.getBoldFont(parent));
+                       titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
+                       updateTitleBar();
+                       Button cancelButton = new Button(messageArea, SWT.FLAT);
+                       cancelButton.setText(CmsMsg.cancel.lead());
+                       cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3));
+                       cancelButton.addSelectionListener((Selected) (e) -> closeShell(CmsDialog.CANCEL));
+                       message = new Label(messageArea, SWT.WRAP);
+                       message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2));
+                       updateMessage();
+               }
+
+               Composite body = new Composite(parent, SWT.BORDER);
+               body.setLayout(new FormLayout());
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               pageBodies = new Composite[guidedForm.getPageCount()];
+               List<GuidedForm.Page> pages = guidedForm.getPages();
+               for (int i = 0; i < pages.size(); i++) {
+                       pageBodies[i] = new Composite(body, SWT.NONE);
+                       pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout());
+                       setSwitchingFormData(pageBodies[i]);
+                       // !! SWT specific
+                       SwtGuidedFormPage page = (SwtGuidedFormPage) pages.get(i);
+                       page.createControl(pageBodies[i]);
+               }
+               showPage(currentPage);
+
+               buttons = new Composite(parent, SWT.NONE);
+               buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+               {
+                       boolean singlePage = guidedForm.getPageCount() == 1;
+                       // singlePage = false;// dev
+                       GridLayout layout = new GridLayout(singlePage ? 1 : 3, true);
+                       layout.marginWidth = 0;
+                       layout.marginHeight = 0;
+                       buttons.setLayout(layout);
+                       // TODO revert order for right-to-left languages
+
+                       if (!singlePage) {
+                               back = new Button(buttons, SWT.PUSH);
+                               back.setText(CmsMsg.wizardBack.lead());
+                               back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               back.addSelectionListener((Selected) (e) -> backPressed());
+
+                               next = new Button(buttons, SWT.PUSH);
+                               next.setText(CmsMsg.wizardNext.lead());
+                               next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                               next.addSelectionListener((Selected) (e) -> nextPressed());
+                       }
+                       finish = new Button(buttons, SWT.PUSH);
+                       finish.setText(CmsMsg.wizardFinish.lead());
+                       finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+                       finish.addSelectionListener((Selected) (e) -> finishPressed());
+
+                       updateButtons();
+               }
+               return body;
+       }
+
+       public GuidedForm.Page getCurrentPage() {
+               return currentPage;
+       }
+
+       public Shell getShell() {
+               return getForegoundShell();
+       }
+
+       public void showPage(GuidedForm.Page page) {
+               List<GuidedForm.Page> pages = guidedForm.getPages();
+               int index = -1;
+               for (int i = 0; i < pages.size(); i++) {
+                       if (page == pages.get(i)) {
+                               index = i;
+                               break;
+                       }
+               }
+               if (index < 0)
+                       throw new IllegalArgumentException("Cannot find index of wizard page " + page);
+               pageBodies[index].moveAbove(pageBodies[currentPageIndex]);
+
+               // // clear
+               // for (Control c : body.getChildren())
+               // c.dispose();
+               // page.createControl(body);
+               // body.layout(true, true);
+               currentPageIndex = index;
+               currentPage = page;
+       }
+
+       @Override
+       public void updateButtons() {
+               if (back != null)
+                       back.setEnabled(guidedForm.getPreviousPage(currentPage) != null);
+               if (next != null)
+                       next.setEnabled(guidedForm.getNextPage(currentPage) != null && currentPage.canFlipToNextPage());
+               if (finish != null) {
+                       finish.setEnabled(guidedForm.canFinish());
+               }
+       }
+
+       public void updateMessage() {
+               if (currentPage.getMessage() != null)
+                       message.setText(currentPage.getMessage());
+       }
+
+       public void updateTitleBar() {
+               if (currentPage.getTitle() != null)
+                       titleBar.setText(currentPage.getTitle());
+       }
+
+       public void updateWindowTitle() {
+               setTitle(guidedForm.getFormTitle());
+       }
+
+       protected boolean onCancel() {
+               return guidedForm.performCancel();
+       }
+
+       protected void nextPressed() {
+               GuidedForm.Page page = guidedForm.getNextPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void backPressed() {
+               GuidedForm.Page page = guidedForm.getPreviousPage(currentPage);
+               showPage(page);
+               updateButtons();
+       }
+
+       protected void finishPressed() {
+               if (guidedForm.performFinish())
+                       closeShell(CmsDialog.OK);
+       }
+
+       private static void setSwitchingFormData(Composite composite) {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               composite.setLayoutData(fdLabel);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtGuidedFormPage.java
new file mode 100644 (file)
index 0000000..f082796
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.cms.ux.widgets.AbstractGuidedFormPage;
+import org.eclipse.swt.widgets.Composite;
+
+public abstract class SwtGuidedFormPage extends AbstractGuidedFormPage {
+
+       public SwtGuidedFormPage(String pageName) {
+               super(pageName);
+       }
+
+       public abstract void createControl(Composite parent);
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTableView.java
new file mode 100644 (file)
index 0000000..3291980
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.cms.swt.widgets;
+
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.TabularPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+
+/** View of a {@link TabularPart} based on a {@link Table}. */
+public class SwtTableView<INPUT, T> extends AbstractSwtView<INPUT, T> {
+       private static final long serialVersionUID = -1114155772446357750L;
+       private final Table table;
+       private TabularPart<INPUT, T> tabularPart;
+
+       private CmsSwtTheme theme;
+
+       public SwtTableView(Composite parent, int style, TabularPart<INPUT, T> tabularPart) {
+               super(parent, tabularPart);
+               theme = CmsSwtUtils.getCmsTheme(parent);
+
+               table = new Table(this, SWT.VIRTUAL | style);
+               table.setLinesVisible(true);
+               table.setLayoutData(CmsSwtUtils.fillAll());
+
+               this.tabularPart = tabularPart;
+       }
+
+       @Override
+       public void refresh() {
+               // TODO optimise
+               table.clearAll();
+               table.addListener(SWT.SetData, event -> {
+                       TableItem item = (TableItem) event.item;
+                       refreshItem(item);
+               });
+               table.setItemCount(tabularPart.getItemCount());
+               for (int i = 0; i < tabularPart.getColumnCount(); i++) {
+                       TableColumn swtColumn = new TableColumn(table, SWT.NONE);
+                       swtColumn.setWidth(tabularPart.getColumn(i).getWidth());
+               }
+               CmsSwtUtils.fill(table);
+
+               table.addSelectionListener(selectionListener);
+
+       }
+
+       protected Object getDataFromEvent(SelectionEvent e) {
+               Object data = e.item.getData();
+               if (data == null)
+                       data = tabularPart.getData(getTable().indexOf((TableItem) e.item));
+               return data;
+       }
+
+       protected void refreshItem(TableItem item) {
+               int row = getTable().indexOf(item);
+               T data = tabularPart.getData(row);
+               for (int i = 0; i < tabularPart.getColumnCount(); i++) {
+                       Column<T> column = tabularPart.getColumn(i);
+                       item.setData(data);
+                       String text = data != null ? column.getText(data) : "";
+                       if (text != null)
+                               item.setText(i, text);
+                       CmsIcon icon = column.getIcon(data);
+                       if (icon != null) {
+                               Image image = theme.getSmallIcon(icon);
+                               item.setImage(i, image);
+                       }
+               }
+       }
+
+       @Override
+       public void notifyItemCountChange() {
+               table.setItemCount(tabularPart.getItemCount());
+       }
+
+       protected Table getTable() {
+               return table;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java b/swt/org.argeo.cms.swt/src/org/argeo/cms/swt/widgets/SwtTreeView.java
new file mode 100644 (file)
index 0000000..778ed41
--- /dev/null
@@ -0,0 +1,108 @@
+package org.argeo.cms.swt.widgets;
+
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsIcon;
+import org.argeo.cms.swt.CmsSwtTheme;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ux.widgets.Column;
+import org.argeo.cms.ux.widgets.HierarchicalPart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
+
+/** View of a {@link HierarchicalPart} based on a {@link Tree}. */
+public class SwtTreeView<T> extends AbstractSwtView<T, T> {
+       private final static CmsLog log = CmsLog.getLog(SwtTreeView.class);
+
+       private static final long serialVersionUID = -6247710601465713047L;
+
+       private final Tree tree;
+
+       private HierarchicalPart<T> hierarchicalPart;
+       private CmsSwtTheme theme;
+
+       public SwtTreeView(Composite parent, int style, HierarchicalPart<T> hierarchicalPart) {
+               super(parent, hierarchicalPart);
+               theme = CmsSwtUtils.getCmsTheme(parent);
+
+               tree = new Tree(this, style);
+               tree.setLayoutData(CmsSwtUtils.fillAll());
+               this.hierarchicalPart = hierarchicalPart;
+
+               tree.addSelectionListener(selectionListener);
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public void refresh() {
+               // TODO optimise
+               for (TreeItem rootItem : tree.getItems()) {
+                       rootItem.dispose();
+               }
+
+               List<T> rootItems = hierarchicalPart.getChildren(hierarchicalPart.getInput());
+               for (T child : rootItems) {
+                       try {
+                               addTreeItem(null, child);
+                       } catch (Exception e) {
+                               if (log.isTraceEnabled())
+                                       log.error("Cannot retrieve child", e);
+                       }
+               }
+
+               tree.addListener(SWT.Expand, event -> {
+                       final TreeItem root = (TreeItem) event.item;
+                       TreeItem[] items = root.getItems();
+                       for (TreeItem item : items) {
+                               if (item.getData() != null) {
+                                       return;
+                               }
+                               item.dispose();
+                       }
+
+                       List<T> children = hierarchicalPart.getChildren((T) root.getData());
+                       for (T child : children) {
+                               addTreeItem(root, child);
+                       }
+               });
+
+               CmsSwtUtils.fill(tree);
+
+       }
+
+       protected TreeItem addTreeItem(TreeItem parent, T data) {
+               TreeItem item = parent == null ? new TreeItem(tree, SWT.NONE) : new TreeItem(parent, SWT.NONE);
+               item.setData(data);
+               for (int i = 0; i < hierarchicalPart.getColumnCount(); i++) {
+                       Column<T> column = hierarchicalPart.getColumn(i);
+                       String txt = column.getText(data);
+                       if (txt != null)
+                               item.setText(txt);
+                       CmsIcon icon = column.getIcon(data);
+                       if (icon != null) {
+                               Image image = theme.getSmallIcon(icon);
+                               item.setImage(image);
+                       }
+               }
+               // TODO optimise
+               List<T> grandChildren = hierarchicalPart.getChildren(data);
+               if (grandChildren.size() != 0)
+                       new TreeItem(item, SWT.NONE);
+               return item;
+       }
+
+       @Override
+       public void notifyItemCountChange() {
+               // TODO what to update ?
+
+       }
+
+       protected Tree getTree() {
+               return tree;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java
new file mode 100644 (file)
index 0000000..64ea2db
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.eclipse.ui;
+
+import org.argeo.cms.ux.widgets.TreeParent;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Tree content provider dealing with tree objects and providing reasonable
+ * defaults.
+ */
+public abstract class AbstractTreeContentProvider implements
+               ITreeContentProvider {
+       private static final long serialVersionUID = 8246126401957763868L;
+
+       /** Does nothing */
+       public void dispose() {
+       }
+
+       /** Does nothing */
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+       }
+
+       public Object[] getChildren(Object element) {
+               if (element instanceof TreeParent) {
+                       return ((TreeParent) element).getChildren();
+               }
+               return new Object[0];
+       }
+
+       public Object getParent(Object element) {
+               if (element instanceof TreeParent) {
+                       return ((TreeParent) element).getParent();
+               }
+               return null;
+       }
+
+       public boolean hasChildren(Object element) {
+               if (element instanceof TreeParent) {
+                       return ((TreeParent) element).hasChildren();
+               }
+               return false;
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java
new file mode 100644 (file)
index 0000000..a38552c
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/**
+ * Wraps the definition of a column to be used in the various JFace viewers
+ * (typically tree and table). It enables definition of generic viewers which
+ * column can be then defined externally. Also used to generate export.
+ */
+public class ColumnDefinition {
+       private ColumnLabelProvider labelProvider;
+       private String label;
+       private int weight = 0;
+       private int minWidth = 120;
+
+       public ColumnDefinition(ColumnLabelProvider labelProvider, String label) {
+               this.labelProvider = labelProvider;
+               this.label = label;
+       }
+
+       public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
+                       int weight) {
+               this.labelProvider = labelProvider;
+               this.label = label;
+               this.weight = weight;
+               this.minWidth = weight;
+       }
+
+       public ColumnDefinition(ColumnLabelProvider labelProvider, String label,
+                       int weight, int minimumWidth) {
+               this.labelProvider = labelProvider;
+               this.label = label;
+               this.weight = weight;
+               this.minWidth = minimumWidth;
+       }
+
+       public ColumnLabelProvider getLabelProvider() {
+               return labelProvider;
+       }
+
+       public void setLabelProvider(ColumnLabelProvider labelProvider) {
+               this.labelProvider = labelProvider;
+       }
+
+       public String getLabel() {
+               return label;
+       }
+
+       public void setLabel(String label) {
+               this.label = label;
+       }
+
+       public int getWeight() {
+               return weight;
+       }
+
+       public void setWeight(int weight) {
+               this.weight = weight;
+       }
+
+       public int getMinWidth() {
+               return minWidth;
+       }
+
+       public void setMinWidth(int minWidth) {
+               this.minWidth = minWidth;
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java
new file mode 100644 (file)
index 0000000..9430a20
--- /dev/null
@@ -0,0 +1,81 @@
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+
+/** Generic column viewer sorter */
+public class ColumnViewerComparator extends ViewerComparator {
+       private static final long serialVersionUID = -2266218906355859909L;
+
+       public static final int ASC = 1;
+
+       public static final int NONE = 0;
+
+       public static final int DESC = -1;
+
+       private int direction = 0;
+
+       private TableViewerColumn column;
+
+       private ColumnViewer viewer;
+
+       public ColumnViewerComparator(TableViewerColumn column) {
+               super(null);
+               this.column = column;
+               this.viewer = column.getViewer();
+               this.column.getColumn().addSelectionListener(new SelectionAdapter() {
+                       private static final long serialVersionUID = 7586796298965472189L;
+
+                       public void widgetSelected(SelectionEvent e) {
+                               if (ColumnViewerComparator.this.viewer.getComparator() != null) {
+                                       if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) {
+                                               int tdirection = ColumnViewerComparator.this.direction;
+
+                                               if (tdirection == ASC) {
+                                                       setSortDirection(DESC);
+                                               } else if (tdirection == DESC) {
+                                                       setSortDirection(NONE);
+                                               }
+                                       } else {
+                                               setSortDirection(ASC);
+                                       }
+                               } else {
+                                       setSortDirection(ASC);
+                               }
+                       }
+               });
+       }
+
+       private void setSortDirection(int direction) {
+               if (direction == NONE) {
+                       column.getColumn().getParent().setSortColumn(null);
+                       column.getColumn().getParent().setSortDirection(SWT.NONE);
+                       viewer.setComparator(null);
+               } else {
+                       column.getColumn().getParent().setSortColumn(column.getColumn());
+                       this.direction = direction;
+
+                       if (direction == ASC) {
+                               column.getColumn().getParent().setSortDirection(SWT.DOWN);
+                       } else {
+                               column.getColumn().getParent().setSortDirection(SWT.UP);
+                       }
+
+                       if (viewer.getComparator() == this) {
+                               viewer.refresh();
+                       } else {
+                               viewer.setComparator(this);
+                       }
+
+               }
+       }
+
+       public int compare(Viewer viewer, Object e1, Object e2) {
+               return direction * super.compare(viewer, e1, e2);
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java
new file mode 100644 (file)
index 0000000..37a36e8
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.eclipse.ui;
+
+/** CMS specific exceptions. */
+public class EclipseUiException extends RuntimeException {
+       private static final long serialVersionUID = -5341764743356771313L;
+
+       public EclipseUiException(String message) {
+               super(message);
+       }
+
+       public EclipseUiException(String message, Throwable e) {
+               super(message, e);
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java
new file mode 100644 (file)
index 0000000..95b45fe
--- /dev/null
@@ -0,0 +1,195 @@
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Utilities to simplify UI development. */
+public class EclipseUiUtils {
+
+       /** Dispose all children of a Composite */
+       public static void clear(Composite composite) {
+               for (Control child : composite.getChildren())
+                       child.dispose();
+       }
+
+       /**
+        * Enables efficient call to the layout method of a composite, refreshing only
+        * some of the children controls.
+        */
+       public static void layout(Composite parent, Control... toUpdateControls) {
+               parent.layout(toUpdateControls);
+       }
+
+       //
+       // FONTS
+       //
+       /** Shortcut to retrieve default italic font from display */
+       public static Font getItalicFont(Composite parent) {
+               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC)
+                               .createFont(parent.getDisplay());
+       }
+
+       /** Shortcut to retrieve default bold font from display */
+       public static Font getBoldFont(Composite parent) {
+               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
+                               .createFont(parent.getDisplay());
+       }
+
+       /** Shortcut to retrieve default bold italic font from display */
+       public static Font getBoldItalicFont(Composite parent) {
+               return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC)
+                               .createFont(parent.getDisplay());
+       }
+
+       //
+       // Simplify grid layouts management
+       //
+       public static GridLayout noSpaceGridLayout() {
+               return noSpaceGridLayout(new GridLayout());
+       }
+
+       public static GridLayout noSpaceGridLayout(int columns) {
+               return noSpaceGridLayout(new GridLayout(columns, false));
+       }
+
+       public static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static GridData fillWidth() {
+               return grabWidth(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData fillWidth(int colSpan) {
+               GridData gd = grabWidth(SWT.FILL, SWT.FILL);
+               gd.horizontalSpan = colSpan;
+               return gd;
+       }
+
+       public static GridData fillAll() {
+               return new GridData(SWT.FILL, SWT.FILL, true, true);
+       }
+
+       public static GridData fillAll(int colSpan, int rowSpan) {
+               return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan);
+       }
+
+       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+       }
+
+       //
+       // Simplify Form layout management
+       //
+
+       /**
+        * Creates a basic form data that is attached to the 4 corners of the parent
+        * composite
+        */
+       public static FormData fillFormData() {
+               FormData formData = new FormData();
+               formData.top = new FormAttachment(0, 0);
+               formData.left = new FormAttachment(0, 0);
+               formData.right = new FormAttachment(100, 0);
+               formData.bottom = new FormAttachment(100, 0);
+               return formData;
+       }
+
+       /**
+        * Create a label and a text field for a grid layout, the text field grabbing
+        * excess horizontal
+        * 
+        * @param parent
+        *            the parent composite
+        * @param label
+        *            the label to display
+        * @param modifyListener
+        *            a {@link ModifyListener} to listen on events on the text, can be
+        *            null
+        * @return the created text
+        * 
+        */
+       // FIXME why was this deprecated.
+       // * @ deprecated use { @ link #createGridLT(Composite, String)} instead
+       // @ Deprecated
+       public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) {
+               Label lbl = new Label(parent, SWT.LEAD);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+               Text txt = new Text(parent, SWT.LEAD | SWT.BORDER);
+               txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               if (modifyListener != null)
+                       txt.addModifyListener(modifyListener);
+               return txt;
+       }
+
+       /**
+        * Create a label and a text field for a grid layout, the text field grabbing
+        * excess horizontal
+        */
+       public static Text createGridLT(Composite parent, String label) {
+               return createGridLT(parent, label, null);
+       }
+
+       /**
+        * Creates one label and a text field not editable with background colour of the
+        * parent (like a label but with selectable text)
+        */
+       public static Text createGridLL(Composite parent, String label, String text) {
+               Text txt = createGridLT(parent, label);
+               txt.setText(text);
+               txt.setEditable(false);
+               txt.setBackground(parent.getBackground());
+               return txt;
+       }
+
+       /**
+        * Create a label and a text field with password display for a grid layout, the
+        * text field grabbing excess horizontal
+        */
+       public static Text createGridLP(Composite parent, String label) {
+               return createGridLP(parent, label, null);
+       }
+
+       /**
+        * Create a label and a text field with password display for a grid layout, the
+        * text field grabbing excess horizontal. The given modify listener will be
+        * added to the newly created text field if not null.
+        */
+       public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) {
+               Label lbl = new Label(parent, SWT.LEAD);
+               lbl.setText(label);
+               lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
+               Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD);
+               txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+               if (modifyListener != null)
+                       txt.addModifyListener(modifyListener);
+               return txt;
+       }
+
+       // MISCELLANEOUS
+
+       /** Simply checks if a string is not null nor empty */
+       public static boolean notEmpty(String stringToTest) {
+               return !(stringToTest == null || "".equals(stringToTest.trim()));
+       }
+
+       /** Simply checks if a string is null or empty */
+       public static boolean isEmpty(String stringToTest) {
+               return stringToTest == null || "".equals(stringToTest.trim());
+       }
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java
new file mode 100644 (file)
index 0000000..e82505d
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.eclipse.ui;
+
+import java.io.InputStream;
+
+/**
+ * Used for file download : subclasses must implement model specific methods to
+ * get a byte array representing a file given is ID.
+ */
+@Deprecated
+public interface FileProvider {
+
+       public byte[] getByteArrayFileFromId(String fileId);
+
+       public InputStream getInputStreamFromFileId(String fileId);
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java
new file mode 100644 (file)
index 0000000..e1d8b05
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.eclipse.ui;
+
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerComparator;
+
+public abstract class GenericTableComparator extends ViewerComparator {
+       private static final long serialVersionUID = -1175894935075325810L;
+       protected int propertyIndex;
+       public static final int ASCENDING = 0, DESCENDING = 1;
+       protected int direction = DESCENDING;
+
+       /**
+        * Creates an instance of a sorter for TableViewer.
+        * 
+        * @param defaultColumnIndex
+        *            the default sorter column
+        */
+
+       public GenericTableComparator(int defaultColumnIndex, int direction) {
+               propertyIndex = defaultColumnIndex;
+               this.direction = direction;
+       }
+
+       public void setColumn(int column) {
+               if (column == this.propertyIndex) {
+                       // Same column as last sort; toggle the direction
+                       direction = 1 - direction;
+               } else {
+                       // New column; do a descending sort
+                       this.propertyIndex = column;
+                       direction = DESCENDING;
+               }
+       }
+
+       /**
+        * Must be Overriden in each view.
+        */
+       public abstract int compare(Viewer viewer, Object e1, Object e2);
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java
new file mode 100644 (file)
index 0000000..ac7b2d8
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.eclipse.ui;
+
+import java.util.List;
+
+/**
+ * Views and editors can implement this interface so that one of the list that
+ * is displayed in the part (For instance in a Table or a Tree Viewer) can be
+ * rebuilt externally. Typically to generate csv or calc extract.
+ */
+public interface IListProvider {
+       /**
+        * Returns an array of current and relevant elements
+        */
+       public Object[] getElements(String extractId);
+
+       /**
+        * Returns the column definition for passed ID
+        */
+       public List<ColumnDefinition> getColumnDefinition(String extractId);
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java
new file mode 100644 (file)
index 0000000..c01b2d7
--- /dev/null
@@ -0,0 +1,450 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashMap;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+
+/** Simple UI provider that populates a composite parent given a NIO path */
+public class AdvancedFsBrowser {
+       private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class);
+
+       // Some local constants to experiment. should be cleaned
+       // private final static int THUMBNAIL_WIDTH = 400;
+       // private Point imageWidth = new Point(250, 0);
+       private final static int COLUMN_WIDTH = 160;
+
+       private Path initialPath;
+       private Path currEdited;
+       // Filter
+       private Composite displayBoxCmp;
+       private Text parentPathTxt;
+       private Text filterTxt;
+       // Browser columns
+       private ScrolledComposite scrolledCmp;
+       // Keep a cache of the opened directories
+       private LinkedHashMap<Path, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<>();
+       private Composite scrolledCmpBody;
+
+       public Control createUi(Composite parent, Path basePath) {
+               if (basePath == null)
+                       throw new IllegalArgumentException("Context cannot be null");
+               parent.setLayout(new GridLayout());
+
+               // top filter
+               Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
+               filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
+               addFilterPanel(filterCmp);
+
+               // Bottom part a sash with browser on the left
+               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+               // form.setLayout(new FillLayout());
+               form.setLayoutData(EclipseUiUtils.fillAll());
+               Composite leftCmp = new Composite(form, SWT.NO_FOCUS);
+               displayBoxCmp = new Composite(form, SWT.NONE);
+               form.setWeights(new int[] { 3, 1 });
+
+               createBrowserPart(leftCmp, basePath);
+               // leftCmp.addControlListener(new ControlAdapter() {
+               // @Override
+               // public void controlResized(ControlEvent e) {
+               // Rectangle r = leftCmp.getClientArea();
+               // log.warn("Browser resized: " + r.toString());
+               // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
+               // SWT.DEFAULT);
+               // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
+               // // r.height));
+               // }
+               // });
+
+               populateCurrEditedDisplay(displayBoxCmp, basePath);
+
+               // INIT
+               setEdited(basePath);
+               initialPath = basePath;
+               // form.layout(true, true);
+               return parent;
+       }
+
+       private void createBrowserPart(Composite parent, Path context) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+               // scrolled composite
+               scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
+               scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               scrolledCmp.setExpandVertical(true);
+               scrolledCmp.setExpandHorizontal(true);
+               scrolledCmp.setShowFocusedControl(true);
+
+               scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS);
+               scrolledCmp.setContent(scrolledCmpBody);
+               scrolledCmpBody.addControlListener(new ControlAdapter() {
+                       private static final long serialVersionUID = 183238447102854553L;
+
+                       @Override
+                       public void controlResized(ControlEvent e) {
+                               Rectangle r = scrolledCmp.getClientArea();
+                               scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height));
+                       }
+               });
+               initExplorer(scrolledCmpBody, context);
+               scrolledCmpBody.layout(true, true);
+               scrolledCmp.layout();
+
+       }
+
+       private Control initExplorer(Composite parent, Path context) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               return createBrowserColumn(parent, context);
+       }
+
+       private Control createBrowserColumn(Composite parent, Path context) {
+               // TODO style is not correctly managed.
+               FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
+               // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
+               table.filterList("*");
+               table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
+               browserCols.put(context, table);
+               parent.layout(true, true);
+               return table;
+       }
+
+       public void addFilterPanel(Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
+
+               parentPathTxt = new Text(parent, SWT.NO_FOCUS);
+               parentPathTxt.setEditable(false);
+
+               filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
+               filterTxt.setMessage("Filter current list");
+               filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
+               filterTxt.addModifyListener(new ModifyListener() {
+                       private static final long serialVersionUID = 1L;
+
+                       public void modifyText(ModifyEvent event) {
+                               modifyFilter(false);
+                       }
+               });
+               filterTxt.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = 2533535233583035527L;
+
+                       @Override
+                       public void keyReleased(KeyEvent e) {
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {
+                               boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
+                               // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
+                               FilterEntitiesVirtualTable currTable = null;
+                               if (currEdited != null) {
+                                       FilterEntitiesVirtualTable table = browserCols.get(currEdited);
+                                       if (table != null && !table.isDisposed())
+                                               currTable = table;
+                               }
+
+                               if (e.keyCode == SWT.ARROW_DOWN)
+                                       currTable.setFocus();
+                               else if (e.keyCode == SWT.BS) {
+                                       if (filterTxt.getText().equals("")
+                                                       && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) {
+                                               Path oldEdited = currEdited;
+                                               Path parentPath = currEdited.getParent();
+                                               setEdited(parentPath);
+                                               if (browserCols.containsKey(parentPath))
+                                                       browserCols.get(parentPath).setSelected(oldEdited);
+                                               filterTxt.setFocus();
+                                               e.doit = false;
+                                       }
+                               } else if (e.keyCode == SWT.TAB && !shiftPressed) {
+                                       Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText());
+                                       if (uniqueChild != null) {
+                                               // Highlight the unique chosen child
+                                               currTable.setSelected(uniqueChild);
+                                               setEdited(uniqueChild);
+                                       }
+                                       filterTxt.setFocus();
+                                       e.doit = false;
+                               }
+                       }
+               });
+       }
+
+       private Path getOnlyChild(Path parent, String filter) {
+               try (DirectoryStream<Path> stream = Files.newDirectoryStream(currEdited, filter + "*")) {
+                       Path uniqueChild = null;
+                       boolean moreThanOne = false;
+                       loop: for (Path entry : stream) {
+                               if (uniqueChild == null) {
+                                       uniqueChild = entry;
+                               } else {
+                                       moreThanOne = true;
+                                       break loop;
+                               }
+                       }
+                       if (!moreThanOne)
+                               return uniqueChild;
+                       return null;
+               } catch (IOException ioe) {
+                       throw new FsUiException(
+                                       "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
+                                       ioe);
+               }
+       }
+
+       private void setEdited(Path path) {
+               currEdited = path;
+               EclipseUiUtils.clear(displayBoxCmp);
+               populateCurrEditedDisplay(displayBoxCmp, currEdited);
+               refreshFilters(path);
+               refreshBrowser(path);
+       }
+
+       private void refreshFilters(Path path) {
+               parentPathTxt.setText(path.toUri().toString());
+               filterTxt.setText("");
+               filterTxt.getParent().layout();
+       }
+
+       private void refreshBrowser(Path currPath) {
+               Path currParPath = currPath.getParent();
+               Object[][] colMatrix = new Object[browserCols.size()][2];
+
+               int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1;
+               for (Path path : browserCols.keySet()) {
+                       colMatrix[i][0] = path;
+                       colMatrix[i][1] = browserCols.get(path);
+                       if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) {
+                               boolean leaveOpened = path.startsWith(currPath);
+                               if (!leaveOpened)
+                                       lastLeftOpenedIndex = i;
+                       }
+                       if (currParPath.equals(path))
+                               currPathIndex = i;
+                       i++;
+               }
+
+               if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) {
+                       // dispose and remove useless cols
+                       for (int l = i - 1; l >= lastLeftOpenedIndex; l--) {
+                               ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
+                               browserCols.remove(colMatrix[l][0]);
+                       }
+               }
+
+               if (browserCols.containsKey(currPath)) {
+                       FilterEntitiesVirtualTable currCol = browserCols.get(currPath);
+                       if (currCol.isDisposed()) {
+                               // Does it still happen ?
+                               log.warn(currPath + " browser column was disposed and still listed");
+                               browserCols.remove(currPath);
+                       }
+               }
+
+               if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath))
+                       createBrowserColumn(scrolledCmpBody, currPath);
+
+               scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
+               scrolledCmpBody.layout(true, true);
+               // also resize the scrolled composite
+               scrolledCmp.layout();
+       }
+
+       private void modifyFilter(boolean fromOutside) {
+               if (!fromOutside)
+                       if (currEdited != null) {
+                               String filter = filterTxt.getText() + "*";
+                               FilterEntitiesVirtualTable table = browserCols.get(currEdited);
+                               if (table != null && !table.isDisposed())
+                                       table.filterList(filter);
+                       }
+       }
+
+       /**
+        * Recreates the content of the box that displays information about the current
+        * selected node.
+        */
+       private void populateCurrEditedDisplay(Composite parent, Path context) {
+               parent.setLayout(new GridLayout());
+
+               // if (isImg(context)) {
+               // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
+               // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
+               // 2, 1));
+               // }
+
+               try {
+                       Label contextL = new Label(parent, SWT.NONE);
+                       contextL.setText(context.getFileName().toString());
+                       contextL.setFont(EclipseUiUtils.getBoldFont(parent));
+                       addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString());
+                       addProperty(parent, "Owner", Files.getOwner(context).getName());
+                       if (Files.isDirectory(context)) {
+                               addProperty(parent, "Type", "Folder");
+                       } else {
+                               String mimeType = Files.probeContentType(context);
+                               if (EclipseUiUtils.isEmpty(mimeType))
+                                       mimeType = "<i>Unknown</i>";
+                               addProperty(parent, "Type", mimeType);
+                               addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false));
+                       }
+                       parent.layout(true, true);
+               } catch (IOException e) {
+                       throw new FsUiException("Cannot display details for " + context, e);
+               }
+       }
+
+       private void addProperty(Composite parent, String propName, String value) {
+               Label contextL = new Label(parent, SWT.NONE);
+               contextL.setText(propName + ": " + value);
+       }
+
+       /**
+        * Almost canonical implementation of a table that displays the content of a
+        * directory
+        */
+       private class FilterEntitiesVirtualTable extends Composite {
+               private static final long serialVersionUID = 2223410043691844875L;
+
+               // Context
+               private Path context;
+               private Path currSelected = null;
+
+               // UI Objects
+               private FsTableViewer viewer;
+
+               @Override
+               public boolean setFocus() {
+                       if (viewer.getTable().isDisposed())
+                               return false;
+                       if (currSelected != null)
+                               viewer.setSelection(new StructuredSelection(currSelected), true);
+                       else if (viewer.getSelection().isEmpty()) {
+                               Object first = viewer.getElementAt(0);
+                               if (first != null)
+                                       viewer.setSelection(new StructuredSelection(first), true);
+                       }
+                       return viewer.getTable().setFocus();
+               }
+
+               /**
+                * Enable highlighting the correct element in the table when externally browsing
+                * (typically via the command-line-like Text field)
+                */
+               void setSelected(Path selected) {
+                       // to prevent change selection event to be thrown
+                       currSelected = selected;
+                       viewer.setSelection(new StructuredSelection(currSelected), true);
+               }
+
+               void filterList(String filter) {
+                       viewer.setInput(context, filter);
+               }
+
+               public FilterEntitiesVirtualTable(Composite parent, int style, Path context) {
+                       super(parent, SWT.NO_FOCUS);
+                       this.context = context;
+                       createTableViewer(this);
+               }
+
+               private void createTableViewer(final Composite parent) {
+                       parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+                       // We must limit the size of the table otherwise the full list is
+                       // loaded before the layout happens
+                       // Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
+                       // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
+                       // gd.widthHint = COLUMN_WIDTH;
+                       // listCmp.setLayoutData(gd);
+                       // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
+                       // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
+                       // SWT.V_SCROLL);
+                       // Table table = viewer.getTable();
+                       // table.setLayoutData(EclipseUiUtils.fillAll());
+
+                       viewer = new FsTableViewer(parent, SWT.MULTI);
+                       Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH);
+
+                       viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+
+                               @Override
+                               public void selectionChanged(SelectionChangedEvent event) {
+                                       IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+                                       if (selection.isEmpty())
+                                               return;
+                                       Object obj = selection.getFirstElement();
+                                       Path newSelected;
+                                       if (obj instanceof Path)
+                                               newSelected = (Path) obj;
+                                       else if (obj instanceof ParentDir)
+                                               newSelected = ((ParentDir) obj).getPath();
+                                       else
+                                               return;
+                                       if (newSelected.equals(currSelected))
+                                               return;
+                                       currSelected = newSelected;
+                                       setEdited(newSelected);
+
+                               }
+                       });
+
+                       table.addKeyListener(new KeyListener() {
+                               private static final long serialVersionUID = -8083424284436715709L;
+
+                               @Override
+                               public void keyReleased(KeyEvent e) {
+                               }
+
+                               @Override
+                               public void keyPressed(KeyEvent e) {
+                                       IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
+                                       Path selected = null;
+                                       if (!selection.isEmpty())
+                                               selected = ((Path) selection.getFirstElement());
+                                       if (e.keyCode == SWT.ARROW_RIGHT) {
+                                               if (!Files.isDirectory(selected))
+                                                       return;
+                                               if (selected != null) {
+                                                       setEdited(selected);
+                                                       browserCols.get(selected).setFocus();
+                                               }
+                                       } else if (e.keyCode == SWT.ARROW_LEFT) {
+                                               if (context.equals(initialPath))
+                                                       return;
+                                               Path parent = context.getParent();
+                                               if (parent == null)
+                                                       return;
+
+                                               setEdited(parent);
+                                               browserCols.get(parent).setFocus();
+                                       }
+                               }
+                       });
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java
new file mode 100644 (file)
index 0000000..d3fc1c9
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/** Basic label provider with icon for NIO file viewers */
+public class FileIconNameLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = 8187902187946523148L;
+
+       private Image folderIcon;
+       private Image fileIcon;
+
+       public FileIconNameLabelProvider() {
+               // if (!PlatformUI.isWorkbenchRunning()) {
+               folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage();
+               fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage();
+               // }
+       }
+
+       @Override
+       public void dispose() {
+               if (folderIcon != null)
+                       folderIcon.dispose();
+               if (fileIcon != null)
+                       fileIcon.dispose();
+               super.dispose();
+       }
+
+       @Override
+       public String getText(Object element) {
+               if (element instanceof Path) {
+                       Path curr = ((Path) element);
+                       Path name = curr.getFileName();
+                       if (name == null)
+                               return "[No name]";
+                       else
+                               return name.toString();
+               } else if (element instanceof ParentDir) {
+                       return "..";
+               }
+               return null;
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               if (element instanceof Path) {
+                       Path curr = ((Path) element);
+                       if (Files.isDirectory(curr))
+                               // if (folderIcon != null)
+                               return folderIcon;
+                       // else
+                       // return
+                       // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
+                       // else if (fileIcon != null)
+                       return fileIcon;
+                       // else
+                       // return
+                       // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
+               } else if (element instanceof ParentDir) {
+                       return folderIcon;
+               }
+               return null;
+       }
+
+       @Override
+       public String getToolTipText(Object element) {
+               if (element instanceof Path) {
+                       Path curr = ((Path) element);
+                       Path name = curr.getFileName();
+                       if (name == null)
+                               return "[No name]";
+                       else
+                               return name.toAbsolutePath().toString();
+               } else if (element instanceof ParentDir) {
+                       return ((ParentDir) element).getPath().toAbsolutePath().toString();
+               }
+               return null;
+       }
+
+}
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java
new file mode 100644 (file)
index 0000000..3b126e9
--- /dev/null
@@ -0,0 +1,141 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Path;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.eclipse.jface.viewers.CellLabelProvider;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+
+/**
+ * Canonical implementation of a JFace table viewer to display the content of a
+ * file folder
+ */
+public class FsTableViewer extends TableViewer {
+       private static final long serialVersionUID = -5632407542678477234L;
+
+       private boolean showHiddenItems = false;
+       private boolean folderFirst = true;
+       private boolean reverseOrder = false;
+       private String orderProperty = FsUiConstants.PROPERTY_NAME;
+
+       private Path initialPath = null;
+
+       public FsTableViewer(Composite parent, int style) {
+               super(parent, style | SWT.VIRTUAL);
+       }
+
+       public Table configureDefaultSingleColumnTable(int tableWidthHint) {
+
+               return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider());
+       }
+
+       public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) {
+               Table table = this.getTable();
+               table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               table.setLinesVisible(false);
+               table.setHeaderVisible(false);
+               // CmsUtils.markup(table);
+               // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
+
+               TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
+               TableColumn tcol = column.getColumn();
+               tcol.setWidth(tableWidthHint);
+               column.setLabelProvider(labelProvider);
+               this.setContentProvider(new MyLazyCP());
+               return table;
+       }
+
+       public Table configureDefaultTable(List<ColumnDefinition> columns) {
+               this.setContentProvider(new MyLazyCP());
+               Table table = this.getTable();
+               table.setLinesVisible(true);
+               table.setHeaderVisible(true);
+               // CmsUtils.markup(table);
+               // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
+               for (ColumnDefinition colDef : columns) {
+                       TableViewerColumn column = new TableViewerColumn(this, SWT.NONE);
+                       column.setLabelProvider(colDef.getLabelProvider());
+                       TableColumn tcol = column.getColumn();
+                       tcol.setResizable(true);
+                       tcol.setText(colDef.getLabel());
+                       tcol.setWidth(colDef.getMinWidth());
+               }
+               return table;
+       }
+
+       public void setInput(Path dir, String filter) {
+               Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder);
+               if (rows == null) {
+                       this.setInput(null);
+                       this.setItemCount(0);
+                       return;
+               }
+               boolean isRoot;
+               try {
+                       isRoot = dir.getRoot().equals(dir);
+               } catch (Exception e) {
+                       // FIXME Workaround for JCR root node access
+                       isRoot = dir.toString().equals("/");
+               }
+               final Object[] res;
+               if (isRoot)
+                       res = rows;
+               else if (initialPath != null && initialPath.equals(dir))
+                       res = rows;
+               else {
+                       res = new Object[rows.length + 1];
+                       res[0] = new ParentDir(dir.getParent());
+                       for (int i = 1; i < res.length; i++) {
+                               res[i] = rows[i - 1];
+                       }
+               }
+               this.setInput(res);
+               int length = res.length;
+               this.setItemCount(length);
+               this.refresh();
+       }
+
+       /** Directly displays bookmarks **/
+       public void setPathsInput(Path... paths) {
+               this.setInput((Object[]) paths);
+               this.setItemCount(paths.length);
+               this.refresh();
+       }
+
+       /**
+        * A path which is to be considered as root (and thus provide no link to a
+        * parent directory)
+        */
+       public void setInitialPath(Path initialPath) {
+               this.initialPath = initialPath;
+       }
+
+       private class MyLazyCP implements ILazyContentProvider {
+               private static final long serialVersionUID = 9096550041395433128L;
+               private Object[] elements;
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+                       // IMPORTANT: don't forget this: an exception will be thrown if
+                       // a selected object is not part of the results anymore.
+                       viewer.setSelection(null);
+                       this.elements = (Object[]) newInput;
+               }
+
+               public void updateElement(int index) {
+                       if (index < elements.length)
+                               FsTableViewer.this.replace(elements[index], index);
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java
new file mode 100644 (file)
index 0000000..f55ead7
--- /dev/null
@@ -0,0 +1,144 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Canonical implementation of a JFace TreeViewer to display the content of a
+ * repository
+ */
+public class FsTreeViewer extends TreeViewer {
+       private static final long serialVersionUID = -5632407542678477234L;
+
+       private boolean showHiddenItems = false;
+       private boolean showDirectoryFirst = true;
+       private String orderingProperty = FsUiConstants.PROPERTY_NAME;
+
+       public FsTreeViewer(Composite parent, int style) {
+               super(parent, style | SWT.VIRTUAL);
+       }
+
+       public Tree configureDefaultSingleColumnTable(int tableWidthHint) {
+               Tree tree = this.getTree();
+               tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               tree.setLinesVisible(true);
+               tree.setHeaderVisible(false);
+//             CmsUtils.markup(tree);
+
+               TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
+               TreeColumn tcol = column.getColumn();
+               tcol.setWidth(tableWidthHint);
+               column.setLabelProvider(new FileIconNameLabelProvider());
+
+               this.setContentProvider(new MyCP());
+               return tree;
+       }
+
+       public Tree configureDefaultTable(List<ColumnDefinition> columns) {
+               this.setContentProvider(new MyCP());
+               Tree tree = this.getTree();
+               tree.setLinesVisible(true);
+               tree.setHeaderVisible(true);
+//             CmsUtils.markup(tree);
+//             CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN);
+               for (ColumnDefinition colDef : columns) {
+                       TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE);
+                       column.setLabelProvider(colDef.getLabelProvider());
+                       TreeColumn tcol = column.getColumn();
+                       tcol.setResizable(true);
+                       tcol.setText(colDef.getLabel());
+                       tcol.setWidth(colDef.getMinWidth());
+               }
+               return tree;
+       }
+
+       public void setInput(Path dir, String filter) {
+               try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
+                       // TODO make this lazy
+                       List<Path> paths = new ArrayList<>();
+                       for (Path entry : stream) {
+                               paths.add(entry);
+                       }
+                       Object[] rows = paths.toArray(new Object[0]);
+                       this.setInput(rows);
+                       // this.setItemCount(rows.length);
+                       this.refresh();
+               } catch (IOException | DirectoryIteratorException e) {
+                       throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e);
+               }
+       }
+
+       /** Directly displays bookmarks **/
+       public void setPathsInput(Path... paths) {
+               this.setInput((Object[]) paths);
+               // this.setItemCount(paths.length);
+               this.refresh();
+       }
+
+       private class MyCP implements ITreeContentProvider {
+               private static final long serialVersionUID = 9096550041395433128L;
+               private Object[] elements;
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+                       // IMPORTANT: don't forget this: an exception will be thrown if
+                       // a selected object is not part of the results anymore.
+                       viewer.setSelection(null);
+                       this.elements = (Object[]) newInput;
+               }
+
+               @Override
+               public Object[] getElements(Object inputElement) {
+                       return elements;
+               }
+
+               @Override
+               public Object[] getChildren(Object parentElement) {
+                       Path path = (Path) parentElement;
+                       if (!Files.isDirectory(path))
+                               return null;
+                       else
+                               return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false);
+               }
+
+               @Override
+               public Object getParent(Object element) {
+                       Path path = (Path) element;
+                       return path.getParent();
+               }
+
+               @Override
+               public boolean hasChildren(Object element) {
+                       Path path = (Path) element;
+                       try {
+                               if (!Files.isDirectory(path))
+                                       return false;
+                               else
+                                       try (DirectoryStream<Path> children = Files.newDirectoryStream(path, "*")) {
+                                               return children.iterator().hasNext();
+                                       }
+                       } catch (IOException e) {
+                               throw new FsUiException("Unable to check child existence on " + path, e);
+                       }
+               }
+
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java
new file mode 100644 (file)
index 0000000..2b51e71
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.eclipse.ui.fs;
+
+/** Centralize constants used by the Nio FS UI parts */
+public interface FsUiConstants {
+
+       // TODO use standard properties
+       String PROPERTY_NAME = "name";
+       String PROPERTY_SIZE = "size";
+       String PROPERTY_LAST_MODIFIED = "last-modified";
+       String PROPERTY_TYPE = "type";
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java
new file mode 100644 (file)
index 0000000..422b0e1
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.eclipse.ui.fs;
+
+/** Files specific exception */
+public class FsUiException extends RuntimeException {
+       private static final long serialVersionUID = 1L;
+
+       public FsUiException(String message) {
+               super(message);
+       }
+
+       public FsUiException(String message, Throwable e) {
+               super(message, e);
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java
new file mode 100644 (file)
index 0000000..956d96b
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.DirectoryIteratorException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Centralise additional utilitary methods to manage Java7 NIO files */
+public class FsUiUtils {
+
+       /**
+        * thanks to
+        * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html
+        */
+       public static String humanReadableByteCount(long bytes, boolean si) {
+               int unit = si ? 1000 : 1024;
+               if (bytes < unit)
+                       return bytes + " B";
+               int exp = (int) (Math.log(bytes) / Math.log(unit));
+               String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
+               return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
+       }
+
+       public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst,
+                       String orderProperty, boolean reverseOrder) {
+               if (!Files.isDirectory(parent))
+                       return null;
+               List<Pair> pairs = new ArrayList<>();
+               try (DirectoryStream<Path> stream = Files.newDirectoryStream(parent, filter)) {
+                       loop: for (Path entry : stream) {
+                               if (!showHiddenItems)
+                                       if (Files.isHidden(entry))
+                                               continue loop;
+                               switch (orderProperty) {
+                               case FsUiConstants.PROPERTY_SIZE:
+                                       if (folderFirst)
+                                               pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry)));
+                                       else
+                                               pairs.add(new LPair(entry, Files.size(entry)));
+                                       break;
+                               case FsUiConstants.PROPERTY_LAST_MODIFIED:
+                                       if (folderFirst)
+                                               pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(),
+                                                               Files.isDirectory(entry)));
+                                       else
+                                               pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis()));
+                                       break;
+                               case FsUiConstants.PROPERTY_NAME:
+                                       if (folderFirst)
+                                               pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry)));
+                                       else
+                                               pairs.add(new SPair(entry, entry.getFileName().toString()));
+                                       break;
+                               default:
+                                       throw new FsUiException("Unable to prepare sort for property " + orderProperty);
+                               }
+                       }
+                       Pair[] rows = pairs.toArray(new Pair[0]);
+                       Arrays.sort(rows);
+                       Path[] results = new Path[rows.length];
+                       if (reverseOrder) {
+                               int j = rows.length - 1;
+                               for (int i = 0; i < rows.length; i++)
+                                       results[i] = rows[j - i].p;
+                       } else
+                               for (int i = 0; i < rows.length; i++)
+                                       results[i] = rows[i].p;
+                       return results;
+               } catch (IOException | DirectoryIteratorException e) {
+                       throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e);
+               }
+       }
+
+       static abstract class Pair implements Comparable<Object> {
+               Path p;
+               Boolean i;
+       };
+
+       static class LPair extends Pair {
+               long v;
+
+               public LPair(Path path, long propValue) {
+                       p = path;
+                       v = propValue;
+               }
+
+               public LPair(Path path, long propValue, boolean isDir) {
+                       p = path;
+                       v = propValue;
+                       i = isDir;
+               }
+
+               public int compareTo(Object o) {
+                       if (i != null) {
+                               Boolean j = ((LPair) o).i;
+                               if (i.booleanValue() != j.booleanValue())
+                                       return i.booleanValue() ? -1 : 1;
+                       }
+                       long u = ((LPair) o).v;
+                       return v < u ? -1 : v == u ? 0 : 1;
+               }
+       };
+
+       static class SPair extends Pair {
+               String v;
+
+               public SPair(Path path, String propValue) {
+                       p = path;
+                       v = propValue;
+               }
+
+               public SPair(Path path, String propValue, boolean isDir) {
+                       p = path;
+                       v = propValue;
+                       i = isDir;
+               }
+
+               public int compareTo(Object o) {
+                       if (i != null) {
+                               Boolean j = ((SPair) o).i;
+                               if (i.booleanValue() != j.booleanValue())
+                                       return i.booleanValue() ? -1 : 1;
+                       }
+                       String u = ((SPair) o).v;
+                       return v.compareTo(u);
+               }
+       };
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java
new file mode 100644 (file)
index 0000000..2bb65ee
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/** Expect a {@link Path} as input element */
+public class NioFileLabelProvider extends ColumnLabelProvider {
+       private final static FileTime EPOCH = FileTime.fromMillis(0);
+       private static final long serialVersionUID = 2160026425187796930L;
+       private final String propName;
+       private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
+
+       // TODO use new formatting
+       // DateTimeFormatter formatter =
+       // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT )
+       // .withLocale( Locale.UK )
+       // .withZone( ZoneId.systemDefault() );
+       public NioFileLabelProvider(String propName) {
+               this.propName = propName;
+       }
+
+       @Override
+       public String getText(Object element) {
+               try {
+                       Path path;
+                       if (element instanceof ParentDir) {
+//                             switch (propName) {
+//                             case FsUiConstants.PROPERTY_SIZE:
+//                                     return "-";
+//                             case FsUiConstants.PROPERTY_LAST_MODIFIED:
+//                                     return "-";
+//                             // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString();
+//                             case FsUiConstants.PROPERTY_TYPE:
+//                                     return "Folder";
+//                             }
+                               path = ((ParentDir) element).getPath();
+                       } else
+                               path = (Path) element;
+                       switch (propName) {
+                       case FsUiConstants.PROPERTY_SIZE:
+                               if (Files.isDirectory(path))
+                                       return "-";
+                               else
+                                       return FsUiUtils.humanReadableByteCount(Files.size(path), false);
+                       case FsUiConstants.PROPERTY_LAST_MODIFIED:
+                               if (Files.isDirectory(path))
+                                       return "-";
+                               FileTime time = Files.getLastModifiedTime(path);
+                               if (time.equals(EPOCH))
+                                       return "-";
+                               else
+                                       return dateFormat.format(new Date(time.toMillis()));
+                       case FsUiConstants.PROPERTY_TYPE:
+                               if (Files.isDirectory(path))
+                                       return "Folder";
+                               else {
+                                       String mimeType = Files.probeContentType(path);
+                                       if (EclipseUiUtils.isEmpty(mimeType))
+                                               return "Unknown";
+                                       else
+                                               return mimeType;
+                               }
+                       default:
+                               throw new IllegalArgumentException("Unsupported property " + propName);
+                       }
+               } catch (IOException ioe) {
+                       throw new FsUiException("Cannot get property " + propName + " on " + element);
+               }
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java
new file mode 100644 (file)
index 0000000..6f09c29
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Path;
+
+/** A parent directory (..) reference. */
+public class ParentDir {
+       Path path;
+
+       public ParentDir(Path path) {
+               super();
+               this.path = path;
+       }
+
+       public Path getPath() {
+               return path;
+       }
+
+       @Override
+       public int hashCode() {
+               return path.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "Parent dir " + path;
+       }
+
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java
new file mode 100644 (file)
index 0000000..2e3d6b4
--- /dev/null
@@ -0,0 +1,211 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Table;
+
+/**
+ * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on
+ * the left hand side and a simple table on the right hand side.
+ */
+public class SimpleFsBrowser extends Composite {
+       private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class);
+       private static final long serialVersionUID = -40347919096946585L;
+
+       private Path currSelected;
+       private FsTableViewer bookmarksViewer;
+       private FsTableViewer directoryDisplayViewer;
+
+       public SimpleFsBrowser(Composite parent, int style) {
+               super(parent, style);
+               createContent(this);
+               // parent.layout(true, true);
+       }
+
+       public Viewer getViewer() {
+               return directoryDisplayViewer;
+       }
+
+       private void createContent(Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+               Composite leftCmp = new Composite(form, SWT.NONE);
+               populateBookmarks(leftCmp);
+
+               Composite rightCmp = new Composite(form, SWT.BORDER);
+               populateDisplay(rightCmp);
+               form.setLayoutData(EclipseUiUtils.fillAll());
+               form.setWeights(new int[] { 1, 3 });
+       }
+
+       public void setInput(Path... paths) {
+               bookmarksViewer.setPathsInput(paths);
+               bookmarksViewer.getTable().getParent().layout(true, true);
+       }
+
+       private void populateBookmarks(final Composite parent) {
+               // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+               // layout.verticalSpacing = 5;
+               parent.setLayout(new GridLayout());
+
+               ISelectionChangedListener selList = new MySelectionChangedListener();
+
+               appendTitle(parent, "My bookmarks");
+               bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
+               Table table = bookmarksViewer.configureDefaultSingleColumnTable(500);
+               GridData gd = EclipseUiUtils.fillWidth();
+               gd.horizontalIndent = 10;
+               table.setLayoutData(gd);
+               bookmarksViewer.addSelectionChangedListener(selList);
+
+               appendTitle(parent, "Jcr + File");
+
+               FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL);
+               table = jcrFilesViewers.configureDefaultSingleColumnTable(500);
+               gd = EclipseUiUtils.fillWidth();
+               gd.horizontalIndent = 10;
+               table.setLayoutData(gd);
+               jcrFilesViewers.addSelectionChangedListener(selList);
+
+               // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
+               // try {
+               // Path testPath = fsProvider.getPath(new URI("jcr+memory:/"));
+               // jcrFilesViewers.setPathsInput(testPath);
+               // } catch (URISyntaxException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // }
+       }
+
+       private Label appendTitle(Composite parent, String value) {
+               Label titleLbl = new Label(parent, SWT.NONE);
+               titleLbl.setText(value);
+               titleLbl.setFont(EclipseUiUtils.getBoldFont(parent));
+               GridData gd = EclipseUiUtils.fillWidth();
+               gd.horizontalIndent = 5;
+               gd.verticalIndent = 5;
+               titleLbl.setLayoutData(gd);
+               return titleLbl;
+       }
+
+       private class MySelectionChangedListener implements ISelectionChangedListener {
+               @Override
+               public void selectionChanged(SelectionChangedEvent event) {
+                       IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection();
+                       if (selection.isEmpty())
+                               return;
+                       else {
+                               Path newSelected = (Path) selection.getFirstElement();
+                               if (newSelected.equals(currSelected))
+                                       return;
+                               currSelected = newSelected;
+                               directoryDisplayViewer.setInput(currSelected, "*");
+                       }
+               }
+       }
+
+       private void populateDisplay(final Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+               List<ColumnDefinition> colDefs = new ArrayList<>();
+               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+                               "Last modified", 200));
+               Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+               table.setLayoutData(EclipseUiUtils.fillAll());
+
+               table.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = -8083424284436715709L;
+
+                       @Override
+                       public void keyReleased(KeyEvent e) {
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {
+                               log.debug("Key event received: " + e.keyCode);
+                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+                               Path selected = null;
+                               if (!selection.isEmpty())
+                                       selected = ((Path) selection.getFirstElement());
+                               if (e.keyCode == SWT.CR) {
+                                       if (!Files.isDirectory(selected))
+                                               return;
+                                       if (selected != null) {
+                                               currSelected = selected;
+                                               directoryDisplayViewer.setInput(currSelected, "*");
+                                       }
+                               } else if (e.keyCode == SWT.BS) {
+                                       currSelected = currSelected.getParent();
+                                       directoryDisplayViewer.setInput(currSelected, "*");
+                                       directoryDisplayViewer.getTable().setFocus();
+                               }
+                       }
+               });
+
+//             directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+//                     @Override
+//                     public void doubleClick(DoubleClickEvent event) {
+//                             IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+//                             Path selected = null;
+//                             if (!selection.isEmpty()) {
+//                                     Object obj = selection.getFirstElement();
+//                                     if (obj instanceof Path)
+//                                             selected = (Path) obj;
+//                                     else if (obj instanceof ParentDir)
+//                                             selected = ((ParentDir) obj).getPath();
+//                             }
+//                             if (selected != null) {
+//                                     if (!Files.isDirectory(selected))
+//                                             return;
+//                                     currSelected = selected;
+//                                     directoryDisplayViewer.setInput(currSelected, "*");
+//                             }
+//                     }
+//             });
+
+               directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
+                       @Override
+                       public void doubleClick(DoubleClickEvent event) {
+                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+                               Path selected = null;
+                               if (!selection.isEmpty()) {
+                                       Object obj = selection.getFirstElement();
+                                       if (obj instanceof Path)
+                                               selected = (Path) obj;
+                                       else if (obj instanceof ParentDir)
+                                               selected = ((ParentDir) obj).getPath();
+                               }
+                               if (selected != null) {
+                                       if (!Files.isDirectory(selected))
+                                               return;
+                                       currSelected = selected;
+                                       directoryDisplayViewer.setInput(currSelected, "*");
+                               }
+                       }
+               });
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java
new file mode 100644 (file)
index 0000000..401e5cb
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.eclipse.ui.fs;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.eclipse.ui.ColumnDefinition;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Tree;
+
+/** A simple Java 7 nio files browser with a tree */
+public class SimpleFsTreeBrowser extends Composite {
+       private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class);
+       private static final long serialVersionUID = -40347919096946585L;
+
+       private Path currSelected;
+       private FsTreeViewer treeViewer;
+       private FsTableViewer directoryDisplayViewer;
+
+       public SimpleFsTreeBrowser(Composite parent, int style) {
+               super(parent, style);
+               createContent(this);
+               // parent.layout(true, true);
+       }
+
+       private void createContent(Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               SashForm form = new SashForm(parent, SWT.HORIZONTAL);
+               Composite child1 = new Composite(form, SWT.NONE);
+               populateTree(child1);
+               Composite child2 = new Composite(form, SWT.BORDER);
+               populateDisplay(child2);
+               form.setLayoutData(EclipseUiUtils.fillAll());
+               form.setWeights(new int[] { 1, 3 });
+       }
+
+       public void setInput(Path... paths) {
+               treeViewer.setPathsInput(paths);
+               treeViewer.getControl().getParent().layout(true, true);
+       }
+
+       private void populateTree(final Composite parent) {
+               // GridLayout layout = EclipseUiUtils.noSpaceGridLayout();
+               // layout.verticalSpacing = 5;
+               parent.setLayout(new GridLayout());
+
+               ISelectionChangedListener selList = new MySelectionChangedListener();
+
+               treeViewer = new FsTreeViewer(parent, SWT.MULTI);
+               Tree tree = treeViewer.configureDefaultSingleColumnTable(500);
+               GridData gd = EclipseUiUtils.fillAll();
+               // gd.horizontalIndent = 10;
+               tree.setLayoutData(gd);
+               treeViewer.addSelectionChangedListener(selList);
+       }
+
+       private class MySelectionChangedListener implements ISelectionChangedListener {
+               @Override
+               public void selectionChanged(SelectionChangedEvent event) {
+                       IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();
+                       if (selection.isEmpty())
+                               return;
+                       else {
+                               Path newSelected = (Path) selection.getFirstElement();
+                               if (newSelected.equals(currSelected))
+                                       return;
+                               currSelected = newSelected;
+                               if (Files.isDirectory(currSelected))
+                                       directoryDisplayViewer.setInput(currSelected, "*");
+                       }
+               }
+       }
+
+       private void populateDisplay(final Composite parent) {
+               parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
+               directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
+               List<ColumnDefinition> colDefs = new ArrayList<>();
+               colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300));
+               colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
+                               "Last modified", 100, 100));
+               Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
+               table.setLayoutData(EclipseUiUtils.fillAll());
+
+               table.addKeyListener(new KeyListener() {
+                       private static final long serialVersionUID = -8083424284436715709L;
+
+                       @Override
+                       public void keyReleased(KeyEvent e) {
+                       }
+
+                       @Override
+                       public void keyPressed(KeyEvent e) {
+                               log.debug("Key event received: " + e.keyCode);
+                               IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
+                               Path selected = null;
+                               if (!selection.isEmpty())
+                                       selected = ((Path) selection.getFirstElement());
+                               if (e.keyCode == SWT.CR) {
+                                       if (!Files.isDirectory(selected))
+                                               return;
+                                       if (selected != null) {
+                                               currSelected = selected;
+                                               directoryDisplayViewer.setInput(currSelected, "*");
+                                       }
+                               } else if (e.keyCode == SWT.BS) {
+                                       currSelected = currSelected.getParent();
+                                       directoryDisplayViewer.setInput(currSelected, "*");
+                                       directoryDisplayViewer.getTable().setFocus();
+                               }
+                       }
+               });
+       }
+}
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png
new file mode 100644 (file)
index 0000000..ce2f2a8
Binary files /dev/null and b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png differ
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png
new file mode 100644 (file)
index 0000000..c31f37e
Binary files /dev/null and b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png differ
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java
new file mode 100644 (file)
index 0000000..d7f83c3
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic SWT/JFace file system utilities. */
+package org.argeo.eclipse.ui.fs;
\ No newline at end of file
diff --git a/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/swt/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java
new file mode 100644 (file)
index 0000000..0d245db
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic SWT/JFace utilities. */
+package org.argeo.eclipse.ui;
\ No newline at end of file
diff --git a/swt/org.argeo.swt.minidesktop/.classpath b/swt/org.argeo.swt.minidesktop/.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.swt.minidesktop/.gitignore b/swt/org.argeo.swt.minidesktop/.gitignore
new file mode 100644 (file)
index 0000000..97adb72
--- /dev/null
@@ -0,0 +1,3 @@
+/bin/
+/target/
+*.log
\ No newline at end of file
diff --git a/swt/org.argeo.swt.minidesktop/.project b/swt/org.argeo.swt.minidesktop/.project
new file mode 100644 (file)
index 0000000..b6c2c1a
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.minidesktop</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/org.argeo.swt.minidesktop/META-INF/.gitignore b/swt/org.argeo.swt.minidesktop/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/org.argeo.swt.minidesktop/bnd.bnd b/swt/org.argeo.swt.minidesktop/bnd.bnd
new file mode 100644 (file)
index 0000000..f3c13be
--- /dev/null
@@ -0,0 +1,2 @@
+Import-Package: org.eclipse.swt,\
+*
diff --git a/swt/org.argeo.swt.minidesktop/build.properties b/swt/org.argeo.swt.minidesktop/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.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java
new file mode 100644 (file)
index 0000000..e7bb9e8
--- /dev/null
@@ -0,0 +1,186 @@
+package org.argeo.minidesktop;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.LocationAdapter;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A very minimalistic web browser based on {@link Browser}. */
+public class MiniBrowser {
+       private static Point defaultShellSize = new Point(800, 480);
+
+       private Browser browser;
+       private Text addressT;
+
+       private final boolean fullscreen;
+       private final boolean appMode;
+
+       public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) {
+               this.fullscreen = fullscreen;
+               this.appMode = appMode;
+               createUi(composite);
+               setUrl(url);
+       }
+
+       public Control createUi(Composite parent) {
+               parent.setLayout(noSpaceGridLayout(new GridLayout()));
+               if (!isAppMode()) {
+                       Control toolBar = createToolBar(parent);
+                       toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               }
+               Control body = createBody(parent);
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               return body;
+       }
+
+       protected Control createToolBar(Composite parent) {
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayout(new FillLayout());
+               addressT = new Text(toolBar, SWT.SINGLE);
+               addressT.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               setUrl(addressT.getText().trim());
+                       }
+               });
+               return toolBar;
+       }
+
+       protected Control createBody(Composite parent) {
+               browser = new Browser(parent, SWT.NONE);
+               if (isFullScreen())
+                       browser.addKeyListener(new KeyAdapter() {
+                               @Override
+                               public void keyPressed(KeyEvent e) {
+                                       if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W
+                                               browser.getShell().dispose();
+                                       }
+                               }
+                       });
+               browser.addLocationListener(new LocationAdapter() {
+                       @Override
+                       public void changed(LocationEvent event) {
+                               System.out.println(event);
+                               if (addressT != null)
+                                       addressT.setText(event.location);
+                       }
+
+               });
+
+               MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserTitleListener(this, browser);
+               MiniDesktopSpecific.getMiniDesktopSpecific().addBrowserOpenWindowListener(this, browser);
+               return browser;
+       }
+
+       public Browser openNewBrowserWindow() {
+
+               if (isFullScreen()) {
+                       // TODO manage multiple tabs?
+                       return browser;
+               } else {
+                       Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM);
+                       MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode());
+                       newShell.setSize(defaultShellSize);
+                       newShell.open();
+                       return newMiniBrowser.browser;
+               }
+       }
+
+       protected boolean isFullScreen() {
+               return fullscreen;
+       }
+
+       void setUrl(String url) {
+               if (browser != null && url != null && !url.equals(browser.getUrl()))
+                       browser.setUrl(url.toString());
+       }
+
+       /** Called when URL changed; to be overridden, does nothing by default. */
+       protected void urlChanged(String url) {
+       }
+
+       /** Called when title changed; to be overridden, does nothing by default. */
+       public void titleChanged(String title) {
+       }
+
+       protected Browser getBrowser() {
+               return browser;
+       }
+
+       protected boolean isAppMode() {
+               return appMode;
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               List<String> options = Arrays.asList(args);
+               if (options.contains("--help")) {
+                       System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]");
+                       System.out.println("A minimalistic web browser Eclipse SWT Browser integration.");
+                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
+                       System.out.println("  --app        : open without an address bar and a toolbar");
+                       System.out.println("  --help       : print this help and exit");
+                       System.exit(1);
+               }
+               boolean fullscreen = options.contains("--fullscreen");
+               boolean appMode = options.contains("--app");
+               String url = "https://start.duckduckgo.com/";
+               if (options.size() > 0) {
+                       String last = options.get(options.size() - 1);
+                       if (!last.startsWith("--"))
+                               url = last.trim();
+               }
+
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell;
+               if (fullscreen) {
+                       shell = new Shell(display, SWT.NO_TRIM);
+                       shell.setFullScreen(true);
+                       Rectangle bounds = display.getBounds();
+                       shell.setSize(bounds.width, bounds.height);
+               } else {
+                       shell = new Shell(display, SWT.SHELL_TRIM);
+                       shell.setSize(defaultShellSize);
+               }
+
+               new MiniBrowser(shell, url, fullscreen, appMode) {
+
+                       @Override
+                       public void titleChanged(String title) {
+                               shell.setText(title);
+                       }
+               };
+               shell.open();
+
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java
new file mode 100644 (file)
index 0000000..db5e6f2
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+/** Icons. */
+public class MiniDesktopImages {
+
+       public final Image homeIcon;
+       public final Image exitIcon;
+       
+       public final Image terminalIcon;
+       public final Image browserIcon;
+       public final Image explorerIcon;
+       public final Image textEditorIcon;
+
+       public final Image folderIcon;
+       public final Image fileIcon;
+
+       public MiniDesktopImages(Display display) {
+               homeIcon = loadImage(display, "nav_home@2x.png");
+               exitIcon = loadImage(display, "delete@2x.png");
+
+               terminalIcon = loadImage(display, "console_view@2x.png");
+               browserIcon = loadImage(display, "external_browser@2x.png");
+               explorerIcon = loadImage(display, "fldr_obj@2x.png");
+               textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png");
+
+               folderIcon = loadImage(display, "fldr_obj@2x.png");
+               fileIcon = loadImage(display, "file_obj@2x.png");
+       }
+
+       static Image loadImage(Display display, String path) {
+               InputStream stream = MiniDesktopImages.class.getResourceAsStream(path);
+               if (stream == null)
+                       throw new IllegalArgumentException("Image " + path + " not found");
+               Image image = null;
+               try {
+                       image = new Image(display, stream);
+               } catch (SWTException ex) {
+               } finally {
+                       try {
+                               stream.close();
+                       } catch (IOException ex) {
+                       }
+               }
+               return image;
+       }
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java
new file mode 100644 (file)
index 0000000..adb2a55
--- /dev/null
@@ -0,0 +1,347 @@
+package org.argeo.minidesktop;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** A very minimalistic desktop manager based on Java and Eclipse SWT. */
+public class MiniDesktopManager {
+       private Display display;
+
+       private Shell rootShell;
+       private Shell toolBarShell;
+       private CTabFolder tabFolder;
+       private int maxTabTitleLength = 16;
+
+       private final boolean fullscreen;
+       private final boolean stacking;
+
+       private MiniDesktopImages images;
+
+       public MiniDesktopManager(boolean fullscreen, boolean stacking) {
+               this.fullscreen = fullscreen;
+               this.stacking = stacking;
+       }
+
+       public void init() {
+               Display.setAppName("Mini SWT Desktop");
+               display = Display.getCurrent();
+               if (display != null)
+                       throw new IllegalStateException("Already a display " + display);
+               display = new Display();
+
+               if (display.getTouchEnabled()) {
+                       System.out.println("Touch enabled.");
+               }
+
+               images = new MiniDesktopImages(display);
+
+               int toolBarSize = 48;
+
+               if (isFullscreen()) {
+                       rootShell = new Shell(display, SWT.NO_TRIM);
+                       rootShell.setFullScreen(true);
+                       Rectangle bounds = display.getBounds();
+                       rootShell.setLocation(0, 0);
+                       rootShell.setSize(bounds.width, bounds.height);
+               } else {
+                       rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
+                       Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
+                       rootShell.setSize(shellArea.width, shellArea.height);
+                       rootShell.setText(Display.getAppName());
+                       rootShell.setImage(images.terminalIcon);
+               }
+
+               rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
+               Composite toolBarArea = new Composite(rootShell, SWT.NONE);
+               toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
+
+               ToolBar toolBar;
+               if (isFullscreen()) {
+                       toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
+                       toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                       createDock(toolBar);
+                       toolBarShell.pack();
+                       toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
+               } else {
+                       toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                       createDock(toolBar);
+                       toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+               }
+
+               if (isStacking()) {
+                       tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
+                       tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
+                       tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+                       Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+                       tabFolder.setSelectionBackground(selectionBackground);
+
+                       // background
+                       Control background = createBackground(tabFolder);
+                       CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
+                       homeTabItem.setText("Home");
+                       homeTabItem.setImage(images.homeIcon);
+                       homeTabItem.setControl(background);
+                       tabFolder.setFocus();
+               } else {
+                       createBackground(rootShell);
+               }
+
+               rootShell.open();
+               // rootShell.layout(true, true);
+
+               if (toolBarShell != null) {
+                       int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
+                       toolBarShell.setLocation(0, toolBarShellY);
+                       toolBarShell.open();
+               }
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
+       }
+
+       protected void createDock(ToolBar toolBar) {
+               // Terminal
+               addToolItem(toolBar, images.terminalIcon, "Terminal", () -> {
+                       String url = System.getProperty("user.home");
+                       AppContext appContext = createAppParent(images.terminalIcon);
+                       new MiniTerminal(appContext.getAppParent(), url) {
+
+                               @Override
+                               protected void exitCalled() {
+                                       if (appContext.shell != null)
+                                               appContext.shell.dispose();
+                                       if (appContext.tabItem != null)
+                                               appContext.tabItem.dispose();
+                               }
+                       };
+                       String title;
+                       try {
+                               title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
+                       } catch (UnknownHostException e) {
+                               title = System.getProperty("user.name") + "@localhost";
+                       }
+                       if (appContext.shell != null)
+                               appContext.shell.setText(title);
+                       if (appContext.tabItem != null) {
+                               appContext.tabItem.setText(tabTitle(title));
+                               appContext.tabItem.setToolTipText(title);
+                       }
+                       openApp(appContext);
+               });
+
+               // Web browser
+               addToolItem(toolBar, images.browserIcon, "Browser", () -> {
+                       String url = "https://start.duckduckgo.com/";
+                       AppContext appContext = createAppParent(images.browserIcon);
+                       new MiniBrowser(appContext.getAppParent(), url, false, false) {
+                               @Override
+                               public void titleChanged(String title) {
+                                       if (appContext.shell != null)
+                                               appContext.shell.setText(title);
+                                       if (appContext.tabItem != null) {
+                                               appContext.tabItem.setText(tabTitle(title));
+                                               appContext.tabItem.setToolTipText(title);
+                                       }
+                               }
+                       };
+                       openApp(appContext);
+               });
+
+               // File explorer
+               addToolItem(toolBar, images.explorerIcon, "Explorer", () -> {
+                       String url = System.getProperty("user.home");
+                       AppContext appContext = createAppParent(images.explorerIcon);
+                       new MiniExplorer(appContext.getAppParent(), url) {
+
+                               @Override
+                               protected void pathChanged(Path path) {
+                                       if (appContext.shell != null)
+                                               appContext.shell.setText(path.toString());
+                                       if (appContext.tabItem != null) {
+                                               appContext.tabItem.setText(path.getFileName().toString());
+                                               appContext.tabItem.setToolTipText(path.toString());
+                                       }
+                               }
+                       };
+                       openApp(appContext);
+               });
+
+               // Separator
+               new ToolItem(toolBar, SWT.SEPARATOR);
+
+               // Exit
+               addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose());
+
+               toolBar.pack();
+       }
+
+       protected String tabTitle(String title) {
+               return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title;
+       }
+
+       protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) {
+               ToolItem searchI = new ToolItem(toolBar, SWT.PUSH);
+               searchI.setImage(icon);
+               searchI.setToolTipText(name);
+               searchI.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               action.run();
+                       }
+
+               });
+       }
+
+       protected AppContext createAppParent(Image icon) {
+               if (isStacking()) {
+                       Composite appParent = new Composite(tabFolder, SWT.CLOSE);
+                       appParent.setLayout(noSpaceGridLayout(new GridLayout()));
+                       CTabItem item = new CTabItem(tabFolder, SWT.CLOSE);
+                       item.setImage(icon);
+                       item.setControl(appParent);
+                       return new AppContext(item);
+               } else {
+                       Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM)
+                                       : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM);
+                       shell.setImage(icon);
+                       return new AppContext(shell);
+               }
+       }
+
+       protected void openApp(AppContext appContext) {
+               if (appContext.shell != null) {
+                       Shell shell = (Shell) appContext.shell;
+                       shell.open();
+                       shell.setSize(new Point(800, 480));
+               }
+               if (appContext.tabItem != null) {
+                       tabFolder.setFocus();
+                       tabFolder.setSelection(appContext.tabItem);
+               }
+       }
+
+       protected Control createBackground(Composite parent) {
+               Composite backgroundArea = new Composite(parent, SWT.NONE);
+               backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               initBackground(backgroundArea);
+               return backgroundArea;
+       }
+
+       protected void initBackground(Composite backgroundArea) {
+               MiniHomePart homePart = new MiniHomePart() {
+
+                       @Override
+                       protected void fillAppsToolBar(ToolBar toolBar) {
+                               createDock(toolBar);
+                       }
+               };
+               homePart.createUiPart(backgroundArea, null);
+       }
+
+       public void run() {
+               while (!rootShell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+       public void dispose() {
+               if (!rootShell.isDisposed())
+                       rootShell.dispose();
+       }
+
+       protected boolean isFullscreen() {
+               return fullscreen;
+       }
+
+       protected boolean isStacking() {
+               return stacking;
+       }
+
+       protected Image getIconForExt(String ext) {
+//             Program program = Program.findProgram(ext);
+//             if (program == null)
+               return display.getSystemImage(SWT.ICON_INFORMATION);
+
+//             ImageData iconData = program.getImageData();
+//             if (iconData == null) {
+//                     return display.getSystemImage(SWT.ICON_INFORMATION);
+//             } else {
+//                     return new Image(display, iconData);
+//             }
+
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               List<String> options = Arrays.asList(args);
+               if (options.contains("--help")) {
+                       System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]");
+                       System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT.");
+                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
+                       System.out.println("  --stacking   : open apps as tabs (default is to create new windows)");
+                       System.out.println("  --help       : print this help and exit");
+                       System.exit(1);
+               }
+               boolean fullscreen = options.contains("--fullscreen");
+               boolean stacking = options.contains("--stacking");
+
+               MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
+               desktopManager.init();
+               desktopManager.run();
+               desktopManager.dispose();
+               System.exit(0);
+       }
+
+       class AppContext {
+               private Shell shell;
+               private CTabItem tabItem;
+
+               public AppContext(Shell shell) {
+                       this.shell = shell;
+               }
+
+               public AppContext(CTabItem tabItem) {
+                       this.tabItem = tabItem;
+               }
+
+               Composite getAppParent() {
+                       if (shell != null)
+                               return shell;
+                       if (tabItem != null)
+                               return (Composite) tabItem.getControl();
+                       throw new IllegalStateException();
+               }
+       }
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopSpecific.java
new file mode 100644 (file)
index 0000000..c748822
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.minidesktop;
+
+import java.util.Objects;
+
+import org.eclipse.swt.browser.Browser;
+
+/**
+ * This customiser is available to all components, in order to be extended with
+ * low-level specific capabilities, which depend on the context (typically
+ * differences between RAP and RCP). It does nothing by default.
+ */
+public class MiniDesktopSpecific {
+       protected void addBrowserTitleListener(MiniBrowser miniBrowser, Browser browser) {
+       }
+
+       protected void addBrowserOpenWindowListener(MiniBrowser miniBrowser, Browser browser) {
+       }
+
+       private static MiniDesktopSpecific SINGLETON = new MiniDesktopSpecific();
+
+       public static void setMiniDesktopSpecific(MiniDesktopSpecific miniDesktopSpecific) {
+               Objects.requireNonNull(miniDesktopSpecific);
+               SINGLETON = miniDesktopSpecific;
+       }
+
+       static MiniDesktopSpecific getMiniDesktopSpecific() {
+               return SINGLETON;
+       }
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java
new file mode 100644 (file)
index 0000000..9a967a2
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniExplorer {
+       private Path path;
+       private Text addressT;
+       private Table browser;
+
+       private boolean showHidden = false;
+
+       public MiniExplorer(Composite parent, String url) {
+               this(parent);
+               setUrl(url);
+       }
+
+       public MiniExplorer(Composite parent) {
+               parent.setLayout(noSpaceGridLayout(new GridLayout()));
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new FillLayout());
+               addressT = new Text(toolBar, SWT.SINGLE);
+               addressT.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               setUrl(addressT.getText().trim());
+                       }
+               });
+               browser = createTable(parent, this.path);
+
+       }
+
+       public void setPath(Path url) {
+               this.path = url;
+               if (addressT != null)
+                       addressT.setText(url.toString());
+               if (browser != null) {
+                       Composite parent = browser.getParent();
+                       browser.dispose();
+                       browser = createTable(parent, this.path);
+                       parent.layout(true, true);
+               }
+               pathChanged(url);
+       }
+
+       protected void pathChanged(Path path) {
+
+       }
+
+       protected Table createTable(Composite parent, Path path) {
+               Table table = new Table(parent, SWT.BORDER);
+               table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               table.addMouseListener(new MouseAdapter() {
+
+                       @Override
+                       public void mouseDoubleClick(MouseEvent e) {
+                               Point pt = new Point(e.x, e.y);
+                               TableItem item = table.getItem(pt);
+                               Path path = (Path) item.getData();
+                               if (Files.isDirectory(path)) {
+                                       setPath(path);
+                               } else {
+                                       //Program.launch(path.toString());
+                               }
+                       }
+               });
+
+               if (path != null) {
+                       if (path.getParent() != null) {
+                               TableItem parentTI = new TableItem(table, SWT.NONE);
+                               parentTI.setText("..");
+                               parentTI.setData(path.getParent());
+                       }
+
+                       try {
+                               // directories
+                               DirectoryStream<Path> ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p));
+                               ds.forEach(p -> {
+                                       TableItem ti = new TableItem(table, SWT.NONE);
+                                       ti.setText(p.getFileName().toString() + "/");
+                                       ti.setData(p);
+                               });
+                               // files
+                               ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p));
+                               ds.forEach(p -> {
+                                       TableItem ti = new TableItem(table, SWT.NONE);
+                                       ti.setText(p.getFileName().toString());
+                                       ti.setData(p);
+                               });
+                       } catch (IOException e1) {
+                               // TODO Auto-generated catch block
+                               e1.printStackTrace();
+                       }
+               }
+               return table;
+       }
+
+       protected boolean isShown(Path path) {
+               if (showHidden)
+                       return true;
+               try {
+                       return !Files.isHidden(path);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot check " + path, e);
+               }
+       }
+
+       public void setUrl(String url) {
+               setPath(Paths.get(url));
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+               new MiniExplorer(shell, url) {
+
+                       @Override
+                       protected void pathChanged(Path path) {
+                               shell.setText(path.toString());
+                       }
+
+               };
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java
new file mode 100644 (file)
index 0000000..877f643
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.minidesktop;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.ToolBar;
+
+/** A start page displaying network information and resources. */
+public class MiniHomePart {
+
+       public Control createUiPart(Composite parent, Object context) {
+               parent.setLayout(new GridLayout(2, false));
+               Display display = parent.getDisplay();
+
+               // Apps
+               Group appsGroup = new Group(parent, SWT.NONE);
+               appsGroup.setText("Apps");
+               appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1));
+               ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT);
+               fillAppsToolBar(appsToolBar);
+
+               // Host
+               Group hostGroup = new Group(parent, SWT.NONE);
+               hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+               hostGroup.setText("Host");
+               hostGroup.setLayout(new GridLayout(2, false));
+               label(hostGroup, "Hostname: ");
+               try {
+                       InetAddress defaultAddr = InetAddress.getLocalHost();
+                       String hostname = defaultAddr.getHostName();
+                       label(hostGroup, hostname);
+                       label(hostGroup, "Address: ");
+                       label(hostGroup, defaultAddr.getHostAddress());
+               } catch (UnknownHostException e) {
+                       label(hostGroup, e.getMessage());
+               }
+
+               Enumeration<NetworkInterface> netInterfaces = null;
+               try {
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+               } catch (SocketException e) {
+                       label(hostGroup, "Interfaces: ");
+                       label(hostGroup, e.getMessage());
+               }
+               if (netInterfaces != null)
+                       while (netInterfaces.hasMoreElements()) {
+                               NetworkInterface netInterface = netInterfaces.nextElement();
+                               byte[] hardwareAddress = null;
+                               try {
+                                       hardwareAddress = netInterface.getHardwareAddress();
+                                       if (hardwareAddress != null) {
+                                               label(hostGroup, convertHardwareAddress(hardwareAddress));
+                                               label(hostGroup, netInterface.getName());
+                                               for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress()));
+                                                       label(hostGroup, Short.toString(addr.getNetworkPrefixLength()));
+                                               }
+                                       }
+                               } catch (SocketException e) {
+                                       label(hostGroup, e.getMessage());
+                               }
+                       }
+
+               // Resources
+               Group resGroup = new Group(parent, SWT.NONE);
+               resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+               resGroup.setText("Resources");
+               resGroup.setLayout(new GridLayout(3, false));
+
+               Runtime runtime = Runtime.getRuntime();
+
+               String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB";
+               label(resGroup, "Max Java memory: ");
+               label(resGroup, maxMemoryStr);
+               label(resGroup, "Java version: " + Runtime.version().toString());
+
+               label(resGroup, "Usable Java memory: ");
+               Label totalMemory = label(resGroup, maxMemoryStr);
+               ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH);
+               totalOnMax.setMaximum(100);
+               label(resGroup, "Used Java memory: ");
+               Label usedMemory = label(resGroup, maxMemoryStr);
+               ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH);
+               totalOnMax.setMaximum(100);
+               new Thread() {
+                       @Override
+                       public void run() {
+                               while (!totalOnMax.isDisposed()) {
+                                       display.asyncExec(() -> {
+                                               if (totalOnMax.isDisposed())
+                                                       return;
+                                               totalOnMax.setSelection(javaTotalOnMaxPerct(runtime));
+                                               usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime));
+                                               totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB");
+                                               usedMemory.setText(
+                                                               Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB");
+                                       });
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (InterruptedException e) {
+                                               return;
+                                       }
+                               }
+                       }
+               }.start();
+               return parent;
+       }
+
+       protected void fillAppsToolBar(ToolBar toolBar) {
+
+       }
+
+       protected int javaUsedOnTotalPerct(Runtime runtime) {
+               return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory());
+       }
+
+       protected int javaTotalOnMaxPerct(Runtime runtime) {
+               return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory());
+       }
+
+       protected Label label(Composite parent, String text) {
+               Label label = new Label(parent, SWT.WRAP);
+               label.setText(text);
+               return label;
+       }
+
+       protected String cleanHostAddress(String hostAddress) {
+               // remove % from Ipv6 addresses
+               int index = hostAddress.indexOf('%');
+               if (index > 0)
+                       return hostAddress.substring(0, index);
+               else
+                       return hostAddress;
+       }
+
+       protected String convertHardwareAddress(byte[] hardwareAddress) {
+               if (hardwareAddress == null)
+                       return "";
+               // from https://stackoverflow.com/a/2797498/7878010
+               StringBuilder sb = new StringBuilder(18);
+               for (byte b : hardwareAddress) {
+                       if (sb.length() > 0)
+                               sb.append(':');
+                       sb.append(String.format("%02x", b).toUpperCase());
+               }
+               return sb.toString();
+       }
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java
new file mode 100644 (file)
index 0000000..86ff53f
--- /dev/null
@@ -0,0 +1,129 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageLoader;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniImageViewer implements PaintListener {
+       private URL url;
+       private Canvas area;
+
+       private Image image;
+
+       public MiniImageViewer(Composite parent, int style) {
+               parent.setLayout(new GridLayout());
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new RowLayout());
+               Button load = new Button(toolBar, SWT.FLAT);
+               load.setText("\u2191");// up arrow
+               load.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               FileDialog fileDialog = new FileDialog(area.getShell());
+                               String path = fileDialog.open();
+                               if (path != null) {
+                                       setUrl(path);
+                               }
+                       }
+
+               });
+
+               area = new Canvas(parent, SWT.NONE);
+               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               area.addPaintListener(this);
+       }
+
+       protected void load(URL url) {
+               try {
+                       ImageLoader imageLoader = new ImageLoader();
+                       ImageData[] data = imageLoader.load(url.openStream());
+                       image = new Image(area.getDisplay(), data[0]);
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       @Override
+       public void paintControl(PaintEvent e) {
+               e.gc.drawImage(image, 0, 0);
+
+       }
+
+       protected Path url2path(URL url) {
+               try {
+                       Path path = Paths.get(url.toURI());
+                       return path;
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+               }
+       }
+
+       public void setUrl(URL url) {
+               this.url = url;
+               if (area != null)
+                       load(this.url);
+       }
+
+       public void setUrl(String url) {
+               try {
+                       setUrl(new URL(url));
+               } catch (MalformedURLException e) {
+                       // try with http
+                       try {
+                               setUrl(new URL("file://" + url));
+                               return;
+                       } catch (MalformedURLException e1) {
+                               // nevermind...
+                       }
+                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE);
+               String url = args.length > 0 ? args[0] : "";
+               if (!url.trim().equals("")) {
+                       miniBrowser.setUrl(url);
+                       shell.setText(url);
+               } else {
+                       shell.setText("*");
+               }
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java
new file mode 100644 (file)
index 0000000..cd1a9f2
--- /dev/null
@@ -0,0 +1,342 @@
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniTerminal implements KeyListener, PaintListener {
+
+       private Canvas area;
+//     private Caret caret;
+
+       private StringBuffer buf = new StringBuffer("");
+       private StringBuffer userInput = new StringBuffer("");
+       private List<String> history = new ArrayList<>();
+
+       private Point charExtent = null;
+       private int charsPerLine = 0;
+       private String[] lines = new String[0];
+       private List<String> logicalLines = new ArrayList<>();
+
+       private Font mono;
+       private Charset charset;
+
+       private Path currentDir;
+       private Path homeDir;
+       private String host = "localhost";
+       private String username;
+
+       // Sub process
+       private Process process;
+       private boolean running = false;
+       private OutputStream stdIn = null;
+
+       private Thread readOut;
+
+       public MiniTerminal(Composite parent, String url) {
+               this(parent);
+               setPath(url);
+       }
+
+       public MiniTerminal(Composite parent) {
+               charset = StandardCharsets.UTF_8;
+
+               Display display = parent.getDisplay();
+               // Linux-specific
+               mono = new Font(display, "Monospace", 10, SWT.NONE);
+
+               parent.setLayout(new GridLayout());
+               area = new Canvas(parent, SWT.NONE);
+               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+//             caret = new Caret(area, SWT.NONE);
+//             area.setCaret(caret);
+
+               area.addKeyListener(this);
+               area.addPaintListener(this);
+
+               username = System.getProperty("user.name");
+               try {
+                       host = InetAddress.getLocalHost().getHostName();
+                       if (host.indexOf('.') > 0)
+                               host = host.substring(0, host.indexOf('.'));
+               } catch (UnknownHostException e) {
+                       host = "localhost";
+               }
+               homeDir = Paths.get(System.getProperty("user.home"));
+               currentDir = homeDir;
+
+               buf = new StringBuffer(prompt());
+       }
+
+       @Override
+       public void keyPressed(KeyEvent e) {
+       }
+
+       @Override
+       public void keyReleased(KeyEvent e) {
+//             if (e.keyLocation != 0)
+//                     return;// weird characters
+               
+               // System.out.println(e.character);
+               if (e.keyCode == 0xd) {// return
+                       markLogicalLine();
+                       if (!running)
+                               processUserInput();
+                       // buf.append(prompt());
+               } else if (e.keyCode == 0x8) {// delete
+                       if (userInput.length() == 0)
+                               return;
+                       userInput.setLength(userInput.length() - 1);
+                       if (!running && buf.length() > 0)
+                               buf.setLength(buf.length() - 1);
+               } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {// Ctrl+C
+                       if (process != null)
+                               process.destroy();
+               } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {// Ctrl+\
+                       if (process != null) {
+                               process.destroyForcibly();
+                       }
+               } else {
+                       // if (!running)
+                       buf.append(e.character);
+                       userInput.append(e.character);
+               }
+
+               if (area.isDisposed())
+                       return;
+               area.redraw();
+               // System.out.println("Append " + e);
+
+               if (running) {
+                       if (stdIn != null) {
+                               try {
+                                       stdIn.write(Character.toString(e.character).getBytes(charset));
+                               } catch (IOException e1) {
+                                       // TODO Auto-generated catch block
+                                       e1.printStackTrace();
+                               }
+                       }
+               }
+       }
+
+       protected String prompt() {
+               String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
+               String end = username.equals("root") ? "]# " : "]$ ";
+               return "[" + username + "@" + host + " " + fileName + end;
+       }
+
+       private void displayPrompt() {
+               buf.append(prompt() + userInput);
+       }
+
+       protected void markLogicalLine() {
+               String str = buf.toString().trim();
+               logicalLines.add(str);
+               buf = new StringBuffer("");
+       }
+
+       private void processUserInput() {
+               String cmd = userInput.toString();
+               userInput = new StringBuffer("");
+               processUserInput(cmd);
+               history.add(cmd);
+       }
+
+       protected void processUserInput(String input) {
+               try {
+                       StringTokenizer st = new StringTokenizer(input);
+                       List<String> args = new ArrayList<>();
+                       while (st.hasMoreTokens())
+                               args.add(st.nextToken());
+                       if (args.size() == 0) {
+                               displayPrompt();
+                               return;
+                       }
+
+                       // change directory
+                       if (args.get(0).equals("cd")) {
+                               if (args.size() == 1) {
+                                       setPath(homeDir);
+                               } else {
+                                       Path newPath = currentDir.resolve(args.get(1));
+                                       if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
+                                               println(newPath + ": No such file or directory");
+                                               return;
+                                       }
+                                       setPath(newPath);
+                               }
+                               displayPrompt();
+                               return;
+                       }
+                       // show current directory
+                       else if (args.get(0).equals("pwd")) {
+                               println(currentDir);
+                               displayPrompt();
+                               return;
+                       }
+                       // exit
+                       else if (args.get(0).equals("exit")) {
+                               println("logout");
+                               exitCalled();
+                               return;
+                       }
+
+                       ProcessBuilder pb = new ProcessBuilder(args);
+                       pb.redirectErrorStream(true);
+                       pb.directory(currentDir.toFile());
+//                     Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile());
+                       process = pb.start();
+
+                       stdIn = process.getOutputStream();
+                       readOut = new Thread("MiniTerminal read out") {
+                               @Override
+                               public void run() {
+                                       running = true;
+                                       try (BufferedReader in = new BufferedReader(
+                                                       new InputStreamReader(process.getInputStream(), charset))) {
+                                               String line = null;
+                                               while ((line = in.readLine()) != null) {
+                                                       println(line);
+                                               }
+                                       } catch (IOException e) {
+                                               println(e.getMessage());
+                                       }
+                                       stdIn = null;
+                                       displayPrompt();
+                                       running = false;
+                                       readOut = null;
+                                       process = null;
+                               }
+                       };
+                       readOut.start();
+               } catch (IOException e) {
+                       println(e.getMessage());
+                       displayPrompt();
+               }
+       }
+
+       protected int linesForLogicalLine(char[] line) {
+               return line.length / charsPerLine + 1;
+       }
+
+       protected void println(Object line) {
+               buf.append(line);
+               markLogicalLine();
+       }
+
+       protected void refreshLines(int charPerLine, int nbrOfLines) {
+               if (lines.length != nbrOfLines) {
+                       lines = new String[nbrOfLines];
+                       Arrays.fill(lines, null);
+               }
+               if (this.charsPerLine != charPerLine)
+                       this.charsPerLine = charPerLine;
+
+               int currentLine = nbrOfLines - 1;
+               // current line
+               if (buf.length() > 0) {
+                       lines[currentLine] = buf.toString();
+               } else {
+                       lines[currentLine] = "";
+               }
+               currentLine--;
+
+               logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
+                       char[] logicalLine = logicalLines.get(i).toCharArray();
+                       int linesNeeded = linesForLogicalLine(logicalLine);
+                       for (int j = linesNeeded - 1; j >= 0; j--) {
+                               int from = j * charPerLine;
+                               int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
+                               lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
+//                             System.out.println("Set line " + currentLine + " to : " + lines[currentLine]);
+                               currentLine--;
+                               if (currentLine < 0)
+                                       break logicalLines;
+                       }
+               }
+       }
+
+       @Override
+       public void paintControl(PaintEvent e) {
+               GC gc = e.gc;
+               gc.setFont(mono);
+               if (charExtent == null)
+                       charExtent = gc.textExtent("a");
+
+               Point areaSize = area.getSize();
+               int charPerLine = areaSize.x / charExtent.x;
+               int nbrOfLines = areaSize.y / charExtent.y;
+               refreshLines(charPerLine, nbrOfLines);
+
+               for (int i = 0; i < lines.length; i++) {
+                       String line = lines[i];
+                       if (line != null)
+                               gc.drawString(line, 0, i * charExtent.y);
+               }
+//             String toDraw = buf.toString();
+//             gc.drawString(toDraw, 0, 0);
+//             area.setCaret(caret);
+       }
+
+       protected void exitCalled() {
+
+       }
+
+       public void setPath(String path) {
+               this.currentDir = Paths.get(path);
+       }
+
+       public void setPath(Path path) {
+               this.currentDir = path;
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+               new MiniTerminal(shell, url) {
+
+                       @Override
+                       protected void exitCalled() {
+                               shell.dispose();
+                               System.exit(0);
+                       }
+               };
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java
new file mode 100644 (file)
index 0000000..91cd19e
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniTextEditor {
+       private URL url;
+       private Text text;
+
+       public MiniTextEditor(Composite parent, int style) {
+               parent.setLayout(new GridLayout());
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new RowLayout());
+               Button load = new Button(toolBar, SWT.FLAT);
+               load.setText("\u2191");// up arrow
+               load.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               FileDialog fileDialog = new FileDialog(text.getShell());
+                               String path = fileDialog.open();
+                               if (path != null) {
+                                       setUrl(path);
+                               }
+                       }
+
+               });
+
+               Button save = new Button(toolBar, SWT.FLAT);
+               save.setText("\u2193");// down arrow
+               // save.setText("\u1F609");// emoji
+               save.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               save(url);
+                       }
+
+               });
+
+               text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
+               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+       }
+
+       protected void load(URL url) {
+               text.setText("");
+               // TODO deal with encoding and binary data
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+                       String line = null;
+                       while ((line = in.readLine()) != null) {
+                               text.append(line + "\n");
+                       }
+                       text.setEditable(true);
+               } catch (IOException e) {
+                       if (e instanceof FileNotFoundException) {
+                               Path path = url2path(url);
+                               try {
+                                       Files.createFile(path);
+                                       load(url);
+                                       return;
+                               } catch (IOException e1) {
+                                       e = e1;
+                               }
+                       }
+                       text.setText(e.getMessage());
+                       text.setEditable(false);
+                       e.printStackTrace();
+                       // throw new IllegalStateException("Cannot load " + url, e);
+               }
+       }
+
+       protected Path url2path(URL url) {
+               try {
+                       Path path = Paths.get(url.toURI());
+                       return path;
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+               }
+       }
+
+       protected void save(URL url) {
+               if (!url.getProtocol().equals("file"))
+                       throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write");
+               Path path = url2path(url);
+               try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) {
+                       out.write(text.getText());
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot save " + url + " to " + path, e);
+               }
+       }
+
+       public void setUrl(URL url) {
+               this.url = url;
+               if (text != null)
+                       load(url);
+       }
+
+       public void setUrl(String url) {
+               try {
+                       setUrl(new URL(url));
+               } catch (MalformedURLException e) {
+                       // try with http
+                       try {
+                               setUrl(new URL("file://" + url));
+                               return;
+                       } catch (MalformedURLException e1) {
+                               // nevermind...
+                       }
+                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE);
+               String url = args.length > 0 ? args[0] : "";
+               if (!url.trim().equals("")) {
+                       miniBrowser.setUrl(url);
+                       shell.setText(url);
+               } else {
+                       shell.setText("*");
+               }
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png
new file mode 100644 (file)
index 0000000..55c614d
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png
new file mode 100644 (file)
index 0000000..54ecae2
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png
new file mode 100644 (file)
index 0000000..eb2fc72
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png
new file mode 100644 (file)
index 0000000..06d337a
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png
new file mode 100644 (file)
index 0000000..31e671c
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png
new file mode 100644 (file)
index 0000000..adb7c2c
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png differ
diff --git a/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png
new file mode 100644 (file)
index 0000000..4c9a16c
Binary files /dev/null and b/swt/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png differ
diff --git a/swt/rap/org.argeo.cms.e4.rap/.classpath b/swt/rap/org.argeo.cms.e4.rap/.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.cms.e4.rap/.project b/swt/rap/org.argeo.cms.e4.rap/.project
new file mode 100644 (file)
index 0000000..40c9e01
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.e4.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rap/org.argeo.cms.e4.rap/bnd.bnd b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..6db081f
--- /dev/null
@@ -0,0 +1,9 @@
+Import-Package: \
+org.argeo.api.acr, \
+org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.eclipse.e4.ui.workbench,\
+org.eclipse.rap.rwt.client,\
+org.eclipse.nebula.widgets.richtext;resolution:=optional,\
+org.eclipse.*;resolution:=optional,\
+*
diff --git a/swt/rap/org.argeo.cms.e4.rap/build.properties b/swt/rap/org.argeo.cms.e4.rap/build.properties
new file mode 100644 (file)
index 0000000..c58ea21
--- /dev/null
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java
new file mode 100644 (file)
index 0000000..81326f3
--- /dev/null
@@ -0,0 +1,139 @@
+package org.argeo.cms.e4.rap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Base class for CMS RAP applications. */
+public abstract class AbstractRapE4App implements ApplicationConfiguration {
+       private String e4Xmi;
+       private String path;
+       private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+       private Map<String, String> baseProperties = new HashMap<String, String>();
+
+       private BundleContext bundleContext;
+       public final static String CONTEXT_NAME_PROPERTY = "contextName";
+       private String contextName;
+
+       /**
+        * To be overridden in order to add multiple entry points, directly or using
+        * {@link #addE4EntryPoint(Application, String, String, Map)}.
+        */
+       protected void addEntryPoints(Application application) {
+       }
+
+       public void configure(Application application) {
+               application.setExceptionHandler(new ExceptionHandler() {
+
+                       @Override
+                       public void handleException(Throwable throwable) {
+                               CmsFeedback.error("Unexpected RWT exception", throwable);
+                       }
+               });
+
+               if (e4Xmi != null) {// backward compatibility
+                       addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
+               } else {
+                       addEntryPoints(application);
+               }
+       }
+
+       protected Map<String, String> getBaseProperties() {
+               return baseProperties;
+       }
+
+//     protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
+//             CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+//             application.addEntryPoint(path, entryPointFactory, properties);
+//             application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+//     }
+
+       protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
+               E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
+               CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+               application.addEntryPoint(path, entryPointFactory, properties);
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+       }
+
+       /**
+        * To be overridden for further configuration.
+        * 
+        * @see E4ApplicationConfig
+        */
+       protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
+               return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+       }
+
+       @Deprecated
+       public void setPageTitle(String pageTitle) {
+               if (pageTitle != null)
+                       baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
+       }
+
+       /** Returns a new map used to customise and entry point. */
+       public Map<String, String> customise(String pageTitle) {
+               Map<String, String> custom = new HashMap<>(getBaseProperties());
+               if (pageTitle != null)
+                       custom.put(WebClient.PAGE_TITLE, pageTitle);
+               return custom;
+       }
+
+       @Deprecated
+       public void setE4Xmi(String e4Xmi) {
+               this.e4Xmi = e4Xmi;
+       }
+
+       @Deprecated
+       public void setPath(String path) {
+               this.path = path;
+       }
+
+       public void setLifeCycleUri(String lifeCycleUri) {
+               this.lifeCycleUri = lifeCycleUri;
+       }
+
+       protected BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       protected void setBundleContext(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+       }
+
+       public String getContextName() {
+               return contextName;
+       }
+
+       public void setContextName(String contextName) {
+               this.contextName = contextName;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               this.bundleContext = bundleContext;
+               for (String key : properties.keySet()) {
+                       Object value = properties.get(key);
+                       if (value != null)
+                               baseProperties.put(key, value.toString());
+               }
+
+               if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
+                       assert properties.get(CONTEXT_NAME_PROPERTY) != null;
+                       contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
+               } else {
+                       contextName = "<unknown context>";
+               }
+       }
+
+       public void destroy(Map<String, Object> properties) {
+
+       }
+}
diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java
new file mode 100644 (file)
index 0000000..a5a3234
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.e4.rap;
+
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.e4.E4EntryPointFactory;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class CmsE4EntryPointFactory extends E4EntryPointFactory {
+       public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+       public CmsE4EntryPointFactory(E4ApplicationConfig config) {
+               super(config);
+       }
+
+       public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
+               super(defaultConfig(e4Xmi, lifeCycleUri));
+       }
+
+       public CmsE4EntryPointFactory(String e4Xmi) {
+               this(e4Xmi, DEFAULT_LIFECYCLE_URI);
+       }
+
+       public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
+               E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+               return config;
+       }
+
+       @Override
+       public EntryPoint create() {
+               EntryPoint ep = createEntryPoint();
+               EntryPoint authEp = new EntryPoint() {
+
+                       @Override
+                       public int createUI() {
+                               Subject subject = new Subject();
+                               return Subject.doAs(subject, new PrivilegedAction<Integer>() {
+
+                                       @Override
+                                       public Integer run() {
+                                               // SPNEGO
+                                               // HttpServletRequest request = RWT.getRequest();
+                                               // String authorization = request.getHeader(HEADER_AUTHORIZATION);
+                                               // if (authorization == null || !authorization.startsWith("Negotiate")) {
+                                               // HttpServletResponse response = RWT.getResponse();
+                                               // response.setStatus(401);
+                                               // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+                                               // response.setDateHeader("Date", System.currentTimeMillis());
+                                               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60
+                                               // * 1000));
+                                               // response.setHeader("Accept-Ranges", "bytes");
+                                               // response.setHeader("Connection", "Keep-Alive");
+                                               // response.setHeader("Keep-Alive", "timeout=5, max=97");
+                                               // // response.setContentType("text/html; charset=UTF-8");
+                                               // }
+
+                                               JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+                                               Integer exitCode = ep.createUI();
+                                               jsExecutor.execute("location.reload()");
+                                               return exitCode;
+                                       }
+
+                               });
+                       }
+               };
+               return authEp;
+       }
+
+       protected EntryPoint createEntryPoint() {
+               return super.create();
+       }
+}
diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
new file mode 100644 (file)
index 0000000..cdd87fd
--- /dev/null
@@ -0,0 +1,193 @@
+package org.argeo.cms.e4.rap;
+
+import java.security.AccessController;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import javax.inject.Inject;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.eclipse.e4.core.services.events.IEventBroker;
+import org.eclipse.e4.ui.workbench.UIEvents;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+@SuppressWarnings("restriction")
+public class CmsLoginLifecycle implements CmsView {
+       private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class);
+
+       @Inject
+       private CmsContext cmsContext;
+       
+       private UxContext uxContext;
+       private CmsImageManager imageManager;
+
+       private LoginContext loginContext;
+       private BrowserNavigation browserNavigation;
+
+       private String state = null;
+       private String uid;
+
+       @PostContextCreate
+       boolean login(final IEventBroker eventBroker) {
+               uid = UUID.randomUUID().toString();
+               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+               if (browserNavigation != null)
+                       browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
+                               private static final long serialVersionUID = -3668136623771902865L;
+
+                               @Override
+                               public void navigated(BrowserNavigationEvent event) {
+                                       state = event.getState();
+                                       if (uxContext != null)// is logged in
+                                               stateChanged();
+                               }
+                       });
+
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               Display display = Display.getCurrent();
+//             UiContext.setData(CmsView.KEY, this);
+               CmsLoginShell loginShell = new CmsLoginShell(this, cmsContext);
+               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+               loginShell.setSubject(subject);
+               try {
+                       // try pre-auth
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell);
+                       loginContext.login();
+               } catch (LoginException e) {
+                       loginShell.createUi();
+                       loginShell.open();
+
+                       while (!loginShell.getShell().isDisposed()) {
+                               if (!display.readAndDispatch())
+                                       display.sleep();
+                       }
+               }
+               if (CurrentUser.getUsername(getSubject()) == null)
+                       return false;
+               uxContext = new SimpleSwtUxContext();
+               imageManager = (CmsImageManager) new AcrSwtImageManager();
+
+               eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
+                       @Override
+                       public void handleEvent(Event event) {
+                               startupComplete();
+                               eventBroker.unsubscribe(this);
+                       }
+               });
+
+               // lcs.changeApplicationLocale(Locale.FRENCH);
+               return true;
+       }
+
+       @PreSave
+       void destroy() {
+               // logout();
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               browserNavigation.pushState(state, state);
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+               if (loginContext == null)
+                       throw new IllegalArgumentException("Login context cannot be null");
+               // logout previous login context
+               // if (this.loginContext != null)
+               // try {
+               // this.loginContext.logout();
+               // } catch (LoginException e1) {
+               // System.err.println("Could not log out: " + e1);
+               // }
+               this.loginContext = loginContext;
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext == null)
+                       throw new IllegalStateException("Login context should not be null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       throw new IllegalStateException("Cannot log out", e);
+               }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               String msg = "Unexpected exception in Eclipse 4 RAP";
+               log.error(msg, e);
+               CmsFeedback.error(msg, e);
+       }
+
+       @Override
+       public CmsImageManager getImageManager() {
+               return imageManager;
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       // CALLBACKS
+       protected void startupComplete() {
+       }
+
+       protected void stateChanged() {
+
+       }
+
+       // GETTERS
+       protected BrowserNavigation getBrowserNavigation() {
+               return browserNavigation;
+       }
+
+       protected String getState() {
+               return state;
+       }
+
+       @Override
+       public <T> T doAs(Callable<T> action) {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java
new file mode 100644 (file)
index 0000000..764234e
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms.e4.rap;
+
+import java.util.Enumeration;
+
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.rap.rwt.application.Application;
+import org.osgi.framework.Bundle;
+
+/** Simple RAP app which loads all e4xmi files. */
+public class SimpleRapE4App extends AbstractRapE4App {
+       private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class);
+
+       private String baseE4xmi = "/e4xmi";
+
+       @Override
+       protected void addEntryPoints(Application application) {
+               Bundle bundle = getBundleContext().getBundle();
+               Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
+               while (paths.hasMoreElements()) {
+                       String p = paths.nextElement();
+                       if (p.endsWith(".e4xmi")) {
+                               String e4xmiPath = bundle.getSymbolicName() + '/' + p;
+                               // FIXME deal with base name
+                               String name=null;// = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
+                               addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
+                               if (log.isDebugEnabled())
+                                       log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
+                       }
+               }
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java
new file mode 100644 (file)
index 0000000..1122f19
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse 4 RAP specific extensions. */
+package org.argeo.cms.e4.rap;
\ No newline at end of file
diff --git a/swt/rap/org.argeo.cms.swt.rap/.classpath b/swt/rap/org.argeo.cms.swt.rap/.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.cms.swt.rap/.project b/swt/rap/org.argeo.cms.swt.rap/.project
new file mode 100644 (file)
index 0000000..84a20ae
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.swt.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore b/swt/rap/org.argeo.cms.swt.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml b/swt/rap/org.argeo.cms.swt.rap/OSGI-INF/cmsWebAppFactory.xml
new file mode 100644 (file)
index 0000000..f5edebc
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="CMS Web App Factory">
+   <implementation class="org.argeo.cms.web.osgi.CmsWebAppFactory"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+   <reference bind="setCmsEventBus" cardinality="1..1" interface="org.argeo.api.cms.CmsEventBus" name="CmsEventBus" policy="static"/>
+</scr:component>
diff --git a/swt/rap/org.argeo.cms.swt.rap/bnd.bnd b/swt/rap/org.argeo.cms.swt.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..81178eb
--- /dev/null
@@ -0,0 +1,10 @@
+Import-Package:\
+org.argeo.api.acr,\
+org.eclipse.swt,\
+org.argeo.eclipse.ui,\
+org.eclipse.swt.graphics,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component: OSGI-INF/cmsWebAppFactory.xml
+
diff --git a/swt/rap/org.argeo.cms.swt.rap/build.properties b/swt/rap/org.argeo.cms.swt.rap/build.properties
new file mode 100644 (file)
index 0000000..2416e52
--- /dev/null
@@ -0,0 +1,7 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/cmsWebAppFactory.xml
+source.. = src/
+additional.bundles = org.argeo.ext.slf4j,\
+                     org.slf4j.api
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/BundleResourceLoader.java
new file mode 100644 (file)
index 0000000..ca93e62
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+
+/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
+public class BundleResourceLoader implements ResourceLoader {
+       private final Bundle bundle;
+
+       public BundleResourceLoader(Bundle bundle) {
+               this.bundle = bundle;
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               URL res = bundle.getEntry(resourceName);
+               if (res == null) {
+                       res = bundle.getResource(resourceName);
+                       if (res == null)
+                               throw new IllegalArgumentException(
+                                               "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
+               }
+               return res.openStream();
+       }
+
+       public Bundle getBundle() {
+               return bundle;
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java
new file mode 100644 (file)
index 0000000..102a4e1
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.argeo.api.cms.ux.CmsTheme;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
+public class CmsThemeResourceLoader implements ResourceLoader {
+       private final CmsTheme theme;
+
+       public CmsThemeResourceLoader(CmsTheme theme) {
+               super();
+               this.theme = theme;
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               return theme.getResourceAsStream(resourceName);
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebApp.java
new file mode 100644 (file)
index 0000000..67fa5ce
--- /dev/null
@@ -0,0 +1,170 @@
+package org.argeo.cms.web;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAppListener;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.util.LangUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/** An RWT web app integrating with a {@link CmsApp}. */
+public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
+       private final static CmsLog log = CmsLog.getLog(CmsWebApp.class);
+
+       private BundleContext bundleContext;
+       private CmsApp cmsApp;
+
+       private CmsEventBus cmsEventBus;
+
+       private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
+
+       private final static String CONTEXT_NAME = "contextName";
+       private String contextName;
+
+       private final static String FAVICON_PNG = "favicon.png";
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               this.bundleContext = bundleContext;
+               contextName = properties.get(CONTEXT_NAME);
+               if (cmsApp != null) {
+                       if (cmsApp.allThemesAvailable())
+                               publishWebApp();
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+               if (cmsApp != null) {
+                       cmsApp.removeCmsAppListener(this);
+                       cmsApp = null;
+               }
+       }
+
+       @Override
+       public void configure(Application application) {
+               // TODO make it configurable?
+               // SWT compatibility is required for:
+               // - Browser.execute()
+               // - blocking dialogs
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+               for (String uiName : cmsApp.getUiNames()) {
+                       CmsTheme theme = cmsApp.getTheme(uiName);
+                       if (theme != null)
+                               WebThemeUtils.apply(application, theme);
+               }
+
+               Map<String, String> properties = new HashMap<>();
+               addEntryPoints(application, properties);
+               application.setExceptionHandler(this);
+       }
+
+       @Override
+       public void handleException(Throwable throwable) {
+               Display display = Display.getCurrent();
+               if (display != null && !display.isDisposed()) {
+                       CmsView cmsView = CmsSwtUtils.getCmsView(display.getActiveShell());
+                       cmsView.exception(throwable);
+               } else {
+                       log.error("Unexpected exception outside an UI thread", throwable);
+               }
+
+       }
+
+       protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
+               for (String uiName : cmsApp.getUiNames()) {
+                       Map<String, String> properties = new HashMap<>(commonProperties);
+                       CmsTheme theme = cmsApp.getTheme(uiName);
+                       if (theme != null) {
+                               properties.put(WebClient.THEME_ID, theme.getThemeId());
+                               properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+                               properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
+                               Set<String> imagePaths = theme.getImagesPaths();
+                               if (imagePaths.contains(FAVICON_PNG)) {
+                                       properties.put(WebClient.FAVICON, FAVICON_PNG);
+                               }
+                       } else {
+                               properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+                       }
+                       String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
+                       application.addEntryPoint(entryPointName, () -> {
+                               CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
+                               return entryPoint;
+                       }, properties);
+                       if (log.isDebugEnabled())
+                               log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
+               }
+//             if (log.isDebugEnabled())
+//                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
+       }
+
+       public CmsApp getCmsApp() {
+               return cmsApp;
+       }
+
+       BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       public void setCmsApp(CmsApp cmsApp) {
+               this.cmsApp = cmsApp;
+//             this.cmsAppId = properties.get(Constants.SERVICE_PID);
+               this.cmsApp.addCmsAppListener(this);
+       }
+
+       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (!contextName.equals(this.contextName))
+                       return;
+               if (this.cmsApp != null) {
+                       this.cmsApp.removeCmsAppListener(this);
+               }
+               if (rwtAppReg != null)
+                       rwtAppReg.unregister();
+               this.cmsApp = null;
+       }
+
+       @Override
+       public void themingUpdated() {
+               if (cmsApp != null && cmsApp.allThemesAvailable())
+                       publishWebApp();
+       }
+
+       protected void publishWebApp() {
+               Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
+               if (rwtAppReg != null)
+                       rwtAppReg.unregister();
+               if (bundleContext != null) {
+                       rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
+                       if (log.isDebugEnabled())
+                               log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
+               }
+       }
+
+       public void setCmsEventBus(CmsEventBus cmsEventBus) {
+               this.cmsEventBus = cmsEventBus;
+       }
+
+       public CmsEventBus getCmsEventBus() {
+               return cmsEventBus;
+       }
+
+       public void setContextName(String contextName) {
+               this.contextName = contextName;
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
new file mode 100644 (file)
index 0000000..70f49f6
--- /dev/null
@@ -0,0 +1,312 @@
+package org.argeo.cms.web;
+
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+
+import java.security.PrivilegedAction;
+import java.util.Locale;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.AbstractSwtCmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The {@link CmsView} for a {@link CmsWebApp}. */
+@SuppressWarnings("restriction")
+public class CmsWebEntryPoint extends AbstractSwtCmsView implements EntryPoint, CmsView, BrowserNavigationListener {
+       private static final long serialVersionUID = 7733510691684570402L;
+       private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class);
+
+       private final CmsWebApp cmsWebApp;
+
+       // Client services
+       // private final JavaScriptExecutor jsExecutor;
+       private final BrowserNavigation browserNavigation;
+
+       /** Experimental OS-like multi windows. */
+       private boolean multipleShells = false;
+
+       private ServerPushSession serverPushSession;
+
+       public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
+               super(uiName);
+               assert cmsWebApp != null;
+               assert uiName != null;
+               this.cmsWebApp = cmsWebApp;
+               uid = UUID.randomUUID().toString();
+
+               // Initial login
+               LoginContext lc;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
+                       lc.login();
+               } catch (LoginException e) {
+                       try {
+                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
+                                               new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+                                                               new ServletHttpResponse(UiContext.getHttpResponse())));
+                               lc.login();
+                       } catch (LoginException e1) {
+                               throw new IllegalStateException("Cannot log in as anonymous", e1);
+                       }
+               }
+               authChange(lc);
+
+               // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+               if (browserNavigation != null)
+                       browserNavigation.addBrowserNavigationListener(this);
+
+       }
+
+       protected void createContents(Composite parent) {
+               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               try {
+                                       uxContext = new SimpleSwtUxContext();
+                                       imageManager = (CmsImageManager) new AcrSwtImageManager();
+                                       CmsSession cmsSession = getCmsSession();
+                                       if (cmsSession != null) {
+                                               UiContext.setLocale(cmsSession.getLocale());
+                                               LocaleUtils.setThreadLocale(cmsSession.getLocale());
+                                       } else {
+                                               Locale rwtLocale = RWT.getUISession().getLocale();
+                                               LocaleUtils.setThreadLocale(rwtLocale);
+                                       }
+                                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+                                       display = parent.getDisplay();
+                                       ui = cmsWebApp.getCmsApp().initUi(parent);
+                                       if (ui instanceof Composite)
+                                               ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+                                       serverPushSession = new ServerPushSession();
+
+                                       // required in order to doAs to work
+                                       // TODO check whether it would be worth optimising
+                                       serverPushSession.start();
+                                       // we need ui to be set before refresh so that CmsView can store UI context data
+                                       // in it.
+                                       cmsWebApp.getCmsApp().refreshUi(ui, null);
+                               } catch (Exception e) {
+                                       throw new IllegalStateException("Cannot create entrypoint contents", e);
+                               }
+                               return null;
+                       }
+               });
+       }
+
+       @Override
+       public synchronized void logout() {
+               if (loginContext == null)
+                       throw new IllegalArgumentException("Login context should not be null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+                       LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS,
+                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
+                       anonymousLc.login();
+                       authChange(anonymousLc);
+               } catch (LoginException e) {
+                       log.error("Cannot logout", e);
+               }
+       }
+
+       @Override
+       public synchronized void authChange(LoginContext lc) {
+               if (lc == null)
+                       throw new IllegalArgumentException("Login context cannot be null");
+               // logout previous login context
+               if (this.loginContext != null)
+                       try {
+                               this.loginContext.logout();
+                       } catch (LoginException e1) {
+                               log.warn("Could not log out: " + e1);
+                       }
+               this.loginContext = lc;
+               doRefresh();
+       }
+
+       @Override
+       public void exception(final Throwable e) {
+               if (e instanceof SWTError) {
+                       SWTError swtError = (SWTError) e;
+                       if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
+                               return;
+               }
+               display.syncExec(() -> {
+                       // TODO internationalise
+                       CmsFeedback.error("Unexpected exception", e);
+                       // TODO report
+//                     doRefresh();
+               });
+       }
+
+       protected synchronized void doRefresh() {
+               if (ui != null)
+                       Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+                               @Override
+                               public Void run() {
+//                                     if (exception != null) {
+//                                             // TODO internationalise
+//                                             CmsFeedback.error("Unexpected exception", exception);
+//                                             exception = null;
+//                                             // TODO report
+//                                     }
+                                       cmsWebApp.getCmsApp().refreshUi(ui, state);
+                                       return null;
+                               }
+                       });
+       }
+
+       /** Sets the state of the entry point and retrieve the related content. */
+       protected String setState(String newState) {
+               cmsWebApp.getCmsApp().setState(ui, newState);
+               state = newState;
+               return null;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+//             exception = null;
+               String title = setState(state);
+               if (title != null)
+                       doRefresh();
+               if (browserNavigation != null)
+                       browserNavigation.pushState(state, title);
+       }
+
+       public CmsImageManager getImageManager() {
+               return imageManager;
+       }
+
+       @Override
+       public void navigated(BrowserNavigationEvent event) {
+               setState(event.getState());
+               // doRefresh();
+       }
+
+       @Override
+       public CmsEventBus getCmsEventBus() {
+               return cmsWebApp.getCmsEventBus();
+       }
+
+       @Override
+       public CmsApp getCmsApp() {
+               return cmsWebApp.getCmsApp();
+       }
+
+       @Override
+       public void stateChanged(String state, String title) {
+               browserNavigation.pushState(state, title);
+       }
+
+       @Override
+       public CmsSession getCmsSession() {
+               CmsSession cmsSession = cmsWebApp.getCmsApp().getCmsContext().getCmsSession(getSubject());
+               if (cmsSession == null)
+                       throw new IllegalStateException("No CMS session available for " + getSubject());
+               return cmsSession;
+       }
+
+       /*
+        * EntryPoint IMPLEMENTATION
+        */
+
+       @Override
+       public int createUI() {
+               Display display = new Display();
+               Shell shell = createShell(display);
+               shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.registerCmsView(shell, this);
+               createContents(shell);
+               shell.layout();
+//             if (shell.getMaximized()) {
+//                     shell.layout();
+//             } else {
+////                   shell.pack();
+//             }
+               shell.open();
+               if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
+                       eventLoop: while (!shell.isDisposed()) {
+                               try {
+                                       Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+                                               @Override
+                                               public Void run() {
+                                                       if (!display.readAndDispatch()) {
+                                                               display.sleep();
+                                                       }
+                                                       return null;
+                                               }
+                                       });
+                               } catch (Throwable e) {
+                                       if (e instanceof SWTError) {
+                                               SWTError swtError = (SWTError) e;
+                                               if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
+                                                       log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
+                                                       continue eventLoop;
+                                               } else {
+                                                       log.error("Unexpected SWT error in event loop, shutting down...", e);
+                                                       break eventLoop;
+                                               }
+                                       } else if (e instanceof ThreadDeath) {
+                                               throw (ThreadDeath) e;
+//                                     } else if (e instanceof Error) {
+//                                             log.error("Unexpected error in event loop, shutting down...", e);
+//                                             break eventLoop;
+                                       } else {
+                                               log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
+                                               continue eventLoop;
+                                       }
+                               }
+                       }
+                       if (!display.isDisposed())
+                               display.dispose();
+               }
+               return 0;
+       }
+
+       protected Shell createShell(Display display) {
+               Shell shell;
+               if (!multipleShells) {
+                       shell = new Shell(display, SWT.NO_TRIM);
+                       shell.setMaximized(true);
+               } else {
+                       shell = new Shell(display, SWT.SHELL_TRIM);
+                       shell.setSize(800, 600);
+               }
+               return shell;
+       }
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/MinimalWebApp.java
new file mode 100644 (file)
index 0000000..2eff71e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.cms.web;
+
+import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.osgi.BundleCmsTheme;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Lightweight web app using only RWT and not the whole Eclipse platform. */
+public class MinimalWebApp implements ApplicationConfiguration {
+
+       private BundleCmsTheme theme;
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
+                       String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
+                       theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
+               }
+       }
+
+       public void destroy() {
+
+       }
+
+       /** To be overridden. Does nothing by default. */
+       protected void addEntryPoints(Application application, Map<String, String> properties) {
+
+       }
+
+       @Override
+       public void configure(Application application) {
+               if (theme != null)
+                       WebThemeUtils.apply(application, theme);
+
+               Map<String, String> properties = new HashMap<>();
+               if (theme != null) {
+                       properties.put(WebClient.THEME_ID, theme.getThemeId());
+                       properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+               } else {
+                       properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+               }
+               addEntryPoints(application, properties);
+
+       }
+
+       public void setTheme(BundleCmsTheme theme) {
+               this.theme = theme;
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/WebThemeUtils.java
new file mode 100644 (file)
index 0000000..e51644b
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.cms.web;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** Web specific utilities around theming. */
+public class WebThemeUtils {
+       private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class);
+
+       public static void apply(Application application, CmsTheme theme) {
+               ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
+               resources: for (String path : theme.getImagesPaths()) {
+                       if (path.startsWith("target/"))
+                               continue resources; // skip maven output
+                       application.addResource(path, resourceLoader);
+                       if (log.isTraceEnabled())
+                               log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
+               }
+               for (String path : theme.getRapCssPaths()) {
+                       application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
+                       if (log.isDebugEnabled())
+                               log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
+               }
+       }
+
+}
diff --git a/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java b/swt/rap/org.argeo.cms.swt.rap/src/org/argeo/cms/web/osgi/CmsWebAppFactory.java
new file mode 100644 (file)
index 0000000..83a83e2
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.cms.web.osgi;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.cms.web.CmsWebApp;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Publish a CmsApp as a RAP application. */
+public class CmsWebAppFactory {
+       private BundleContext bundleContext = FrameworkUtil.getBundle(CmsWebAppFactory.class).getBundleContext();
+       private final static String CONTEXT_NAME = "contextName";
+
+       private CmsEventBus cmsEventBus;
+
+       private Map<String, CmsWebApp> registrations = Collections.synchronizedMap(new HashMap<>());
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       CmsWebApp cmsWebApp = new CmsWebApp();
+                       cmsWebApp.setCmsEventBus(cmsEventBus);
+                       cmsWebApp.setCmsApp(cmsApp);
+                       Hashtable<String, String> serviceProperties = new Hashtable<>();
+                       if (!contextName.equals(""))
+                               serviceProperties.put(CONTEXT_NAME, contextName);
+                       cmsWebApp.init(bundleContext, serviceProperties);
+                       registrations.put(contextName, cmsWebApp);
+               }
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       CmsWebApp cmsWebApp = registrations.get(contextName);
+                       if (cmsWebApp != null) {
+                               cmsWebApp.destroy(bundleContext, new HashMap<>());
+                               cmsWebApp.unsetCmsApp(cmsApp, properties);
+                       } else {
+                               // TODO log warning
+                       }
+               }
+       }
+
+       public void setCmsEventBus(CmsEventBus cmsEventBus) {
+               this.cmsEventBus = cmsEventBus;
+       }
+
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/.classpath b/swt/rap/org.argeo.swt.specific.rap/.classpath
new file mode 100644 (file)
index 0000000..248abe0
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src" />
+       <classpathentry kind="con"
+               path="org.eclipse.pde.core.requiredPlugins" />
+       <classpathentry kind="con"
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17" />
+       <classpathentry kind="output" path="bin" />
+</classpath>
diff --git a/swt/rap/org.argeo.swt.specific.rap/.project b/swt/rap/org.argeo.swt.specific.rap/.project
new file mode 100644 (file)
index 0000000..53d7976
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.specific.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore b/swt/rap/org.argeo.swt.specific.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rap/org.argeo.swt.specific.rap/bnd.bnd b/swt/rap/org.argeo.swt.specific.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..bcd9b19
--- /dev/null
@@ -0,0 +1,5 @@
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.dialogs,\
+org.eclipse.swt.events,\
+javax.servlet.http;version="[3,5)",\
+*
diff --git a/swt/rap/org.argeo.swt.specific.rap/build.properties b/swt/rap/org.argeo.swt.specific.rap/build.properties
new file mode 100644 (file)
index 0000000..fd806ca
--- /dev/null
@@ -0,0 +1,2 @@
+source.. = src/
+output.. = bin/
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java
new file mode 100644 (file)
index 0000000..ac4e0df
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.awt.image.BufferedImage;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class BufferedImageDisplay extends Composite {
+       private static final long serialVersionUID = 4541163690514461514L;
+       private BufferedImage image;
+
+       public BufferedImageDisplay(Composite parent, int style) {
+               super(parent, SWT.NO_BACKGROUND);
+       }
+
+       public void setImage(BufferedImage image) {
+               this.image = image;
+       }
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
new file mode 100644 (file)
index 0000000..6100c1a
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+       private static final long serialVersionUID = -7540791204102318801L;
+
+       public CmsFileDialog(Shell parent, int style) {
+               super(parent, style);
+       }
+
+       public CmsFileDialog(Shell parent) {
+               super(parent);
+       }
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
new file mode 100644 (file)
index 0000000..3f30bde
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+       private static final long serialVersionUID = 8027963992680980549L;
+
+       public CmsFileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public void setText(String text) {
+               super.setText(text);
+       }
+
+       @Override
+       public String getFileName() {
+               return super.getFileName();
+       }
+
+       @Override
+       public String[] getFileNames() {
+               return super.getFileNames();
+       }
+
+       @Override
+       public void addSelectionListener(SelectionListener listener) {
+               super.addSelectionListener(listener);
+       }
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
new file mode 100644 (file)
index 0000000..a89b921
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.AbstractTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+
+       public static void setStyleData(Widget widget, Object data) {
+               widget.setData(RWT.CUSTOM_VARIANT, data);
+       }
+
+       public static Object getStyleData(Widget widget) {
+               return widget.getData(RWT.CUSTOM_VARIANT);
+       }
+
+       public static void setMarkupData(Widget widget) {
+               widget.setData(RWT.MARKUP_ENABLED, true);
+       }
+
+       public static void setMarkupValidationDisabledData(Widget widget) {
+               widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
+       }
+
+       /**
+        * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
+        */
+       public static void enableToolTipSupport(Viewer viewer) {
+               if (viewer instanceof ColumnViewer)
+                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+       }
+
+       private EclipseUiSpecificUtils() {
+       }
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
new file mode 100644 (file)
index 0000000..f9ca816
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.ClientFile;
+import org.eclipse.rap.rwt.client.service.ClientFileUploader;
+import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+/** Configures a {@link Control} to receive files drop from the client OS. */
+public class FileDropAdapter {
+
+       public void prepareDropTarget(Control control, DropTarget dropTarget) {
+               dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
+               dropTarget.addDropListener(new DropTargetAdapter() {
+                       private static final long serialVersionUID = 5361645765549463168L;
+
+                       @Override
+                       public void dropAccept(DropTargetEvent event) {
+                               if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+                                       event.detail = DND.DROP_NONE;
+                               }
+                       }
+
+                       @Override
+                       public void drop(DropTargetEvent event) {
+                               handleFileDrop(control, event);
+                       }
+               });
+       }
+
+       public void handleFileDrop(Control control, DropTargetEvent event) {
+               ClientFile[] clientFiles = (ClientFile[]) event.data;
+               ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
+//             DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
+               FileUploadReceiver receiver = new FileUploadReceiver() {
+
+                       @Override
+                       public void receive(InputStream stream, FileDetails details) throws IOException {
+                               control.getDisplay().syncExec(() -> {
+                                       try {
+                                               processUpload(stream, details.getFileName(), details.getContentType());
+                                       } catch (IOException e) {
+                                               throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
+                                       }
+                               });
+                       }
+               };
+               FileUploadHandler handler = new FileUploadHandler(receiver);
+//                 handler.setMaxFileSize( sizeLimit );
+//                 handler.setUploadTimeLimit( timeLimit );
+               service.submit(handler.getUploadUrl(), clientFiles);
+//             for (File file : receiver.getTargetFiles()) {
+//                     paths.add(file.toPath());
+//             }
+       }
+
+       /** Executed in UI thread */
+       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+       }
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java
new file mode 100644 (file)
index 0000000..72e17a2
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+       /** Can be null, thus indicating that we are not in a web context. */
+       public static HttpServletRequest getHttpRequest() {
+               return RWT.getRequest();
+       }
+
+       public static HttpServletResponse getHttpResponse() {
+               return RWT.getResponse();
+       }
+
+       public static Locale getLocale() {
+               if (Display.getCurrent() != null)
+                       return RWT.getUISession().getLocale();
+               else
+                       return Locale.getDefault();
+       }
+
+       public static void setLocale(Locale locale) {
+               if (Display.getCurrent() != null)
+                       RWT.getUISession().setLocale(locale);
+               else
+                       Locale.setDefault(locale);
+       }
+
+       /** Can always be null */
+       @SuppressWarnings("unchecked")
+       public static <T> T getData(String key) {
+               Display display = getDisplay();
+               if (display == null)
+                       return null;
+               return (T) display.getData(key);
+       }
+
+       public static void setData(String key, Object value) {
+               Display display = getDisplay();
+               if (display == null)
+                       throw new IllegalStateException("Not display available");
+               display.setData(key, value);
+       }
+
+       private static Display getDisplay() {
+               return Display.getCurrent();
+       }
+
+       private UiContext() {
+       }
+
+}
diff --git a/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/swt/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java
new file mode 100644 (file)
index 0000000..4ec451f
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
+package org.argeo.eclipse.ui.specific;
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.classpath b/swt/rcp/org.argeo.cms.e4.rcp/.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.cms.e4.rcp/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/.gitignore
new file mode 100644 (file)
index 0000000..710cd68
--- /dev/null
@@ -0,0 +1,3 @@
+/bin/
+/target/
+/exec
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.project b/swt/rcp/org.argeo.cms.e4.rcp/.project
new file mode 100644 (file)
index 0000000..64d5619
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.e4.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..0c68a61
--- /dev/null
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..f29e940
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi
new file mode 100644 (file)
index 0000000..6743a4e
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ASCII"?>
+<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmi:id="_c4iAgCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.application">
+  <children xsi:type="basic:TrimmedWindow" xmi:id="_hSGBwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.trimmedwindow.argeocompanion" label="Argeo Companion">
+    <children xsi:type="advanced:PerspectiveStack" xmi:id="_nxzQICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspectivestack.0">
+      <children xsi:type="advanced:Perspective" xmi:id="_oI_oICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspective.cmsadmin" label="CMS Admin">
+        <children xsi:type="basic:PartSashContainer" xmi:id="_qc16ECnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partsashcontainer.0" horizontal="true">
+          <children xsi:type="basic:PartStack" xmi:id="_RE87kDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.partstack.1">
+            <children xsi:type="basic:Part" xmi:id="_V1WvgDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.part.files" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files"/>
+            <children xsi:type="basic:Part" xmi:id="_vOqDQCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.part.jcr" containerData="4000" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR"/>
+          </children>
+          <children xsi:type="basic:PartStack" xmi:id="_0eRiwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partstack.0" containerData="6000">
+            <tags>editorArea</tags>
+          </children>
+        </children>
+      </children>
+    </children>
+  </children>
+  <descriptors xmi:id="__9SDsC8JEeq0koquN4xGyg" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" allowMultiple="true" category="editorArea" closeable="true" contributionURI="bundleclass://org.argeo.cms.jcr.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+  <addons xmi:id="_c4iAgSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
+  <addons xmi:id="_c4iAginCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
+  <addons xmi:id="_c4iAgynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
+  <addons xmi:id="_c4iAhCnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
+  <addons xmi:id="_c4iAhSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
+  <addons xmi:id="_c4iAhinCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
+  <addons xmi:id="_c4iAhynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
+</application:Application>
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties
new file mode 100644 (file)
index 0000000..0a0da75
--- /dev/null
@@ -0,0 +1,23 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+
+applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi
+lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle
+clearPersistedState=true
+#argeo.cms.desktop.inTray=true
+
+# Remote node:
+#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node
+
+# Logging
+log.org.argeo=DEBUG
+
+argeo.node.useradmin.uris=os:///
+eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..eaa51ad
--- /dev/null
@@ -0,0 +1,8 @@
+Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true
+
+Require-Bundle: org.eclipse.core.runtime
+
+Import-Package: !org.eclipse.core.runtime,\
+org.eclipse.swt,\
+org.eclipse.*;resolution:=optional,\
+*
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/build.properties b/swt/rcp/org.argeo.cms.e4.rcp/build.properties
new file mode 100644 (file)
index 0000000..355413e
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               argeo-companion.e4xmi
+source.. = src/
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties b/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties
new file mode 100644 (file)
index 0000000..13f949f
--- /dev/null
@@ -0,0 +1,32 @@
+log4j.rootLogger=WARN, development
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
+log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
+
+#log4j.logger.org.springframework.security=DEBUG
+#log4j.logger.org.apache.commons.exec=DEBUG
+#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
+#log4j.logger.org.apache.jackrabbit.remote=DEBUG
+#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
+
+log4j.logger.org.apache.catalina=INFO
+log4j.logger.org.apache.coyote=INFO
+
+log4j.logger.org.apache.directory=INFO
+log4j.logger.org.apache.directory.server=ERROR
+log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
+
+# development appender (slow!)
+log4j.appender.development=org.apache.log4j.ConsoleAppender
+log4j.appender.development.layout=org.apache.log4j.PatternLayout
+log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml b/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml
new file mode 100644 (file)
index 0000000..3e6896b
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+         id="CmsE4Application"
+         name="CMS E4 Application"
+         point="org.eclipse.core.runtime.applications">
+      <application
+            cardinality="singleton-global"
+            thread="main"
+            visible="true">
+         <run class="org.argeo.cms.e4.rcp.CmsE4Application"></run>
+      </application>
+   </extension>
+</plugin>
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java
new file mode 100644 (file)
index 0000000..3861597
--- /dev/null
@@ -0,0 +1,212 @@
+package org.argeo.cms.e4.rcp;
+
+import java.security.PrivilegedExceptionAction;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.ux.CmsImageManager;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.api.cms.ux.UxContext;
+import org.argeo.cms.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.equinox.app.IApplication;
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.swt.widgets.Display;
+
+public class CmsE4Application implements IApplication, CmsView {
+       private LoginContext loginContext;
+       private IApplication e4Application;
+       private UxContext uxContext;
+       private String uid;
+
+       @Override
+       public Object start(IApplicationContext context) throws Exception {
+               // TODO wait for CMS to be ready
+               Thread.sleep(5000);
+
+               uid = UUID.randomUUID().toString();
+               Subject subject = new Subject();
+               Display display = createDisplay();
+               CmsLoginShell loginShell = new CmsLoginShell(this, null);
+               // TODO customize CmsLoginShell to be smaller and centered
+               loginShell.setSubject(subject);
+               try {
+                       // try pre-auth
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell);
+                       loginContext.login();
+               } catch (LoginException e) {
+                       e.printStackTrace();
+                       loginShell.createUi();
+                       loginShell.open();
+
+                       while (!loginShell.getShell().isDisposed()) {
+                               if (!display.readAndDispatch())
+                                       display.sleep();
+                       }
+               }
+               if (CurrentUser.getUsername(getSubject()) == null)
+                       throw new IllegalStateException("Cannot log in");
+
+               // try {
+               // CallbackHandler callbackHandler = new DefaultLoginDialog(
+               // display.getActiveShell());
+               // loginContext = new LoginContext(
+               // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject,
+               // callbackHandler);
+               // } catch (LoginException e1) {
+               // throw new CmsException("Cannot initialize login context", e1);
+               // }
+               //
+               // // login
+               // try {
+               // loginContext.login();
+               // subject = loginContext.getSubject();
+               // } catch (LoginException e) {
+               // e.printStackTrace();
+               // display.dispose();
+               // try {
+               // Thread.sleep(2000);
+               // } catch (InterruptedException e1) {
+               // // silent
+               // }
+               // return null;
+               // }
+
+               uxContext = new SimpleSwtUxContext();
+               // UiContext.setData(CmsView.KEY, this);
+               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+               e4Application = getApplication(null);
+               Object res = Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
+
+                       @Override
+                       public Object run() throws Exception {
+                               return e4Application.start(context);
+                       }
+
+               });
+               return res;
+       }
+
+       @Override
+       public void stop() {
+               if (e4Application != null)
+                       e4Application.stop();
+       }
+
+       static IApplication getApplication(String[] args) {
+               IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME,
+                               Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application");
+               try {
+                       IConfigurationElement[] elements = extension.getConfigurationElements();
+                       if (elements.length > 0) {
+                               IConfigurationElement[] runs = elements[0].getChildren("run");
+                               if (runs.length > 0) {
+                                       Object runnable;
+                                       runnable = runs[0].createExecutableExtension("class");
+                                       if (runnable instanceof IApplication)
+                                               return (IApplication) runnable;
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot find e4 application", e);
+               }
+               throw new IllegalStateException("Cannot find e4 application");
+       }
+
+       public static Display createDisplay() {
+               Display.setAppName("Argeo CMS RCP");
+
+               // create the display
+               Display newDisplay = Display.getCurrent();
+               if (newDisplay == null) {
+                       newDisplay = new Display();
+               }
+               // Set the priority higher than normal so as to be higher
+               // than the JobManager.
+               Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1));
+               return newDisplay;
+       }
+
+       //
+       // CMS VIEW
+       //
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+               if (loginContext == null)
+                       throw new IllegalStateException("Login context cannot be null");
+               // logout previous login context
+               // if (this.loginContext != null)
+               // try {
+               // this.loginContext.logout();
+               // } catch (LoginException e1) {
+               // System.err.println("Could not log out: " + e1);
+               // }
+               this.loginContext = loginContext;
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext == null)
+                       throw new IllegalStateException("Login context should not bet null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       throw new IllegalStateException("Cannot log out", e);
+               }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public CmsImageManager getImageManager() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       @Override
+       public <T> T doAs(Callable<T> action) {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java
new file mode 100644 (file)
index 0000000..1d38fe7
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.cms.e4.rcp;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals;
+
+@SuppressWarnings("restriction")
+public class CmsRcpLifeCycle {
+
+       @PostContextCreate
+       void postContextCreate(IEclipseContext workbenchContext) {
+       }
+
+       @PreSave
+       void preSave(IEclipseContext workbenchContext) {
+       }
+
+       @ProcessAdditions
+       void processAdditions(IEclipseContext workbenchContext) {
+       }
+
+       @ProcessRemovals
+       void processRemovals(IEclipseContext workbenchContext) {
+       }
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/.classpath b/swt/rcp/org.argeo.cms.swt.rcp/.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.cms.swt.rcp/.gitignore b/swt/rcp/org.argeo.cms.swt.rcp/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/.project b/swt/rcp/org.argeo.cms.swt.rcp/.project
new file mode 100644 (file)
index 0000000..bcded59
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.swt.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.cms.swt.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpDisplayFactory.xml
new file mode 100644 (file)
index 0000000..8b1d146
--- /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="init" deactivate="destroy" immediate="true" name="CMS RCP Display Factory">
+   <implementation class="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+</scr:component>
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml b/swt/rcp/org.argeo.cms.swt.rcp/OSGI-INF/cmsRcpHttpLauncher.xml
new file mode 100644 (file)
index 0000000..03abe19
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="CMS RCP Servlet Factory">
+   <implementation class="org.argeo.cms.ui.rcp.CmsRcpHttpLauncher"/>
+   <reference bind="setHttpServer" cardinality="1..1" interface="com.sun.net.httpserver.HttpServer" name="HttpServer" policy="static"/>
+   <reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+</scr:component>
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.swt.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..5cf5164
--- /dev/null
@@ -0,0 +1,13 @@
+Bundle-SymbolicName: org.argeo.cms.swt.rcp;singleton=true
+
+Import-Package:\
+org.argeo.cms.auth,\
+org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.w3c.css.sac,\
+*
+
+Service-Component:\
+OSGI-INF/cmsRcpDisplayFactory.xml,\
+OSGI-INF/cmsRcpHttpLauncher.xml
+
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/build.properties b/swt/rcp/org.argeo.cms.swt.rcp/build.properties
new file mode 100644 (file)
index 0000000..4ed1d47
--- /dev/null
@@ -0,0 +1,6 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/,\
+               OSGI-INF/cmsRcpHttpLauncher.xml
+source.. = src/
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java
new file mode 100644 (file)
index 0000000..a88ff38
--- /dev/null
@@ -0,0 +1,167 @@
+package org.argeo.cms.ui.rcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.PrivilegedAction;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsEventBus;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsTheme;
+import org.argeo.api.cms.ux.CmsView;
+import org.argeo.cms.swt.AbstractSwtCmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
+import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Runs a {@link CmsApp} as an SWT desktop application. */
+@SuppressWarnings("restriction")
+public class CmsRcpApp extends AbstractSwtCmsView implements CmsView {
+       private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class);
+
+       private Shell shell;
+       private CmsApp cmsApp;
+
+       private CSSEngine cssEngine;
+
+       public CmsRcpApp(String uiName) {
+               super(uiName);
+               uid = UUID.randomUUID().toString();
+       }
+
+       public void initRcpApp() {
+               display = Display.getCurrent();
+               shell = new Shell(display);
+               shell.setText("Argeo CMS");
+               Composite parent = shell;
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
+
+               try {
+                       loginContext = new LoginContext(CmsAuth.SINGLE_USER.getLoginContextName());
+                       loginContext.login();
+               } catch (LoginException e) {
+                       throw new IllegalStateException("Could not log in.", e);
+               }
+               if (log.isDebugEnabled())
+                       log.debug("Logged in to desktop: " + loginContext.getSubject());
+
+               Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
+
+                       // TODO factorise with web app
+                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+                       ui = cmsApp.initUi(parent);
+                       if (ui instanceof Composite)
+                               ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+                       // we need ui to be set before refresh so that CmsView can store UI context data
+                       // in it.
+                       cmsApp.refreshUi(ui, null);
+
+                       // Styling
+                       CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
+                       if (theme != null) {
+                               cssEngine = new CSSSWTEngineImpl(display);
+                               for (String path : theme.getSwtCssPaths()) {
+                                       try (InputStream in = theme.loadPath(path)) {
+                                               cssEngine.parseStyleSheet(in);
+                                       } catch (IOException e) {
+                                               throw new IllegalStateException("Cannot load stylesheet " + path, e);
+                                       }
+                               }
+                               cssEngine.setErrorHandler(new CSSErrorHandler() {
+                                       public void error(Exception e) {
+                                               log.error("SWT styling error: ", e);
+                                       }
+                               });
+                               applyStyles(shell);
+                       }
+                       shell.layout(true, true);
+
+                       shell.open();
+                       return null;
+               });
+       }
+
+       /*
+        * CMS VIEW
+        */
+
+       @Override
+       public void navigateTo(String state) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext != null)
+                       try {
+                               loginContext.logout();
+                       } catch (LoginException e) {
+                               log.error("Cannot log out", e);
+                       }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               log.error("Unexpected exception in CMS RCP", e);
+       }
+
+       @Override
+       public CmsSession getCmsSession() {
+               CmsSession cmsSession = cmsApp.getCmsContext().getCmsSession(getSubject());
+               return cmsSession;
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return false;
+       }
+
+       @Override
+       public void applyStyles(Object node) {
+               if (cssEngine != null)
+                       cssEngine.applyStyles(node, true);
+       }
+
+       public Shell getShell() {
+               return shell;
+       }
+
+       @Override
+       public CmsEventBus getCmsEventBus() {
+               return cmsApp.getCmsContext().getCmsEventBus();
+       }
+
+       @Override
+       public CmsApp getCmsApp() {
+               return cmsApp;
+       }
+
+       /*
+        * DEPENDENCY INJECTION
+        */
+       public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               this.cmsApp = cmsApp;
+       }
+
+       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               this.cmsApp = null;
+       }
+
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpDisplayFactory.java
new file mode 100644 (file)
index 0000000..a83a54d
--- /dev/null
@@ -0,0 +1,93 @@
+package org.argeo.cms.ui.rcp;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Path;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.cms.util.OS;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.widgets.Display;
+
+/** Creates the SWT {@link Display} in a dedicated thread. */
+public class CmsRcpDisplayFactory {
+       private final static Logger logger = System.getLogger(CmsRcpDisplayFactory.class.getName());
+
+       /** File name in a run directory */
+       private final static String ARGEO_RCP_URL = "argeo.rcp.url";
+
+       /** There is only one display in RCP mode */
+       private static Display display;
+
+       private CmsUiThread uiThread;
+
+       private boolean shutdown = false;
+
+       public void init() {
+               uiThread = new CmsUiThread();
+               uiThread.start();
+               while (display == null)
+                       try {
+                               Thread.sleep(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+       }
+
+       public void destroy() {
+               shutdown = true;
+               display.wake();
+               try {
+                       uiThread.join();
+               } catch (InterruptedException e) {
+                       // silent
+               } finally {
+                       uiThread = null;
+               }
+       }
+
+       class CmsUiThread extends Thread {
+
+               public CmsUiThread() {
+                       super("CMS UI");
+               }
+
+               @Override
+               public void run() {
+                       try {
+                               display = Display.getDefault();
+                               display.setRuntimeExceptionHandler((e) -> e.printStackTrace());
+                               display.setErrorHandler((e) -> e.printStackTrace());
+
+                               while (!shutdown) {
+                                       if (!display.readAndDispatch())
+                                               display.sleep();
+                               }
+                               display.dispose();
+                               display = null;
+                       } catch (UnsatisfiedLinkError e) {
+                               logger.log(Level.ERROR,
+                                               "Cannot load SWT, probably because the OSGi framework has been refresh. Restart the application.",
+                                               e);
+                       }
+               }
+       }
+
+       public static Display getDisplay() {
+               return display;
+       }
+
+       public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
+               CmsRcpDisplayFactory.getDisplay().syncExec(() -> {
+                       CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
+                       cmsRcpApp.setCmsApp(cmsApp, null);
+                       cmsRcpApp.initRcpApp();
+                       if (disposeListener != null)
+                               cmsRcpApp.getShell().addDisposeListener(disposeListener);
+               });
+       }
+
+       public static Path getUrlRunFile() {
+               return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL);
+       }
+}
diff --git a/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java b/swt/rcp/org.argeo.cms.swt.rcp/src/org/argeo/cms/ui/rcp/CmsRcpHttpLauncher.java
new file mode 100644 (file)
index 0000000..6246b0d
--- /dev/null
@@ -0,0 +1,128 @@
+package org.argeo.cms.ui.rcp;
+
+import static java.lang.System.Logger.Level.DEBUG;
+
+import java.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.argeo.api.cms.CmsApp;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/** Publishes one {@link CmsRcpServlet} per {@link CmsApp}. */
+public class CmsRcpHttpLauncher {
+       private final static Logger logger = System.getLogger(CmsRcpHttpLauncher.class.getName());
+       private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+               Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+               try {
+                       if (Files.exists(runFile)) {
+                               Files.delete(runFile);
+                       }
+               } catch (IOException e) {
+                       logger.log(Level.ERROR, "Cannot delete " + runFile, e);
+               }
+       }
+
+       public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       httpServer.thenAcceptAsync((httpServer) -> {
+                               httpServer.createContext("/" + contextName, new HttpHandler() {
+
+                                       @Override
+                                       public void handle(HttpExchange exchange) throws IOException {
+                                               String path = exchange.getRequestURI().getPath();
+                                               String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : "";
+                                               CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+                                               exchange.sendResponseHeaders(200, -1);
+                                               logger.log(Level.DEBUG, "Opened RCP UI  " + uiName + " of  CMS App /" + contextName);
+                                       }
+                               });
+                       }).exceptionally(e -> {
+                               logger.log(Level.ERROR, "Cannot register RCO app " + contextName, e);
+                               return null;
+                       });
+                       logger.log(Level.DEBUG, "Registered RCP CMS APP /" + contextName);
+               }
+       }
+
+       public void removeCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
+               if (contextName != null) {
+                       httpServer.thenAcceptAsync((httpServer) -> {
+                               httpServer.removeContext("/" + contextName);
+                       });
+               }
+       }
+
+       public void setHttpServer(HttpServer httpServer) {
+               Integer httpPort = httpServer.getAddress().getPort();
+               String baseUrl = "http://localhost:" + httpPort + "/";
+               Path runFile = CmsRcpDisplayFactory.getUrlRunFile();
+               try {
+                       if (!Files.exists(runFile)) {
+                               Files.createDirectories(runFile.getParent());
+                               // TODO give read permission only to the owner
+                               Files.createFile(runFile);
+                       } else {
+                               URI uri = URI.create(Files.readString(runFile));
+                               if (!httpPort.equals(uri.getPort()))
+                                       if (!isPortAvailable(uri.getPort())) {
+                                               throw new IllegalStateException("Another CMS is running on " + runFile);
+                                       } else {
+                                               logger.log(Level.WARNING,
+                                                               "Run file " + runFile + " found but port of " + uri + " is available. Overwriting...");
+                                       }
+                       }
+                       Files.writeString(runFile, baseUrl, StandardCharsets.UTF_8);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot write run file to " + runFile, e);
+               }
+               logger.log(DEBUG, "RCP available under " + baseUrl + ", written to " + runFile);
+               this.httpServer.complete(httpServer);
+       }
+
+       protected boolean isPortAvailable(int port) {
+               ServerSocket ss = null;
+               DatagramSocket ds = null;
+               try {
+                       ss = new ServerSocket(port);
+                       ss.setReuseAddress(true);
+                       ds = new DatagramSocket(port);
+                       ds.setReuseAddress(true);
+                       return true;
+               } catch (IOException e) {
+               } finally {
+                       if (ds != null) {
+                               ds.close();
+                       }
+
+                       if (ss != null) {
+                               try {
+                                       ss.close();
+                               } catch (IOException e) {
+                                       /* should not be thrown */
+                               }
+                       }
+               }
+
+               return false;
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.classpath b/swt/rcp/org.argeo.swt.specific.rcp/.classpath
new file mode 100644 (file)
index 0000000..248abe0
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src" />
+       <classpathentry kind="con"
+               path="org.eclipse.pde.core.requiredPlugins" />
+       <classpathentry kind="con"
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17" />
+       <classpathentry kind="output" path="bin" />
+</classpath>
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.gitignore b/swt/rcp/org.argeo.swt.specific.rcp/.gitignore
new file mode 100644 (file)
index 0000000..5e77890
--- /dev/null
@@ -0,0 +1,3 @@
+/target/
+/bin/
+*.log
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/.project b/swt/rcp/org.argeo.swt.specific.rcp/.project
new file mode 100644 (file)
index 0000000..c79ee3f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.specific.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/swt/rcp/org.argeo.swt.specific.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..bb88efd
--- /dev/null
@@ -0,0 +1,20 @@
+Import-Package: \
+!java.*,\
+org.apache.commons.io,\
+org.eclipse.core.commands,\
+!org.eclipse.core.runtime,\
+!org.eclipse.ui.plugin,\
+org.eclipse.swt,\
+javax.servlet.http;version="[3,5)",\
+javax.servlet;version="[3,5)",\
+*
+
+Export-Package: org.argeo.*,\
+org.eclipse.rap.fileupload.*;version="3.10",\
+org.eclipse.rap.rwt.*;version="3.10"
+
+# Was !org.eclipse.core.commands,\ why ?
+
+#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
+#Bundle-ActivationPolicy: lazy
+#Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/build.properties b/swt/rcp/org.argeo.swt.specific.rcp/build.properties
new file mode 100644 (file)
index 0000000..c6b651a
--- /dev/null
@@ -0,0 +1,3 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java
new file mode 100644 (file)
index 0000000..0d9ce48
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.client.service.ClientService;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class RcpClient implements Client {
+
+       @Override
+       public <T extends ClientService> T getService(Class<T> type) {
+               if (type.isAssignableFrom(JavaScriptExecutor.class))
+                       return (T) javaScriptExecutor;
+               else if (type.isAssignableFrom(BrowserNavigation.class))
+                       return (T) browserNavigation;
+               else
+                       return null;
+       }
+
+       private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() {
+
+               @Override
+               public void execute(String code) {
+                       // TODO Auto-generated method stub
+
+               }
+       };
+       private BrowserNavigation browserNavigation = new BrowserNavigation() {
+
+               @Override
+               public void pushState(String state, String title) {
+                       // TODO Auto-generated method stub
+
+               }
+
+               @Override
+               public void addBrowserNavigationListener(
+                               BrowserNavigationListener listener) {
+                       // TODO Auto-generated method stub
+
+               }
+       };
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java
new file mode 100644 (file)
index 0000000..47ff35d
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.argeo.cms.util.StreamUtils;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RcpResourceManager implements ResourceManager {
+       private Map<String, byte[]> register = Collections.synchronizedMap(new TreeMap<String, byte[]>());
+
+       @Override
+       public void register(String name, InputStream in) {
+               try {
+                       register.put(name, StreamUtils.toByteArray(in));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot register " + name, e);
+               }
+       }
+
+       @Override
+       public boolean unregister(String name) {
+               return register.remove(name) != null;
+       }
+
+       @Override
+       public InputStream getRegisteredContent(String name) {
+               return new ByteArrayInputStream(register.get(name));
+       }
+
+       @Override
+       public String getLocation(String name) {
+               return name;
+       }
+
+       @Override
+       public boolean isRegistered(String name) {
+               return register.containsKey(name);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/BufferedImageDisplay.java
new file mode 100644 (file)
index 0000000..7fd2db1
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.awt.BorderLayout;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+
+import javax.swing.JPanel;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.awt.SWT_AWT;
+import org.eclipse.swt.widgets.Composite;
+
+public class BufferedImageDisplay extends Composite {
+       private BufferedImage image;
+
+       public BufferedImageDisplay(Composite parent, int style) {
+               super(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND);
+               Frame frame = SWT_AWT.new_Frame(this);
+               frame.setLayout(new BorderLayout());
+               frame.add(new JPanel() {
+                       private static final long serialVersionUID = 8924410573598922364L;
+
+                       public void paintComponent(Graphics g) {
+                               super.paintComponent(g);
+                               if (image != null)
+                                       g.drawImage(image, 0, 0, this);
+                       }
+
+               }, BorderLayout.CENTER);
+               frame.setVisible(true);
+       }
+
+       public void setImage(BufferedImage image) {
+               this.image = image;
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
new file mode 100644 (file)
index 0000000..0c5d346
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+       public CmsFileDialog(Shell parent, int style) {
+               super(parent, style);
+       }
+
+       public CmsFileDialog(Shell parent) {
+               super(parent);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
new file mode 100644 (file)
index 0000000..638859a
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+       public CmsFileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public void setText(String text) {
+               super.setText(text);
+       }
+
+       @Override
+       public String getFileName() {
+               return super.getFileName();
+       }
+
+       @Override
+       public String[] getFileNames() {
+               return super.getFileNames();
+       }
+
+       @Override
+       public void addSelectionListener(SelectionListener listener) {
+               super.addSelectionListener(listener);
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java
new file mode 100644 (file)
index 0000000..fbb4fbf
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.eclipse.ui.specific;
+
+/** RCP specific {@link NLS} to be extended */
+public class DefaultNLS {// extends NLS {
+//     public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+//
+//     public DefaultNLS() {
+//             this(DEFAULT_BUNDLE_LOCATION);
+//     }
+//
+//     public DefaultNLS(String bundleName) {
+//             NLS.initializeMessages(bundleName, getClass());
+//     }
+}
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java
new file mode 100644 (file)
index 0000000..ac862d7
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.eclipse.ui.specific;
+
+/** Constants which are specific to RWT.*/
+public interface EclipseUiConstants {
+       final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+       final static String MARKUP_SUPPORT = null;
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
new file mode 100644 (file)
index 0000000..d1acbcf
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+       private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+
+       public static void setStyleData(Widget widget, Object data) {
+               widget.setData(CSS_CLASS, data);
+       }
+
+       public static Object getStyleData(Widget widget) {
+               return widget.getData(CSS_CLASS);
+       }
+
+       public static void setMarkupData(Widget widget) {
+               // does nothing
+       }
+
+       public static void setMarkupValidationDisabledData(Widget widget) {
+               // does nothing
+       }
+
+       /**
+        * TootlTip support is supported for {@link ColumnViewer} in RCP
+        * 
+        * @see ColumnViewerToolTipSupport#enableFor(Viewer)
+        */
+       public static void enableToolTipSupport(Viewer viewer) {
+               if (viewer instanceof ColumnViewer)
+                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+       }
+
+       private EclipseUiSpecificUtils() {
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
new file mode 100644 (file)
index 0000000..524447e
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.FileTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+public class FileDropAdapter {
+
+       public void prepareDropTarget(Control control, DropTarget dropTarget) {
+               dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
+               dropTarget.addDropListener(new DropTargetAdapter() {
+                       @Override
+                       public void dropAccept(DropTargetEvent event) {
+                               if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+                                       event.detail = DND.DROP_NONE;
+                               }
+                       }
+
+                       @Override
+                       public void drop(DropTargetEvent event) {
+                               handleFileDrop(control, event);
+                       }
+               });
+       }
+
+       public void handleFileDrop(Control control, DropTargetEvent event) {
+               String fileList[] = null;
+               FileTransfer ft = FileTransfer.getInstance();
+               if (ft.isSupportedType(event.currentDataType)) {
+                       fileList = (String[]) event.data;
+               }
+               System.out.println(Arrays.toString(fileList));
+       }
+
+       /** Executed in UI thread */
+       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java
new file mode 100644 (file)
index 0000000..20163cf
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+
+       public static HttpServletRequest getHttpRequest() {
+               return null;
+       }
+
+       public static HttpServletResponse getHttpResponse() {
+               return null;
+       }
+
+       public static Locale getLocale() {
+               return Locale.getDefault();
+       }
+
+       public static void setLocale(Locale locale) {
+               Locale.setDefault(locale);
+       }
+
+       /** Can always be null */
+       @SuppressWarnings("unchecked")
+       public static <T> T getData(String key) {
+               Display display = getDisplay();
+               if (display == null)
+                       return null;
+               return (T) display.getData(key);
+       }
+
+       public static void setData(String key, Object value) {
+               Display display = getDisplay();
+               if (display == null)
+                       throw new IllegalStateException("Not display available");
+               display.setData(key, value);
+       }
+
+       private static Display getDisplay() {
+               return Display.getCurrent();
+       }
+
+       private UiContext() {
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java
new file mode 100644 (file)
index 0000000..fbb36dd
--- /dev/null
@@ -0,0 +1,9 @@
+package org.eclipse.rap.fileupload;
+
+public interface FileDetails {
+       String getContentType();
+
+       long getContentLength();
+
+       String getFileName();
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java
new file mode 100644 (file)
index 0000000..a745280
--- /dev/null
@@ -0,0 +1,21 @@
+package org.eclipse.rap.fileupload;
+
+import java.util.EventObject;
+
+public abstract class FileUploadEvent extends EventObject {
+
+       private static final long serialVersionUID = 1L;
+
+       protected FileUploadEvent(FileUploadHandler source) {
+               super(source);
+       }
+
+       public abstract FileDetails[] getFileDetails();
+
+       public abstract long getContentLength();
+
+       public abstract long getBytesRead();
+
+       public abstract Exception getException();
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java
new file mode 100644 (file)
index 0000000..7d89300
--- /dev/null
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+/**
+ * A file upload handler is used to accept file uploads from a client. After
+ * creating a file upload handler, the server will accept file uploads to the
+ * URL returned by <code>getUploadUrl()</code>. Upload listeners can be attached
+ * to react on progress. When the upload has finished, a FileUploadHandler has
+ * to be disposed of by calling its <code>dispose()</code> method.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class FileUploadHandler {
+
+       public FileUploadHandler(FileUploadReceiver fileUploadReceiver) {
+       }
+
+       public void dispose() {
+
+       }
+
+       public void addUploadListener(FileUploadListener listener) {
+
+       }
+
+       public String getUploadUrl() {
+               return null;
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java
new file mode 100644 (file)
index 0000000..b59fd39
--- /dev/null
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import org.eclipse.swt.widgets.Display;
+
+
+/**
+ * Listener to react on progress and completion of a file upload.
+ * <p>
+ * <strong>Note:</strong> This listener will be called from a different thread than the UI thread.
+ * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI.
+ * </p>
+ *
+ * @see FileUploadEvent
+ */
+public interface FileUploadListener {
+
+  /**
+   * Called when new information about an in-progress upload is available.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent#getBytesRead()
+   */
+  void uploadProgress( FileUploadEvent event );
+
+  /**
+   * Called when a file upload has finished successfully.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent
+   */
+  void uploadFinished( FileUploadEvent event );
+
+  /**
+   * Called when a file upload failed.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent#getErrorMessage()
+   */
+  void uploadFailed( FileUploadEvent event );
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java
new file mode 100644 (file)
index 0000000..3f4cf47
--- /dev/null
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2013 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Instances of this interface are responsible for reading and processing the data from a file
+ * upload.
+ */
+public abstract class FileUploadReceiver {
+
+  /**
+   * Reads and processes all data from the provided input stream.
+   *
+   * @param stream the stream to read from
+   * @param details the details of the uploaded file like file name, content-type and size
+   * @throws IOException if an input / output error occurs
+   */
+  public abstract void receive( InputStream stream, FileDetails details ) throws IOException;
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java
new file mode 100644 (file)
index 0000000..1688594
--- /dev/null
@@ -0,0 +1,45 @@
+package org.eclipse.rap.rwt;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient;
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager;
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RWT {
+       public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT";
+       public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED";
+       public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED";
+       public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT";
+       public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS";
+       public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS";
+       public final static String DEFAULT_THEME_ID  = "argeo-rcp:DEFAULT_THEME_ID";
+
+       public final static int HYPERLINK = 0;
+
+       private static Locale locale = Locale.getDefault();
+       private static RcpClient client = new RcpClient();
+       private static ResourceManager resourceManager = new RcpResourceManager();
+       static {
+
+       }
+
+       public static Locale getLocale() {
+               return locale;
+       }
+
+       public static HttpServletRequest getRequest() {
+               return null;
+       }
+
+       public static ResourceManager getResourceManager() {
+               return resourceManager;
+       }
+
+       public static Client getClient() {
+               return client;
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java
new file mode 100644 (file)
index 0000000..6e30aa6
--- /dev/null
@@ -0,0 +1,7 @@
+package org.eclipse.rap.rwt;
+
+public class SingletonUtil {
+       public static <T> T getSessionInstance(Class<T> clss) {
+               return null;
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java
new file mode 100644 (file)
index 0000000..980a818
--- /dev/null
@@ -0,0 +1,43 @@
+package org.eclipse.rap.rwt.application;
+
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public abstract class AbstractEntryPoint implements EntryPoint {
+       private Display display;
+       private Shell shell;
+
+       protected Shell createShell(Display display) {
+               return new Shell(display);
+       }
+
+       protected void createContents(Composite parent) {
+
+       }
+
+       public int createUI() {
+               display = new Display();
+               shell = createShell(display);
+               shell.setLayout(new GridLayout(1, false));
+               createContents(shell);
+               if (shell.getMaximized()) {
+                       shell.layout();
+               } else {
+                       shell.pack();
+               }
+               shell.open();
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch()) {
+                               display.sleep();
+                       }
+               }
+               display.dispose();
+               return 0;
+       }
+
+       protected Shell getShell() {
+               return shell;
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java
new file mode 100644 (file)
index 0000000..6cb5f29
--- /dev/null
@@ -0,0 +1,27 @@
+package org.eclipse.rap.rwt.application;
+
+import java.util.Map;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+public interface Application {
+       public static enum OperationMode {
+               JEE_COMPATIBILITY, SWT_COMPATIBILITY,
+       }
+
+       void setOperationMode(OperationMode operationMode);
+
+       void addResource(String name, ResourceLoader resourceLoader);
+
+       void setExceptionHandler(ExceptionHandler exceptionHandler);
+
+       void addEntryPoint(String path, EntryPointFactory entryPointFactory,
+                       Map<String, String> properties);
+
+       void addEntryPoint(String path, Class<? extends EntryPoint> entryPoint,
+                       Map<String, String> properties);
+
+       void addStyleSheet(String themeId, String styleSheetLocation,
+                       ResourceLoader resourceLoader);
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java
new file mode 100644 (file)
index 0000000..961ad70
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface ApplicationConfiguration {
+       void configure(Application application);
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java
new file mode 100644 (file)
index 0000000..c0d559a
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPoint {
+       int createUI();
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java
new file mode 100644 (file)
index 0000000..d5b24d8
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPointFactory {
+       public EntryPoint create();
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java
new file mode 100644 (file)
index 0000000..13daf21
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface ExceptionHandler {
+       public void handleException(Throwable throwable);
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java
new file mode 100644 (file)
index 0000000..934feae
--- /dev/null
@@ -0,0 +1,18 @@
+package org.eclipse.rap.rwt.client;
+
+import java.io.Serializable;
+
+import org.eclipse.rap.rwt.client.service.ClientService;
+
+public interface Client extends Serializable {
+
+  /**
+   * Returns this client's implementation of a given service, if available.
+   *
+   * @param type the type of the requested service, must be a subtype of ClientService
+   * @return the requested service if provided by this client, otherwise <code>null</code>
+   * @see ClientService
+   */
+  <T extends ClientService> T getService( Class<T> type );
+
+}
\ No newline at end of file
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java
new file mode 100644 (file)
index 0000000..1f19bdd
--- /dev/null
@@ -0,0 +1,10 @@
+package org.eclipse.rap.rwt.client;
+
+public interface WebClient {
+       public final static String FAVICON = "rcp:FAVICON";
+       public final static String PAGE_TITLE = "rcp:PAGE_TITLE";
+       public final static String BODY_HTML = "rcp:BODY_HTML";
+       public final static String THEME_ID = "rcp:THEME_ID";
+       public final static String HEAD_HTML = "rcp:HEAD_HTML";
+       public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW";
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java
new file mode 100644 (file)
index 0000000..ffba4e4
--- /dev/null
@@ -0,0 +1,7 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigation extends ClientService {
+       void pushState(String state, String title);
+
+       void addBrowserNavigationListener(BrowserNavigationListener listener);
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java
new file mode 100644 (file)
index 0000000..3e1b3eb
--- /dev/null
@@ -0,0 +1,10 @@
+package org.eclipse.rap.rwt.client.service;
+
+public class BrowserNavigationEvent {
+       private String state;
+
+       public String getState() {
+               return state;
+       }
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java
new file mode 100644 (file)
index 0000000..8319c03
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigationListener {
+       public void navigated(BrowserNavigationEvent event);
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java
new file mode 100644 (file)
index 0000000..9f479d1
--- /dev/null
@@ -0,0 +1,6 @@
+package org.eclipse.rap.rwt.client.service;
+
+import java.io.Serializable;
+
+public interface ClientService extends Serializable {
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
new file mode 100644 (file)
index 0000000..6c44c72
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface JavaScriptExecutor extends ClientService {
+       public void execute( String code );
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java
new file mode 100644 (file)
index 0000000..9dae811
--- /dev/null
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.rwt.client.service;
+
+/**
+ * The UrlLauncher service allows loading an URL in an external window, application or save dialog.
+ *
+ * @since 2.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface UrlLauncher extends ClientService {
+
+  /**
+   * Opens the given URL.
+   *
+   * Any HTTP URL or relative URL will be opened in a new window.
+   * Modern browser may block any attempt to open new windows, but will usually prompt the user to
+   * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only
+   * to future attempts. It could also trigger a document reload, causing a session restart.
+   *
+   * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client
+   * to have a matching protocol handler registered.
+   *
+   * @param url the URL to open
+   */
+  void openURL( String url );
+
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java
new file mode 100644 (file)
index 0000000..7e7116c
--- /dev/null
@@ -0,0 +1,9 @@
+package org.eclipse.rap.rwt.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface ResourceLoader {
+       public InputStream getResourceAsStream(String resourceName)
+                       throws IOException;
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java
new file mode 100644 (file)
index 0000000..c3379ea
--- /dev/null
@@ -0,0 +1,15 @@
+package org.eclipse.rap.rwt.service;
+
+import java.io.InputStream;
+
+public interface ResourceManager {
+       public void register(String name, InputStream in);
+
+       boolean unregister(String name);
+
+       public InputStream getRegisteredContent(String name);
+
+       public String getLocation(String name);
+
+       public boolean isRegistered(String name);
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java
new file mode 100644 (file)
index 0000000..bed194f
--- /dev/null
@@ -0,0 +1,12 @@
+package org.eclipse.rap.rwt.service;
+
+/** Mock, does nothing as this is irrelevant for RCP. */
+public class ServerPushSession {
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java
new file mode 100644 (file)
index 0000000..b2a2005
--- /dev/null
@@ -0,0 +1,33 @@
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Widget;
+
+public class DropDown {
+       private boolean visible=false;
+
+       public DropDown(Widget parent, int style) {
+               // FIXME implement a shell
+       }
+
+       public DropDown(Widget parent) {
+               this(parent, SWT.NONE);
+       }
+
+       public void setVisible(boolean visible) {
+               this.visible = visible;
+       }
+
+       public boolean isVisible() {
+               return visible;
+       }
+       
+       public void setItems( String[] items ) {
+               
+       }
+       
+       public void setSelectionIndex( int selection ) {
+               
+       }
+       
+}
diff --git a/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java b/swt/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java
new file mode 100644 (file)
index 0000000..cbf1449
--- /dev/null
@@ -0,0 +1,37 @@
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+public class FileUpload extends Composite {
+
+       public FileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       public void addSelectionListener(SelectionListener listener) {
+
+       }
+
+       public void submit(String url) {
+
+       }
+
+       public void setImage(Image image) {
+
+       }
+
+       public void setText(String text) {
+
+       }
+
+       public String getFileName() {
+               return null;
+       }
+
+       public String[] getFileNames() {
+               return null;
+       }
+
+}