Put Eclipse and JCR components in subdirs in order to clarify indirect
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 28 Jan 2022 10:27:24 +0000 (11:27 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 28 Jan 2022 10:27:24 +0000 (11:27 +0100)
licensing issues with GPL.

1042 files changed:
Makefile
eclipse/cnf/maven.bnd [new file with mode: 0644]
eclipse/org.argeo.cms.e4/.classpath [new file with mode: 0644]
eclipse/org.argeo.cms.e4/.project [new file with mode: 0644]
eclipse/org.argeo.cms.e4/META-INF/.gitignore [new file with mode: 0644]
eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml [new file with mode: 0644]
eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml [new file with mode: 0644]
eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml [new file with mode: 0644]
eclipse/org.argeo.cms.e4/bnd.bnd [new file with mode: 0644]
eclipse/org.argeo.cms.e4/build.properties [new file with mode: 0644]
eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi [new file with mode: 0644]
eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi [new file with mode: 0644]
eclipse/org.argeo.cms.e4/pom.xml [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/.classpath [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/.project [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/bnd.bnd [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/build.properties [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/pom.xml [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java [new file with mode: 0644]
eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/.classpath [new file with mode: 0644]
eclipse/org.argeo.cms.swt/.project [new file with mode: 0644]
eclipse/org.argeo.cms.swt/META-INF/.gitignore [new file with mode: 0644]
eclipse/org.argeo.cms.swt/bnd.bnd [new file with mode: 0644]
eclipse/org.argeo.cms.swt/build.properties [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/add.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/close-all.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/delete.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/edit.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/save-all.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/actions/save.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/active.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/add.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/add.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/addFolder.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/addPrivileges.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/addRepo.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/addWorkspace.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/adminLog.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/batch.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/begin.gif [new file with mode: 0755]
eclipse/org.argeo.cms.swt/icons/binary.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/browser.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/bundles.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/changePassword.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/clear.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/close-all.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/commit.gif [new file with mode: 0755]
eclipse/org.argeo.cms.swt/icons/delete.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/dumpNode.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/file.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/folder.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/getSize.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/group.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/home.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/home.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/import_fs.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/installed.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/log.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/logout.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/maintenance.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/node.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/nodes.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/password.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/person-logged-in.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/person.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/query.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/refresh.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/remote_connected.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/remove.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/removePrivileges.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/rename.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/repositories.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/repository_connected.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/resolved.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/role.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/rollback.gif [new file with mode: 0755]
eclipse/org.argeo.cms.swt/icons/save-all.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/save.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/save.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/save_security.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/save_security_disabled.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/security.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/service_published.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/service_referenced.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/sort.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/starting.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/sync.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/user.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/users.gif [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/workgroup.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/workgroup.xcf [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/workspace_connected.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/pom.xml [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java [new file with mode: 0644]
eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java [new file with mode: 0644]
eclipse/pom.xml [new file with mode: 0644]
jcr/cnf/maven.bnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/.classpath [new file with mode: 0644]
jcr/org.argeo.cms.jcr/.project [new file with mode: 0644]
jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/bnd.bnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/build.properties [new file with mode: 0644]
jcr/org.argeo.cms.jcr/pom.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java [new file with mode: 0644]
jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/.classpath [new file with mode: 0644]
jcr/org.argeo.cms.ui/.project [new file with mode: 0644]
jcr/org.argeo.cms.ui/META-INF/.gitignore [new file with mode: 0644]
jcr/org.argeo.cms.ui/bnd.bnd [new file with mode: 0644]
jcr/org.argeo.cms.ui/build.properties [new file with mode: 0644]
jcr/org.argeo.cms.ui/icons/loading.gif [new file with mode: 0644]
jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png [new file with mode: 0644]
jcr/org.argeo.cms.ui/icons/noPic-square-640px.png [new file with mode: 0644]
jcr/org.argeo.cms.ui/pom.xml [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java [new file with mode: 0644]
jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java [new file with mode: 0644]
jcr/pom.xml [new file with mode: 0644]
org.argeo.cms.e4/.classpath [deleted file]
org.argeo.cms.e4/.project [deleted file]
org.argeo.cms.e4/META-INF/.gitignore [deleted file]
org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml [deleted file]
org.argeo.cms.e4/OSGI-INF/homeRepository.xml [deleted file]
org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml [deleted file]
org.argeo.cms.e4/bnd.bnd [deleted file]
org.argeo.cms.e4/build.properties [deleted file]
org.argeo.cms.e4/e4xmi/cms-devops.e4xmi [deleted file]
org.argeo.cms.e4/e4xmi/cms-ego.e4xmi [deleted file]
org.argeo.cms.e4/pom.xml [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java [deleted file]
org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java [deleted file]
org.argeo.cms.jcr/.classpath [deleted file]
org.argeo.cms.jcr/.project [deleted file]
org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs [deleted file]
org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/filesServlet.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml [deleted file]
org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml [deleted file]
org.argeo.cms.jcr/bnd.bnd [deleted file]
org.argeo.cms.jcr/build.properties [deleted file]
org.argeo.cms.jcr/pom.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java [deleted file]
org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java [deleted file]
org.argeo.cms.servlet/.classpath [deleted file]
org.argeo.cms.servlet/.project [deleted file]
org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml [deleted file]
org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml [deleted file]
org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml [deleted file]
org.argeo.cms.servlet/bnd.bnd [deleted file]
org.argeo.cms.servlet/build.properties [deleted file]
org.argeo.cms.servlet/pom.xml [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java [deleted file]
org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java [deleted file]
org.argeo.cms.swt/.classpath [deleted file]
org.argeo.cms.swt/.project [deleted file]
org.argeo.cms.swt/META-INF/.gitignore [deleted file]
org.argeo.cms.swt/bnd.bnd [deleted file]
org.argeo.cms.swt/build.properties [deleted file]
org.argeo.cms.swt/icons/actions/add.png [deleted file]
org.argeo.cms.swt/icons/actions/close-all.png [deleted file]
org.argeo.cms.swt/icons/actions/delete.png [deleted file]
org.argeo.cms.swt/icons/actions/edit.png [deleted file]
org.argeo.cms.swt/icons/actions/save-all.png [deleted file]
org.argeo.cms.swt/icons/actions/save.png [deleted file]
org.argeo.cms.swt/icons/active.gif [deleted file]
org.argeo.cms.swt/icons/add.gif [deleted file]
org.argeo.cms.swt/icons/add.png [deleted file]
org.argeo.cms.swt/icons/addFolder.gif [deleted file]
org.argeo.cms.swt/icons/addPrivileges.gif [deleted file]
org.argeo.cms.swt/icons/addRepo.gif [deleted file]
org.argeo.cms.swt/icons/addWorkspace.png [deleted file]
org.argeo.cms.swt/icons/adminLog.gif [deleted file]
org.argeo.cms.swt/icons/batch.gif [deleted file]
org.argeo.cms.swt/icons/begin.gif [deleted file]
org.argeo.cms.swt/icons/binary.png [deleted file]
org.argeo.cms.swt/icons/browser.gif [deleted file]
org.argeo.cms.swt/icons/bundles.gif [deleted file]
org.argeo.cms.swt/icons/changePassword.gif [deleted file]
org.argeo.cms.swt/icons/clear.gif [deleted file]
org.argeo.cms.swt/icons/close-all.png [deleted file]
org.argeo.cms.swt/icons/commit.gif [deleted file]
org.argeo.cms.swt/icons/delete.png [deleted file]
org.argeo.cms.swt/icons/dumpNode.gif [deleted file]
org.argeo.cms.swt/icons/file.gif [deleted file]
org.argeo.cms.swt/icons/folder.gif [deleted file]
org.argeo.cms.swt/icons/getSize.gif [deleted file]
org.argeo.cms.swt/icons/group.png [deleted file]
org.argeo.cms.swt/icons/home.gif [deleted file]
org.argeo.cms.swt/icons/home.png [deleted file]
org.argeo.cms.swt/icons/import_fs.png [deleted file]
org.argeo.cms.swt/icons/installed.gif [deleted file]
org.argeo.cms.swt/icons/log.gif [deleted file]
org.argeo.cms.swt/icons/logout.png [deleted file]
org.argeo.cms.swt/icons/maintenance.gif [deleted file]
org.argeo.cms.swt/icons/node.gif [deleted file]
org.argeo.cms.swt/icons/nodes.gif [deleted file]
org.argeo.cms.swt/icons/osgi_explorer.gif [deleted file]
org.argeo.cms.swt/icons/password.gif [deleted file]
org.argeo.cms.swt/icons/person-logged-in.png [deleted file]
org.argeo.cms.swt/icons/person.png [deleted file]
org.argeo.cms.swt/icons/query.png [deleted file]
org.argeo.cms.swt/icons/refresh.png [deleted file]
org.argeo.cms.swt/icons/remote_connected.gif [deleted file]
org.argeo.cms.swt/icons/remote_disconnected.gif [deleted file]
org.argeo.cms.swt/icons/remove.gif [deleted file]
org.argeo.cms.swt/icons/removePrivileges.gif [deleted file]
org.argeo.cms.swt/icons/rename.gif [deleted file]
org.argeo.cms.swt/icons/repositories.gif [deleted file]
org.argeo.cms.swt/icons/repository_connected.gif [deleted file]
org.argeo.cms.swt/icons/repository_disconnected.gif [deleted file]
org.argeo.cms.swt/icons/resolved.gif [deleted file]
org.argeo.cms.swt/icons/role.gif [deleted file]
org.argeo.cms.swt/icons/rollback.gif [deleted file]
org.argeo.cms.swt/icons/save-all.png [deleted file]
org.argeo.cms.swt/icons/save.gif [deleted file]
org.argeo.cms.swt/icons/save.png [deleted file]
org.argeo.cms.swt/icons/save_security.png [deleted file]
org.argeo.cms.swt/icons/save_security_disabled.png [deleted file]
org.argeo.cms.swt/icons/security.gif [deleted file]
org.argeo.cms.swt/icons/service_published.gif [deleted file]
org.argeo.cms.swt/icons/service_referenced.gif [deleted file]
org.argeo.cms.swt/icons/sort.gif [deleted file]
org.argeo.cms.swt/icons/starting.gif [deleted file]
org.argeo.cms.swt/icons/sync.gif [deleted file]
org.argeo.cms.swt/icons/user.gif [deleted file]
org.argeo.cms.swt/icons/users.gif [deleted file]
org.argeo.cms.swt/icons/workgroup.png [deleted file]
org.argeo.cms.swt/icons/workgroup.xcf [deleted file]
org.argeo.cms.swt/icons/workspace_connected.png [deleted file]
org.argeo.cms.swt/icons/workspace_disconnected.png [deleted file]
org.argeo.cms.swt/pom.xml [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java [deleted file]
org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java [deleted file]
org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java [deleted file]
org.argeo.cms.ui/.classpath [deleted file]
org.argeo.cms.ui/.project [deleted file]
org.argeo.cms.ui/META-INF/.gitignore [deleted file]
org.argeo.cms.ui/bnd.bnd [deleted file]
org.argeo.cms.ui/build.properties [deleted file]
org.argeo.cms.ui/icons/loading.gif [deleted file]
org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png [deleted file]
org.argeo.cms.ui/icons/noPic-square-640px.png [deleted file]
org.argeo.cms.ui/pom.xml [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java [deleted file]
pom.xml

index 21af0b9d0cc5334ca8f3dcd42e50f82314623329..ad95904076e04fbd0d0c0469e0e749c369baca5c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -61,10 +61,10 @@ $(SDK_SRC_BASE)/org.argeo.cms/bin:$\
 
 rcp: base
        $(JVM) -jar $(ECJ_JAR) -11 -nowarn -time -cp $(RCP_CLASSPATH) \
-       $(SDK_SRC_BASE)/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/org.argeo.cms.servlet/bin] \
+       $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/bin] \
        $(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/bin] \
        $(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/bin] \
-       $(SDK_SRC_BASE)/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/org.argeo.cms.swt/bin] \
+       $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/bin] \
        $(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/bin] \
 
 
diff --git a/eclipse/cnf/maven.bnd b/eclipse/cnf/maven.bnd
new file mode 100644 (file)
index 0000000..4bd5c0c
--- /dev/null
@@ -0,0 +1 @@
+-include: ../../cnf/maven.bnd
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.e4/.classpath b/eclipse/org.argeo.cms.e4/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /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-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
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/eclipse/org.argeo.cms.e4/META-INF/.gitignore b/eclipse/org.argeo.cms.e4/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/eclipse/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/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml
new file mode 100644 (file)
index 0000000..65690f2
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="Home Repository">
+   <implementation class="org.argeo.cms.e4.OsgiFilterContextFunction"/>
+   <property name="service.context.key" type="String" value="(cn=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
new file mode 100644 (file)
index 0000000..a267aa5
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="User Admin Wrapper">
+   <implementation class="org.argeo.cms.e4.users.UserAdminWrapper"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.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
new file mode 100644 (file)
index 0000000..e4a4519
--- /dev/null
@@ -0,0 +1,15 @@
+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,\
+*
diff --git a/eclipse/org.argeo.cms.e4/build.properties b/eclipse/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/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi
new file mode 100644 (file)
index 0000000..89bcc37
--- /dev/null
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="ASCII"?>
+<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_XqkCQKknEeObFrG_clJBYA" elementId="">
+  <children xsi:type="basic:TrimmedWindow" xmi:id="_Zdy6cKknEeObFrG_clJBYA" elementId="org.argeo.cms.e4.apps.admin.trimmedwindow.0" label="" x="10" y="10" width="500" height="500">
+    <persistedState key="styleOverride" value="8"/>
+    <tags>shellMaximized</tags>
+    <tags>auth.cn=admin,ou=roles,ou=node</tags>
+    <children xsi:type="advanced:PerspectiveStack" xmi:id="_jXVqsCk4Eein5vuhpK-Dew" elementId="org.argeo.cms.e4.perspectivestack.0" selectedElement="_xOVlsDvOEeiF1foPJZSZkw">
+      <children xsi:type="advanced:Perspective" xmi:id="_xOVlsDvOEeiF1foPJZSZkw" elementId="org.argeo.cms.e4.perspective.users" label="Users" iconURI="platform:/plugin/org.argeo.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
new file mode 100644 (file)
index 0000000..ef659fc
--- /dev/null
@@ -0,0 +1,67 @@
+<?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/pom.xml b/eclipse/org.argeo.cms.e4/pom.xml
new file mode 100644 (file)
index 0000000..e318931
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>eclipse</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.cms.e4</artifactId>
+       <name>CMS E4</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.ui</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+
+               <!-- UI -->
+               <dependency>
+                       <groupId>org.argeo.commons.rap</groupId>
+                       <artifactId>org.argeo.swt.specific.rap</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp</groupId>
+                       <artifactId>argeo-tp-rap-e4</artifactId>
+                       <version>${version.argeo-tp}</version>
+                       <type>pom</type>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..21abf58
--- /dev/null
@@ -0,0 +1,77 @@
+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
new file mode 100644 (file)
index 0000000..c42a02a
--- /dev/null
@@ -0,0 +1,33 @@
+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
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/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
new file mode 100644 (file)
index 0000000..3d57e16
--- /dev/null
@@ -0,0 +1,104 @@
+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
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/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
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/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
new file mode 100644 (file)
index 0000000..cb9f9b9
--- /dev/null
@@ -0,0 +1,50 @@
+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
new file mode 100644 (file)
index 0000000..b481dd4
--- /dev/null
@@ -0,0 +1,2 @@
+/** Files browser perspective. */
+package org.argeo.cms.e4.files;
\ No newline at end of file
diff --git a/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
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/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
new file mode 100644 (file)
index 0000000..0ecd0a1
--- /dev/null
@@ -0,0 +1,137 @@
+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
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/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
new file mode 100644 (file)
index 0000000..a365f3d
--- /dev/null
@@ -0,0 +1,28 @@
+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
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/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
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/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
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/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
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/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
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/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
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/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
new file mode 100644 (file)
index 0000000..e17f17b
--- /dev/null
@@ -0,0 +1,141 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.jcr.PropertyLabelProvider;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.layout.TreeColumnLayout;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.IBaseLabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+
+/**
+ * Generic editor property page. Lists all properties of current node as a
+ * complex tree. TODO: enable editing
+ */
+public class GenericPropertyPage {
+
+       // Main business Objects
+       private Node currentNode;
+
+       public GenericPropertyPage(Node currentNode) {
+               this.currentNode = currentNode;
+       }
+
+       protected void createFormContent(Composite parent) {
+               Composite innerBox = new Composite(parent, SWT.NONE);
+               // Composite innerBox = new Composite(body, SWT.NO_FOCUS);
+               FillLayout layout = new FillLayout();
+               layout.marginHeight = 5;
+               layout.marginWidth = 5;
+               innerBox.setLayout(layout);
+               createComplexTree(innerBox);
+               // TODO TreeColumnLayout triggers a scroll issue with the form:
+               // The inside body is always to big and a scroll bar is shown
+               // Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
+               // createComplexTree(tableCmp);
+       }
+
+       private TreeViewer createComplexTree(Composite parent) {
+               int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION;
+               Tree tree = new Tree(parent, style);
+               TreeColumnLayout tableColumnLayout = new TreeColumnLayout();
+
+               createColumn(tree, tableColumnLayout, "Property", SWT.LEFT, 200, 30);
+               createColumn(tree, tableColumnLayout, "Value(s)", SWT.LEFT, 300, 60);
+               createColumn(tree, tableColumnLayout, "Type", SWT.LEFT, 75, 10);
+               createColumn(tree, tableColumnLayout, "Attributes", SWT.LEFT, 75, 0);
+               // Do not apply the treeColumnLayout it does not work yet
+               // parent.setLayout(tableColumnLayout);
+
+               tree.setLinesVisible(true);
+               tree.setHeaderVisible(true);
+
+               TreeViewer treeViewer = new TreeViewer(tree);
+               treeViewer.setContentProvider(new TreeContentProvider());
+               treeViewer.setLabelProvider((IBaseLabelProvider) new PropertyLabelProvider());
+               treeViewer.setInput(currentNode);
+               treeViewer.expandAll();
+               return treeViewer;
+       }
+
+       private static TreeColumn createColumn(Tree parent, TreeColumnLayout tableColumnLayout, String name, int style,
+                       int width, int weight) {
+               TreeColumn column = new TreeColumn(parent, style);
+               column.setText(name);
+               column.setWidth(width);
+               column.setMoveable(true);
+               column.setResizable(true);
+               tableColumnLayout.setColumnData(column, new ColumnWeightData(weight, width, true));
+               return column;
+       }
+
+       private class TreeContentProvider implements ITreeContentProvider {
+               private static final long serialVersionUID = -6162736530019406214L;
+
+               public Object[] getElements(Object parent) {
+                       Object[] props = null;
+                       try {
+
+                               if (parent instanceof Node) {
+                                       Node node = (Node) parent;
+                                       PropertyIterator pi;
+                                       pi = node.getProperties();
+                                       List<Property> propList = new ArrayList<Property>();
+                                       while (pi.hasNext()) {
+                                               propList.add(pi.nextProperty());
+                                       }
+                                       props = propList.toArray();
+                               }
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Unexpected exception while listing node properties", e);
+                       }
+                       return props;
+               }
+
+               public Object getParent(Object child) {
+                       return null;
+               }
+
+               public Object[] getChildren(Object parent) {
+                       if (parent instanceof Property) {
+                               Property prop = (Property) parent;
+                               try {
+                                       if (prop.isMultiple())
+                                               return prop.getValues();
+                               } catch (RepositoryException e) {
+                                       throw new EclipseUiException("Cannot get multi-prop values on " + prop, e);
+                               }
+                       }
+                       return null;
+               }
+
+               public boolean hasChildren(Object parent) {
+                       try {
+                               return (parent instanceof Property && ((Property) parent).isMultiple());
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Cannot check if property is multiple for " + parent, e);
+                       }
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+
+               public void dispose() {
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..98e8093
--- /dev/null
@@ -0,0 +1,349 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..ad6a547
--- /dev/null
@@ -0,0 +1,36 @@
+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
new file mode 100644 (file)
index 0000000..ae2b325
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.e4.jcr;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.jcr.Node;
+
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+
+public class JcrNodeEditor {
+       final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor";
+
+       @PostConstruct
+       public void createUi(Composite parent, MPart part, ESelectionService selectionService) {
+               parent.setLayout(new FillLayout());
+               List<?> selection = (List<?>) selectionService.getSelection();
+               Node node = ((SingleJcrNodeElem) selection.get(0)).getNode();
+               GenericPropertyPage propertyPage = new GenericPropertyPage(node);
+               propertyPage.createFormContent(parent);
+       }
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..17d8d2a
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.e4.jcr;
+
+import javax.annotation.PostConstruct;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+public class SimplePart {
+
+       @PostConstruct
+       void init(Composite parent) {
+               parent.setLayout(new GridLayout());
+               Label label = new Label(parent, SWT.NONE);
+               label.setText("Hello e4 World");
+       }
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..8f5bc36
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..dc47f6e
--- /dev/null
@@ -0,0 +1,210 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.net.URI;
+import java.util.Hashtable;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..1974e4d
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..4ae072c
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.JcrBrowserUtils;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..97674ab
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.cms.e4.jcr.handlers;
+
+import java.util.List;
+
+import javax.inject.Named;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.cms.e4.jcr.JcrBrowserView;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.dialogs.SingleValue;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.EPartService;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+
+/**
+ * Canonically call JCR Session#move(String, String) on the first element
+ * returned by HandlerUtil#getActiveWorkbenchWindow()
+ * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}.
+ * The user must then fill a new name in and confirm
+ */
+public class RenameNode {
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService,
+                       ESelectionService selectionService) {
+               List<?> selection = (List<?>) selectionService.getSelection();
+               if (selection == null || selection.size() != 1)
+                       return;
+               JcrBrowserView view = (JcrBrowserView) part.getObject();
+
+               Object element = selection.get(0);
+               if (element instanceof SingleJcrNodeElem) {
+                       SingleJcrNodeElem sjn = (SingleJcrNodeElem) element;
+                       Node node = sjn.getNode();
+                       Session session = null;
+                       String newName = null;
+                       String oldPath = null;
+                       try {
+                               newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]");
+                               // TODO sanity check and user feedback
+                               newName = JcrUtils.replaceInvalidChars(newName);
+                               oldPath = node.getPath();
+                               session = node.getSession();
+                               session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName);
+                               session.save();
+
+                               // Manually refresh the browser view. Must be enhanced
+                               view.refresh(sjn);
+                       } catch (RepositoryException e) {
+                               throw new EclipseUiException("Unable to rename " + node + " to " + newName, e);
+                       }
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..4e075e2
--- /dev/null
@@ -0,0 +1,2 @@
+/** JCR browser handlers. */
+package org.argeo.cms.e4.jcr.handlers;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..3e92fb0
--- /dev/null
@@ -0,0 +1,2 @@
+/** JCR browser perspective. */
+package org.argeo.cms.e4.jcr;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..4fd1d68
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.util.Collection;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+abstract class AbstractOsgiComposite extends Composite {
+       private static final long serialVersionUID = -4097415973477517137L;
+       protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+       protected final CmsLog log = CmsLog.getLog(getClass());
+
+       public AbstractOsgiComposite(Composite parent, int style) {
+               super(parent, style);
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               setLayout(CmsSwtUtils.noSpaceGridLayout());
+               setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               initUi(style);
+       }
+
+       protected abstract void initUi(int style);
+
+       protected <T> T getService(Class<? extends T> clazz) {
+               return bc.getService(bc.getServiceReference(clazz));
+       }
+
+       protected <T> Collection<ServiceReference<T>> getServiceReferences(Class<T> clazz, String filter) {
+               try {
+                       return bc.getServiceReferences(clazz, filter);
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Filter " + filter + " is invalid", e);
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..260a114
--- /dev/null
@@ -0,0 +1,576 @@
+package org.argeo.cms.e4.maintenance;
+
+import static org.eclipse.swt.SWT.RIGHT;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.LinkedHashMap;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.argeo.api.cms.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
new file mode 100644 (file)
index 0000000..97f3e67
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.e4.maintenance;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.useradmin.UserAdmin;
+
+class ConnectivityDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public ConnectivityDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               StringBuffer text = new StringBuffer();
+               text.append("<span style='font-variant: small-caps;'>Provided Servers</span><br/>");
+
+               ServiceReference<HttpService> userAdminRef = bc.getServiceReference(HttpService.class);
+               if (userAdminRef != null) {
+                       // FIXME use constants
+                       Object httpPort = userAdminRef.getProperty("http.port");
+                       Object httpsPort = userAdminRef.getProperty("https.port");
+                       if (httpPort != null)
+                               text.append("<b>http</b> ").append(httpPort).append("<br/>");
+                       if (httpsPort != null)
+                               text.append("<b>https</b> ").append(httpsPort).append("<br/>");
+
+               }
+
+               text.append("<br/>");
+               text.append("<span style='font-variant: small-caps;'>Referenced Servers</span><br/>");
+
+               Label label = new Label(this, SWT.NONE);
+               label.setData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               CmsSwtUtils.markup(label);
+               label.setText(text.toString());
+       }
+
+       protected boolean isDeployed() {
+               return bc.getServiceReference(UserAdmin.class) != null;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..ef95bde
--- /dev/null
@@ -0,0 +1,139 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileStore;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.ServiceReference;
+
+class DataDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public DataDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               if (isDeployed()) {
+                       initCurrentUi(this);
+               } else {
+                       initNewUi(this);
+               }
+       }
+
+       private void initNewUi(Composite parent) {
+//             try {
+//                     ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
+//                     Configuration[] confs = confAdmin.listConfigurations(
+//                                     "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")");
+//                     if (confs == null || confs.length == 0) {
+//                             Group buttonGroup = new Group(parent, SWT.NONE);
+//                             buttonGroup.setText("Repository Type");
+//                             buttonGroup.setLayout(new GridLayout(2, true));
+//                             buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL));
+//
+//                             SelectionListener selectionListener = new SelectionAdapter() {
+//                                     private static final long serialVersionUID = 6247064348421088092L;
+//
+//                                     public void widgetSelected(SelectionEvent event) {
+//                                             Button radio = (Button) event.widget;
+//                                             if (!radio.getSelection())
+//                                                     return;
+//                                             log.debug(event);
+//                                             JackrabbitType nodeType = (JackrabbitType) radio.getData();
+//                                             if (log.isDebugEnabled())
+//                                                     log.debug(" selected = " + nodeType.name());
+//                                     };
+//                             };
+//
+//                             for (JackrabbitType nodeType : JackrabbitType.values()) {
+//                                     Button radio = new Button(buttonGroup, SWT.RADIO);
+//                                     radio.setText(nodeType.name());
+//                                     radio.setData(nodeType);
+//                                     if (nodeType.equals(JackrabbitType.localfs))
+//                                             radio.setSelection(true);
+//                                     radio.addSelectionListener(selectionListener);
+//                             }
+//
+//                     } else if (confs.length == 1) {
+//
+//                     } else {
+//                             throw new CmsException("Multiple repos not yet supported");
+//                     }
+//             } catch (Exception e) {
+//                     throw new CmsException("Cannot initialize UI", e);
+//             }
+
+       }
+
+       private void initCurrentUi(Composite parent) {
+               parent.setLayout(new GridLayout());
+               Collection<ServiceReference<RepositoryContext>> contexts = getServiceReferences(RepositoryContext.class,
+                               "(" + CmsConstants.CN + "=*)");
+               StringBuffer text = new StringBuffer();
+               text.append("<span style='font-variant: small-caps;'>Jackrabbit Repositories</span><br/>");
+               for (ServiceReference<RepositoryContext> sr : contexts) {
+                       RepositoryContext repositoryContext = bc.getService(sr);
+                       String alias = sr.getProperty(CmsConstants.CN).toString();
+                       String rootNodeId = repositoryContext.getRootNodeId().toString();
+                       RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig();
+                       Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath();
+                       // TODO check data store
+
+                       text.append("<b>" + alias + "</b><br/>");
+                       text.append("rootNodeId: " + rootNodeId + "<br/>");
+                       try {
+                               FileStore fileStore = Files.getFileStore(repoHomePath);
+                               text.append("partition: " + fileStore.toString() + "<br/>");
+                               text.append(
+                                               percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)<br/>");
+                       } catch (IOException e) {
+                               log.error("Cannot check fileStore for " + repoHomePath, e);
+                       }
+               }
+               Label label = new Label(parent, SWT.NONE);
+               label.setData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               CmsSwtUtils.markup(label);
+               label.setText("<span style=''>" + text.toString() + "</span>");
+       }
+
+       private String humanReadable(long bytes) {
+               long mb = bytes / (1024 * 1024);
+               return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB";
+       }
+
+       private String percentUsed(FileStore fs) throws IOException {
+               long used = fs.getTotalSpace() - fs.getUnallocatedSpace();
+               long percent = used * 100 / fs.getTotalSpace();
+               if (log.isTraceEnabled()) {
+                       // output identical to `df -B 1`)
+                       log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace());
+               }
+               String span;
+               if (percent < 80)
+                       span = "<span style='color:green;font-weight:bold'>";
+               else if (percent < 95)
+                       span = "<span style='color:orange;font-weight:bold'>";
+               else
+                       span = "<span style='color:red;font-weight:bold'>";
+               return span + percent + "%</span>";
+       }
+
+       protected boolean isDeployed() {
+               return bc.getServiceReference(RepositoryContext.class) != null;
+       }
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..e713f53
--- /dev/null
@@ -0,0 +1,96 @@
+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
new file mode 100644 (file)
index 0000000..fa5d3da
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Text;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+class LogDeploymentUi extends AbstractOsgiComposite implements LogListener {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm");
+
+       private Display display;
+       private Text logDisplay;
+
+       public LogDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               LogReaderService logReader = getService(LogReaderService.class);
+               // FIXME use server push
+               // logReader.addLogListener(this);
+               this.display = getDisplay();
+               this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY);
+               logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               CmsSwtUtils.markup(logDisplay);
+               Enumeration<LogEntry> logEntries = (Enumeration<LogEntry>) logReader.getLog();
+               while (logEntries.hasMoreElements())
+                       logDisplay.append(printEntry(logEntries.nextElement()));
+       }
+
+       private String printEntry(LogEntry entry) {
+               StringBuilder sb = new StringBuilder();
+               GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault());
+               calendar.setTimeInMillis(entry.getTime());
+               sb.append(dateFormat.format(calendar.getTime())).append(' ');
+               sb.append(entry.getMessage());
+               sb.append('\n');
+               return sb.toString();
+       }
+
+       @Override
+       public void logged(LogEntry entry) {
+               if (display.isDisposed())
+                       return;
+               display.asyncExec(() -> {
+                       if (logDisplay.isDisposed())
+                               return;
+                       logDisplay.append(printEntry(entry));
+               });
+               display.wake();
+       }
+
+       // @Override
+       // public void dispose() {
+       // super.dispose();
+       // getService(LogReaderService.class).removeLogListener(this);
+       // }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..df1be51
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.e4.maintenance;
+
+/** Specific styles used by the various maintenance pages . */
+public interface MaintenanceStyles {
+       // General
+       public final static String PREFIX = "maintenance_";
+
+       // Browser
+       public final static String BROWSER_COLUMN = "browser_column";
+       }
diff --git a/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
new file mode 100644 (file)
index 0000000..cb38ce8
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.cms.e4.maintenance;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+public class NonAdminPage implements CmsUiProvider{
+
+       @Override
+       public Control createUi(Composite parent, Node context)
+                       throws RepositoryException {
+               Composite body = new Composite(parent, SWT.NO_FOCUS);
+               body.setLayoutData(CmsSwtUtils.fillAll());
+               body.setLayout(new GridLayout());
+               Label label = new Label(body, SWT.NONE);
+               label.setText("You should be an admin to perform maintenance operations. "
+                               + "Are you sure you are logged in?");
+               label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               return null;
+       }
+       
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..3492c54
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.cms.e4.maintenance;
+
+import java.net.URI;
+
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+class SecurityDeploymentUi extends AbstractOsgiComposite {
+       private static final long serialVersionUID = 590221539553514693L;
+
+       public SecurityDeploymentUi(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       protected void initUi(int style) {
+               if (isDeployed()) {
+                       initCurrentUi(this);
+               } else {
+                       initNewUi(this);
+               }
+       }
+
+       private void initNewUi(Composite parent) {
+               new Label(parent, SWT.NONE).setText("Security is not configured");
+       }
+
+       private void initCurrentUi(Composite parent) {
+               ServiceReference<UserAdmin> userAdminRef = bc.getServiceReference(UserAdmin.class);
+               UserAdmin userAdmin = bc.getService(userAdminRef);
+               StringBuffer text = new StringBuffer();
+               text.append("<span style='font-variant: small-caps;'>Domains</span><br/>");
+               domains: for (String key : userAdminRef.getPropertyKeys()) {
+                       if (!key.startsWith("/"))
+                               continue domains;
+                       URI uri;
+                       try {
+                               uri = new URI(key);
+                       } catch (Exception e) {
+                               // ignore non URI keys
+                               continue domains;
+                       }
+
+                       String rootDn = uri.getPath().substring(1, uri.getPath().length());
+                       // FIXME make reading query options more robust, using utils
+                       boolean readOnly = uri.getQuery().equals("readOnly=true");
+                       if (readOnly)
+                               text.append("<span style='font-weight:bold;font-style: italic'>");
+                       else
+                               text.append("<span style='font-weight:bold'>");
+
+                       text.append(rootDn);
+                       text.append("</span><br/>");
+                       try {
+                               Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")");
+                               long userCount = 0;
+                               long groupCount = 0;
+                               for (Role role : roles) {
+                                       if (role.getType() == Role.USER)
+                                               userCount++;
+                                       else
+                                               groupCount++;
+                               }
+                               text.append(" " + userCount + " users, " + groupCount +" groups.<br/>");
+                       } catch (InvalidSyntaxException e) {
+                               log.error("Invalid syntax", e);
+                       }
+               }
+               Label label = new Label(parent, SWT.NONE);
+               label.setData(new GridData(SWT.FILL, SWT.FILL, false, false));
+               CmsSwtUtils.markup(label);
+               label.setText(text.toString());
+       }
+
+       protected boolean isDeployed() {
+               return bc.getServiceReference(UserAdmin.class) != null;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..e4d2ad4
--- /dev/null
@@ -0,0 +1,2 @@
+/** Maintenance perspective. */
+package org.argeo.cms.e4.maintenance;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..962ad38
--- /dev/null
@@ -0,0 +1,46 @@
+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
new file mode 100644 (file)
index 0000000..c639255
--- /dev/null
@@ -0,0 +1,114 @@
+//package org.argeo.eclipse.ui.workbench.osgi;
+//public class BundlesView {}
+
+package org.argeo.cms.e4.monitoring;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.eclipse.ui.ColumnViewerComparator;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.e4.ui.di.Focus;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * Overview of the bundles as a table. Equivalent to Equinox 'ss' console
+ * command.
+ */
+public class BundlesView {
+       private final static BundleContext bc = FrameworkUtil.getBundle(BundlesView.class).getBundleContext();
+       private TableViewer viewer;
+
+       @PostConstruct
+       public void createPartControl(Composite parent) {
+               viewer = new TableViewer(parent);
+               viewer.setContentProvider(new BundleContentProvider());
+               viewer.getTable().setHeaderVisible(true);
+
+               EclipseUiSpecificUtils.enableToolTipSupport(viewer);
+
+               // ID
+               TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(30);
+               column.getColumn().setText("ID");
+               column.getColumn().setAlignment(SWT.RIGHT);
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -3122136344359358605L;
+
+                       public String getText(Object element) {
+                               return Long.toString(((Bundle) element).getBundleId());
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // State
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(18);
+               column.getColumn().setText("State");
+               column.setLabelProvider(new StateLabelProvider());
+               new ColumnViewerComparator(column);
+
+               // Symbolic name
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(250);
+               column.getColumn().setText("Symbolic Name");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = -4280840684440451080L;
+
+                       public String getText(Object element) {
+                               return ((Bundle) element).getSymbolicName();
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               // Version
+               column = new TableViewerColumn(viewer, SWT.NONE);
+               column.getColumn().setWidth(250);
+               column.getColumn().setText("Version");
+               column.setLabelProvider(new ColumnLabelProvider() {
+                       private static final long serialVersionUID = 6871926308708629989L;
+
+                       public String getText(Object element) {
+                               Bundle bundle = (org.osgi.framework.Bundle) element;
+                               return bundle.getVersion().toString();
+                       }
+               });
+               new ColumnViewerComparator(column);
+
+               viewer.setInput(bc);
+
+       }
+
+       @Focus
+       public void setFocus() {
+               if (viewer != null)
+                       viewer.getControl().setFocus();
+       }
+
+       /** Content provider managing the array of bundles */
+       private static class BundleContentProvider implements IStructuredContentProvider {
+               private static final long serialVersionUID = -8533792785725875977L;
+
+               public Object[] getElements(Object inputElement) {
+                       if (inputElement instanceof BundleContext) {
+                               BundleContext bc = (BundleContext) inputElement;
+                               return bc.getBundles();
+                       }
+                       return null;
+               }
+
+               public void dispose() {
+               }
+
+               public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..8a36050
--- /dev/null
@@ -0,0 +1,173 @@
+//package org.argeo.eclipse.ui.workbench.osgi;
+//public class BundlesView {}
+
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import 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
new file mode 100644 (file)
index 0000000..f0d8c29
--- /dev/null
@@ -0,0 +1,91 @@
+package org.argeo.cms.e4.monitoring;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..759b3e9
--- /dev/null
@@ -0,0 +1,163 @@
+package org.argeo.cms.e4.monitoring;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.argeo.cms.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
new file mode 100644 (file)
index 0000000..7217fe6
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.cms.e4.monitoring;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons. */
+public class OsgiExplorerImages extends CmsImages {
+       public final static Image INSTALLED = createIcon("installed.gif");
+       public final static Image RESOLVED = createIcon("resolved.gif");
+       public final static Image STARTING = createIcon("starting.gif");
+       public final static Image ACTIVE = createIcon("active.gif");
+       public final static Image SERVICE_PUBLISHED = createIcon("service_published.gif");
+       public final static Image SERVICE_REFERENCED = createIcon("service_referenced.gif");
+       public final static Image CONFIGURATION = createIcon("node.gif");
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..d9c45fe
--- /dev/null
@@ -0,0 +1,46 @@
+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
new file mode 100644 (file)
index 0000000..5cb5b65
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.e4.monitoring;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+/** Label provider showing the sate of bundles */
+class StateLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = -7885583135316000733L;
+
+       @Override
+       public Image getImage(Object element) {
+               int state;
+               if (element instanceof Bundle)
+                       state = ((Bundle) element).getState();
+               else if (element instanceof BundleNode)
+                       state = ((BundleNode) element).getBundle().getState();
+               else if (element instanceof ServiceReferenceNode)
+                       if (((ServiceReferenceNode) element).isPublished())
+                               return OsgiExplorerImages.SERVICE_PUBLISHED;
+                       else
+                               return OsgiExplorerImages.SERVICE_REFERENCED;
+               else
+                       return null;
+
+               switch (state) {
+               case Bundle.UNINSTALLED:
+                       return OsgiExplorerImages.INSTALLED;
+               case Bundle.INSTALLED:
+                       return OsgiExplorerImages.INSTALLED;
+               case Bundle.RESOLVED:
+                       return OsgiExplorerImages.RESOLVED;
+               case Bundle.STARTING:
+                       return OsgiExplorerImages.STARTING;
+               case Bundle.STOPPING:
+                       return OsgiExplorerImages.STARTING;
+               case Bundle.ACTIVE:
+                       return OsgiExplorerImages.ACTIVE;
+               default:
+                       return null;
+               }
+       }
+
+       @Override
+       public String getText(Object element) {
+               return null;
+       }
+
+       @Override
+       public String getToolTipText(Object element) {
+               Bundle bundle = (Bundle) element;
+               Integer state = bundle.getState();
+               switch (state) {
+               case Bundle.UNINSTALLED:
+                       return "UNINSTALLED";
+               case Bundle.INSTALLED:
+                       return "INSTALLED";
+               case Bundle.RESOLVED:
+                       return "RESOLVED";
+               case Bundle.STARTING:
+                       String activationPolicy = bundle.getHeaders()
+                                       .get(Constants.BUNDLE_ACTIVATIONPOLICY).toString();
+
+                       // .get("Bundle-ActivationPolicy").toString();
+                       // FIXME constant triggers the compilation failure
+                       if (activationPolicy != null
+                                       && activationPolicy.equals(Constants.ACTIVATION_LAZY))
+                               // && activationPolicy.equals("lazy"))
+                               // FIXME constant triggers the compilation failure
+                               // && activationPolicy.equals(Constants.ACTIVATION_LAZY))
+                               return "<<LAZY>>";
+                       return "STARTING";
+               case Bundle.STOPPING:
+                       return "STOPPING";
+               case Bundle.ACTIVE:
+                       return "ACTIVE";
+               default:
+                       return null;
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..873bf31
--- /dev/null
@@ -0,0 +1,2 @@
+/** Monitoring perspective. */
+package org.argeo.cms.e4.monitoring;
\ No newline at end of file
diff --git a/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
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/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
new file mode 100644 (file)
index 0000000..5a805d1
--- /dev/null
@@ -0,0 +1,46 @@
+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
new file mode 100644 (file)
index 0000000..137f762
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..07df312
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.e4.users;
+
+/** Centralize the declaration of Workbench specific CSS Styles */
+interface CmsWorkbenchStyles {
+
+       // Specific People layouting
+       String WORKBENCH_FORM_TEXT = "workbench_form_text";
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..a011c5f
--- /dev/null
@@ -0,0 +1,566 @@
+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
new file mode 100644 (file)
index 0000000..ddad34f
--- /dev/null
@@ -0,0 +1,251 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.api.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
new file mode 100644 (file)
index 0000000..7bbe3c7
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.cms.e4.users;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons that must be declared programmatically . */
+public class SecurityAdminImages extends CmsImages {
+       private final static String PREFIX = "icons/";
+
+       public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png");
+       public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png");
+
+       public final static Image ICON_USER = ICON_USER_DESC.createImage();
+       public final static Image ICON_GROUP = createImg(PREFIX + "group.png");
+       public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png");
+       public final static Image ICON_ROLE = createImg(PREFIX + "role.gif");
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..f856492
--- /dev/null
@@ -0,0 +1,34 @@
+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
new file mode 100644 (file)
index 0000000..eb64aba
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.cms.e4.users;
+
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.UserAdminEvent;
+import org.osgi.service.useradmin.UserAdminListener;
+
+/** Convenience class to insure the call to refresh is done in the UI thread */
+public abstract class UiUserAdminListener implements UserAdminListener {
+
+       private final Display display;
+
+       public UiUserAdminListener(Display display) {
+               this.display = display;
+       }
+
+       @Override
+       public void roleChanged(final UserAdminEvent event) {
+               display.asyncExec(new Runnable() {
+                       @Override
+                       public void run() {
+                               roleChangedToUiThread(event);
+                       }
+               });
+       }
+
+       public abstract void roleChangedToUiThread(UserAdminEvent event);
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..16aa783
--- /dev/null
@@ -0,0 +1,153 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..a38d171
--- /dev/null
@@ -0,0 +1,606 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.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
new file mode 100644 (file)
index 0000000..66f4420
--- /dev/null
@@ -0,0 +1,535 @@
+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
new file mode 100644 (file)
index 0000000..c6d024e
--- /dev/null
@@ -0,0 +1,39 @@
+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
new file mode 100644 (file)
index 0000000..877a925
--- /dev/null
@@ -0,0 +1,182 @@
+package org.argeo.cms.e4.users;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+
+import org.argeo.api.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
new file mode 100644 (file)
index 0000000..742bc3f
--- /dev/null
@@ -0,0 +1,95 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.GroupsView;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/** Delete the selected groups */
+public class DeleteGroups {
+       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID +
+       // ".deleteGroups";
+
+       /* DEPENDENCY INJECTION */
+       @Inject
+       private UserAdminWrapper userAdminWrapper;
+
+       @Inject
+       ESelectionService selectionService;
+
+       @SuppressWarnings("unchecked")
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               // ISelection selection = null;// HandlerUtil.getCurrentSelection(event);
+               // if (selection.isEmpty())
+               // return null;
+               //
+               // List<Group> groups = new ArrayList<Group>();
+               // Iterator<Group> it = ((IStructuredSelection) selection).iterator();
+
+               List<Group> selection = (List<Group>) selectionService.getSelection();
+               if (selection == null)
+                       return;
+
+               StringBuilder builder = new StringBuilder();
+               for (Group group : selection) {
+                       Group currGroup = group;
+                       String groupName = UserAdminUtils.getUserLocalId(currGroup.getName());
+                       // TODO add checks
+                       builder.append(groupName).append("; ");
+                       // groups.add(currGroup);
+               }
+
+               if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Groups", "Are you sure that you "
+                               + "want to delete these groups?\n" + builder.substring(0, builder.length() - 2)))
+                       return;
+
+               userAdminWrapper.beginTransactionIfNeeded();
+               UserAdmin userAdmin = userAdminWrapper.getUserAdmin();
+               // IWorkbenchPage iwp =
+               // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
+               for (Group group : selection) {
+                       String groupName = group.getName();
+                       // TODO find a way to close the editor cleanly if opened. Cannot be
+                       // done through the UserAdminListeners, it causes a
+                       // java.util.ConcurrentModificationException because disposing the
+                       // editor unregisters and disposes the listener
+                       // IEditorPart part = iwp.findEditor(new UserEditorInput(groupName));
+                       // if (part != null)
+                       // iwp.closeEditor(part, false);
+                       userAdmin.removeRole(groupName);
+               }
+               userAdminWrapper.commitOrNotifyTransactionStateChange();
+
+               // Update the view
+               for (Group group : selection) {
+                       userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, group));
+               }
+
+               // return null;
+       }
+
+       @CanExecute
+       public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               return part.getObject() instanceof GroupsView && selectionService.getSelection() != null;
+       }
+
+       /* DEPENDENCY INJECTION */
+       // public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) {
+       // this.userAdminWrapper = userAdminWrapper;
+       // }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..d1afd22
--- /dev/null
@@ -0,0 +1,88 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.argeo.cms.e4.users.UserAdminWrapper;
+import org.argeo.cms.e4.users.UsersView;
+import org.eclipse.e4.core.di.annotations.CanExecute;
+import org.eclipse.e4.core.di.annotations.Execute;
+import org.eclipse.e4.ui.model.application.ui.basic.MPart;
+import org.eclipse.e4.ui.services.IServiceConstants;
+import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+import org.osgi.service.useradmin.UserAdminEvent;
+
+/** Delete the selected users */
+public class DeleteUsers {
+       // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".deleteUsers";
+
+       /* DEPENDENCY INJECTION */
+       @Inject
+       private UserAdminWrapper userAdminWrapper;
+
+       @SuppressWarnings("unchecked")
+       @Execute
+       public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               // ISelection selection = null;// HandlerUtil.getCurrentSelection(event);
+               // if (selection.isEmpty())
+               // return null;
+               List<User> selection = (List<User>) selectionService.getSelection();
+               if (selection == null)
+                       return;
+
+//             Iterator<User> it = ((IStructuredSelection) selection).iterator();
+//             List<User> users = new ArrayList<User>();
+               StringBuilder builder = new StringBuilder();
+
+               for(User user:selection) {
+                       User currUser = user;
+//                     User currUser = it.next();
+                       String userName = UserAdminUtils.getUserLocalId(currUser.getName());
+                       if (UserAdminUtils.isCurrentUser(currUser)) {
+                               MessageDialog.openError(Display.getCurrent().getActiveShell(), "Deletion forbidden",
+                                               "You cannot delete your own user this way.");
+                               return;
+                       }
+                       builder.append(userName).append("; ");
+//                     users.add(currUser);
+               }
+
+               if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Users",
+                               "Are you sure that you want to delete these users?\n" + builder.substring(0, builder.length() - 2)))
+                       return;
+
+               userAdminWrapper.beginTransactionIfNeeded();
+               UserAdmin userAdmin = userAdminWrapper.getUserAdmin();
+               // IWorkbenchPage iwp =
+               // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
+
+               for (User user : selection) {
+                       String userName = user.getName();
+                       // TODO find a way to close the editor cleanly if opened. Cannot be
+                       // done through the UserAdminListeners, it causes a
+                       // java.util.ConcurrentModificationException because disposing the
+                       // editor unregisters and disposes the listener
+                       // IEditorPart part = iwp.findEditor(new UserEditorInput(userName));
+                       // if (part != null)
+                       // iwp.closeEditor(part, false);
+                       userAdmin.removeRole(userName);
+               }
+               userAdminWrapper.commitOrNotifyTransactionStateChange();
+
+               for (User user : selection) {
+                       userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user));
+               }
+       }
+
+       @CanExecute
+       public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) {
+               return part.getObject() instanceof UsersView && selectionService.getSelection() != null;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..d2ffa79
--- /dev/null
@@ -0,0 +1,212 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..07d82c7
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.e4.users.handlers;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..cf3db1d
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management handlers. */
+package org.argeo.cms.e4.users.handlers;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..c6f14b0
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management perspective. */
+package org.argeo.cms.e4.users;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..2d8db67
--- /dev/null
@@ -0,0 +1,21 @@
+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
new file mode 100644 (file)
index 0000000..e23729d
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.argeo.cms.auth.UserAdminUtils;
+import org.osgi.service.useradmin.User;
+
+/** The human friendly domain name for the corresponding user. */
+public class DomainNameLP extends UserAdminAbstractLP {
+       private static final long serialVersionUID = 5256703081044911941L;
+
+       @Override
+       public String getText(User user) {
+               return UserAdminUtils.getDomainName(user);
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..52d3b85
--- /dev/null
@@ -0,0 +1,15 @@
+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
new file mode 100644 (file)
index 0000000..8c94093
--- /dev/null
@@ -0,0 +1,35 @@
+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
new file mode 100644 (file)
index 0000000..e33b153
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.cms.e4.users.providers;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.cms.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
new file mode 100644 (file)
index 0000000..56a2624
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.dnd.DragSourceEvent;
+import org.eclipse.swt.dnd.DragSourceListener;
+import org.osgi.service.useradmin.User;
+
+/** Default drag listener to modify group and users via the UI */
+public class UserDragListener implements DragSourceListener {
+       private static final long serialVersionUID = -2074337775033781454L;
+       private final Viewer viewer;
+
+       public UserDragListener(Viewer viewer) {
+               this.viewer = viewer;
+       }
+
+       public void dragStart(DragSourceEvent event) {
+               // TODO implement finer checks
+               IStructuredSelection selection = (IStructuredSelection) viewer
+                               .getSelection();
+               if (selection.isEmpty() || selection.size() > 1)
+                       event.doit = false;
+               else
+                       event.doit = true;
+       }
+
+       public void dragSetData(DragSourceEvent event) {
+               // TODO Support multiple selection
+               Object obj = ((IStructuredSelection) viewer.getSelection())
+                               .getFirstElement();
+               if (obj != null) {
+                       User user = (User) obj;
+                       event.data = user.getName();
+               }
+       }
+
+       public void dragFinished(DragSourceEvent event) {
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..154b047
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.cms.e4.users.providers;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
+
+import org.argeo.api.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
new file mode 100644 (file)
index 0000000..3cd00eb
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.e4.users.providers;
+
+import org.osgi.service.useradmin.User;
+
+/** Simply declare a label provider that returns the username of a user */
+public class UserNameLP extends UserAdminAbstractLP {
+       private static final long serialVersionUID = 6550449442061090388L;
+
+       @Override
+       public String getText(User user) {
+               return user.getName();
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..33bef8d
--- /dev/null
@@ -0,0 +1,2 @@
+/** Users management content providers. */
+package org.argeo.cms.e4.users.providers;
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.servlet/.classpath b/eclipse/org.argeo.cms.servlet/.classpath
new file mode 100644 (file)
index 0000000..e801ebf
--- /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-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
new file mode 100644 (file)
index 0000000..d39f974
--- /dev/null
@@ -0,0 +1,33 @@
+<?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
new file mode 100644 (file)
index 0000000..c007351
--- /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="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
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/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/eclipse/org.argeo.cms.servlet/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/eclipse/org.argeo.cms.servlet/bnd.bnd b/eclipse/org.argeo.cms.servlet/bnd.bnd
new file mode 100644 (file)
index 0000000..b539a49
--- /dev/null
@@ -0,0 +1,11 @@
+Import-Package:\
+org.osgi.service.http;version=0.0.0,\
+org.osgi.service.http.whiteboard;version=0.0.0,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.argeo.cms.osgi,\
+*
+
+Service-Component:\
+OSGI-INF/jettyServiceFactory.xml,\
+OSGI-INF/pkgServletContext.xml,\
+OSGI-INF/pkgServlet.xml
diff --git a/eclipse/org.argeo.cms.servlet/build.properties b/eclipse/org.argeo.cms.servlet/build.properties
new file mode 100644 (file)
index 0000000..ee94f53
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/jettyServiceFactory.xml
+source.. = src/
diff --git a/eclipse/org.argeo.cms.servlet/pom.xml b/eclipse/org.argeo.cms.servlet/pom.xml
new file mode 100644 (file)
index 0000000..2cd38a1
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <version>2.3-SNAPSHOT</version>
+               <artifactId>eclipse</artifactId>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.cms.servlet</artifactId>
+       <packaging>jar</packaging>
+       <name>CMS Servlet</name>
+       <description>CMS components depending on the Servlet APIs</description>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..1ae6286
--- /dev/null
@@ -0,0 +1,98 @@
+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
new file mode 100644 (file)
index 0000000..3bea0b4
--- /dev/null
@@ -0,0 +1,39 @@
+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
new file mode 100644 (file)
index 0000000..95912e4
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.cms.servlet;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpRequest implements RemoteAuthRequest {
+       private final HttpServletRequest request;
+
+       public ServletHttpRequest(HttpServletRequest request) {
+               Objects.requireNonNull(request);
+               this.request = request;
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               return new ServletHttpSession(request.getSession(false));
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               return new ServletHttpSession(request.getSession(true));
+       }
+
+       @Override
+       public Locale getLocale() {
+               return request.getLocale();
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return request.getAttribute(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               request.setAttribute(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               return request.getHeader(key);
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               return request.getRemoteAddr();
+       }
+
+       @Override
+       public int getLocalPort() {
+               return request.getLocalPort();
+       }
+
+       @Override
+       public int getRemotePort() {
+               return request.getRemotePort();
+       }
+}
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
new file mode 100644 (file)
index 0000000..de47365
--- /dev/null
@@ -0,0 +1,22 @@
+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
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/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
new file mode 100644 (file)
index 0000000..70f2cc6
--- /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/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
new file mode 100644 (file)
index 0000000..c762b67
--- /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.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
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/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
new file mode 100644 (file)
index 0000000..05de32c
--- /dev/null
@@ -0,0 +1,79 @@
+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
new file mode 100644 (file)
index 0000000..e03d341
--- /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-11" />
+       <classpathentry kind="output" path="bin" />
+</classpath>
diff --git a/eclipse/org.argeo.cms.swt/.project b/eclipse/org.argeo.cms.swt/.project
new file mode 100644 (file)
index 0000000..082112e
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/eclipse/org.argeo.cms.swt/bnd.bnd b/eclipse/org.argeo.cms.swt/bnd.bnd
new file mode 100644 (file)
index 0000000..9d4eae6
--- /dev/null
@@ -0,0 +1,7 @@
+Import-Package: org.eclipse.swt,\
+                               org.eclipse.jface.window,\
+                               org.eclipse.core.commands.common,\
+                               *
+
+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
new file mode 100644 (file)
index 0000000..0e04387
--- /dev/null
@@ -0,0 +1,5 @@
+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
new file mode 100644 (file)
index 0000000..5c06bf0
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/add.png 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
new file mode 100644 (file)
index 0000000..81bfc95
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/close-all.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/delete.png b/eclipse/org.argeo.cms.swt/icons/actions/delete.png
new file mode 100644 (file)
index 0000000..9712723
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/delete.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/edit.png b/eclipse/org.argeo.cms.swt/icons/actions/edit.png
new file mode 100644 (file)
index 0000000..ad3db9f
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/edit.png 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
new file mode 100644 (file)
index 0000000..f48ed32
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/save-all.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save.png b/eclipse/org.argeo.cms.swt/icons/actions/save.png
new file mode 100644 (file)
index 0000000..1c58ada
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/save.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/active.gif b/eclipse/org.argeo.cms.swt/icons/active.gif
new file mode 100644 (file)
index 0000000..7d24707
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/active.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/add.gif b/eclipse/org.argeo.cms.swt/icons/add.gif
new file mode 100644 (file)
index 0000000..252d7eb
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/add.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/add.png b/eclipse/org.argeo.cms.swt/icons/add.png
new file mode 100644 (file)
index 0000000..c7edfec
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/add.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addFolder.gif b/eclipse/org.argeo.cms.swt/icons/addFolder.gif
new file mode 100644 (file)
index 0000000..d3f43d9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addFolder.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif
new file mode 100644 (file)
index 0000000..a6b251f
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addRepo.gif b/eclipse/org.argeo.cms.swt/icons/addRepo.gif
new file mode 100644 (file)
index 0000000..26d81c0
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addRepo.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png
new file mode 100644 (file)
index 0000000..bbee775
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/adminLog.gif b/eclipse/org.argeo.cms.swt/icons/adminLog.gif
new file mode 100644 (file)
index 0000000..6ef3bca
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/adminLog.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/batch.gif b/eclipse/org.argeo.cms.swt/icons/batch.gif
new file mode 100644 (file)
index 0000000..b8ca14a
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/batch.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/begin.gif b/eclipse/org.argeo.cms.swt/icons/begin.gif
new file mode 100755 (executable)
index 0000000..feb8e94
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/begin.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/binary.png b/eclipse/org.argeo.cms.swt/icons/binary.png
new file mode 100644 (file)
index 0000000..fdf4f82
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/binary.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/browser.gif b/eclipse/org.argeo.cms.swt/icons/browser.gif
new file mode 100644 (file)
index 0000000..6c7320c
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/browser.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/bundles.gif b/eclipse/org.argeo.cms.swt/icons/bundles.gif
new file mode 100644 (file)
index 0000000..e9a6bd9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/bundles.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/changePassword.gif b/eclipse/org.argeo.cms.swt/icons/changePassword.gif
new file mode 100644 (file)
index 0000000..274a850
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/changePassword.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/clear.gif b/eclipse/org.argeo.cms.swt/icons/clear.gif
new file mode 100644 (file)
index 0000000..6bc10f9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/clear.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/close-all.png b/eclipse/org.argeo.cms.swt/icons/close-all.png
new file mode 100644 (file)
index 0000000..85d4d42
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/close-all.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/commit.gif b/eclipse/org.argeo.cms.swt/icons/commit.gif
new file mode 100755 (executable)
index 0000000..876f3eb
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/commit.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/delete.png b/eclipse/org.argeo.cms.swt/icons/delete.png
new file mode 100644 (file)
index 0000000..676a39d
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/delete.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif
new file mode 100644 (file)
index 0000000..14eb1be
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/file.gif b/eclipse/org.argeo.cms.swt/icons/file.gif
new file mode 100644 (file)
index 0000000..ef30288
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/file.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/folder.gif b/eclipse/org.argeo.cms.swt/icons/folder.gif
new file mode 100644 (file)
index 0000000..42e027c
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/folder.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/getSize.gif b/eclipse/org.argeo.cms.swt/icons/getSize.gif
new file mode 100644 (file)
index 0000000..b05bf3e
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/getSize.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/group.png b/eclipse/org.argeo.cms.swt/icons/group.png
new file mode 100644 (file)
index 0000000..cc6683a
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/group.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/home.gif b/eclipse/org.argeo.cms.swt/icons/home.gif
new file mode 100644 (file)
index 0000000..fd0c669
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/home.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/home.png b/eclipse/org.argeo.cms.swt/icons/home.png
new file mode 100644 (file)
index 0000000..5eb0967
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/home.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/import_fs.png b/eclipse/org.argeo.cms.swt/icons/import_fs.png
new file mode 100644 (file)
index 0000000..d7c890c
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/import_fs.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/installed.gif b/eclipse/org.argeo.cms.swt/icons/installed.gif
new file mode 100644 (file)
index 0000000..2988716
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/installed.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/log.gif b/eclipse/org.argeo.cms.swt/icons/log.gif
new file mode 100644 (file)
index 0000000..e3ecc55
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/log.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/logout.png b/eclipse/org.argeo.cms.swt/icons/logout.png
new file mode 100644 (file)
index 0000000..f2952fa
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/logout.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/maintenance.gif b/eclipse/org.argeo.cms.swt/icons/maintenance.gif
new file mode 100644 (file)
index 0000000..e5690ec
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/maintenance.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/node.gif b/eclipse/org.argeo.cms.swt/icons/node.gif
new file mode 100644 (file)
index 0000000..364c0e7
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/node.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/nodes.gif b/eclipse/org.argeo.cms.swt/icons/nodes.gif
new file mode 100644 (file)
index 0000000..bba3dbc
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/nodes.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif
new file mode 100644 (file)
index 0000000..e9a6bd9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/password.gif b/eclipse/org.argeo.cms.swt/icons/password.gif
new file mode 100644 (file)
index 0000000..a6b251f
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/password.gif 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
new file mode 100644 (file)
index 0000000..87acc14
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/person-logged-in.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/person.png b/eclipse/org.argeo.cms.swt/icons/person.png
new file mode 100644 (file)
index 0000000..7d979a5
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/person.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/query.png b/eclipse/org.argeo.cms.swt/icons/query.png
new file mode 100644 (file)
index 0000000..54c089d
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/query.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/refresh.png b/eclipse/org.argeo.cms.swt/icons/refresh.png
new file mode 100644 (file)
index 0000000..71b3481
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/refresh.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif
new file mode 100644 (file)
index 0000000..1492b4e
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif
new file mode 100644 (file)
index 0000000..6c54da9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/remove.gif b/eclipse/org.argeo.cms.swt/icons/remove.gif
new file mode 100644 (file)
index 0000000..0ae6dec
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remove.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif
new file mode 100644 (file)
index 0000000..aa78fd2
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/rename.gif b/eclipse/org.argeo.cms.swt/icons/rename.gif
new file mode 100644 (file)
index 0000000..8048405
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/rename.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repositories.gif b/eclipse/org.argeo.cms.swt/icons/repositories.gif
new file mode 100644 (file)
index 0000000..c13bea1
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repositories.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif
new file mode 100644 (file)
index 0000000..a15fa55
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif
new file mode 100644 (file)
index 0000000..4576dc5
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/resolved.gif b/eclipse/org.argeo.cms.swt/icons/resolved.gif
new file mode 100644 (file)
index 0000000..f4a1ea1
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/resolved.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/role.gif b/eclipse/org.argeo.cms.swt/icons/role.gif
new file mode 100644 (file)
index 0000000..274a850
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/role.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/rollback.gif b/eclipse/org.argeo.cms.swt/icons/rollback.gif
new file mode 100755 (executable)
index 0000000..c753995
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/rollback.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save-all.png b/eclipse/org.argeo.cms.swt/icons/save-all.png
new file mode 100644 (file)
index 0000000..b68a29b
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save-all.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save.gif b/eclipse/org.argeo.cms.swt/icons/save.gif
new file mode 100644 (file)
index 0000000..654ad7b
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save.png b/eclipse/org.argeo.cms.swt/icons/save.png
new file mode 100644 (file)
index 0000000..f27ef2d
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/save_security.png b/eclipse/org.argeo.cms.swt/icons/save_security.png
new file mode 100644 (file)
index 0000000..ca41dc9
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save_security.png 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
new file mode 100644 (file)
index 0000000..fb7d08d
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/security.gif b/eclipse/org.argeo.cms.swt/icons/security.gif
new file mode 100644 (file)
index 0000000..57fb95e
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/security.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/service_published.gif b/eclipse/org.argeo.cms.swt/icons/service_published.gif
new file mode 100644 (file)
index 0000000..17f771a
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/service_published.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif
new file mode 100644 (file)
index 0000000..c24a95f
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/sort.gif b/eclipse/org.argeo.cms.swt/icons/sort.gif
new file mode 100644 (file)
index 0000000..23c5d0b
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/sort.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/starting.gif b/eclipse/org.argeo.cms.swt/icons/starting.gif
new file mode 100644 (file)
index 0000000..563743d
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/starting.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/sync.gif b/eclipse/org.argeo.cms.swt/icons/sync.gif
new file mode 100644 (file)
index 0000000..b4fa052
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/sync.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/user.gif b/eclipse/org.argeo.cms.swt/icons/user.gif
new file mode 100644 (file)
index 0000000..90a0014
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/user.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/users.gif b/eclipse/org.argeo.cms.swt/icons/users.gif
new file mode 100644 (file)
index 0000000..2de7edd
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/users.gif differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.png b/eclipse/org.argeo.cms.swt/icons/workgroup.png
new file mode 100644 (file)
index 0000000..7fef996
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workgroup.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf
new file mode 100644 (file)
index 0000000..f517c82
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png
new file mode 100644 (file)
index 0000000..0430baa
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png differ
diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png
new file mode 100644 (file)
index 0000000..fddcb8c
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png differ
diff --git a/eclipse/org.argeo.cms.swt/pom.xml b/eclipse/org.argeo.cms.swt/pom.xml
new file mode 100644 (file)
index 0000000..a8b43aa
--- /dev/null
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <version>2.3-SNAPSHOT</version>
+               <artifactId>eclipse</artifactId>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.cms.swt</artifactId>
+       <name>CMS SWT</name>
+       <dependencies>
+<!--           <dependency> -->
+<!--                   <groupId>org.argeo.commons</groupId> -->
+<!--                   <artifactId>org.argeo.util</artifactId> -->
+<!--                   <version>2.1.89-SNAPSHOT</version> -->
+<!--           </dependency> -->
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.servlet</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+
+               <!-- Specific -->
+               <dependency>
+                       <groupId>org.argeo.commons.rap</groupId>
+                       <artifactId>org.argeo.swt.specific.rap</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+                       <scope>provided</scope>
+               </dependency>
+               <!-- UI -->
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.rwt</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.core.commands</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.jface</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+       </dependencies>
+</project>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..4ff89f2
--- /dev/null
@@ -0,0 +1,25 @@
+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
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/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
new file mode 100644 (file)
index 0000000..b5f7c0e
--- /dev/null
@@ -0,0 +1,21 @@
+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
new file mode 100644 (file)
index 0000000..a94d707
--- /dev/null
@@ -0,0 +1,251 @@
+package org.argeo.cms.swt;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsStyle;
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsView;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/** SWT utilities. */
+public class CmsSwtUtils {
+
+       /** Singleton. */
+       private CmsSwtUtils() {
+       }
+
+       public static CmsTheme getCmsTheme(Composite parent) {
+               CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName());
+               if (theme == null) {
+                       // find parent shell
+                       Shell topShell = parent.getShell();
+                       while (topShell.getParent() != null)
+                               topShell = (Shell) topShell.getParent();
+                       theme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+                       parent.setData(CmsTheme.class.getName(), theme);
+               }
+               return theme;
+       }
+
+       public static void registerCmsTheme(Shell shell, CmsTheme theme) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsTheme.class.getName()) != null) {
+                       CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+                       throw new IllegalArgumentException(
+                                       "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
+               }
+               topShell.setData(CmsTheme.class.getName(), theme);
+       }
+
+       public static CmsView getCmsView(Control parent) {
+               // find parent shell
+               Shell topShell = parent.getShell();
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               return (CmsView) topShell.getData(CmsView.class.getName());
+       }
+
+       public static void registerCmsView(Shell shell, CmsView view) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsView.class.getName()) != null) {
+                       CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
+                       throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
+               }
+               shell.setData(CmsView.class.getName(), view);
+       }
+
+       /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
+       public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
+               SelectionListener listener = (Selected) (e) -> {
+                       getCmsView(control.getParent()).sendEvent(topic, properties);
+               };
+               if (control instanceof Button) {
+                       ((Button) control).addSelectionListener(listener);
+               } else
+                       throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
+       }
+
+       /**
+        * Convenience method to sends an event via
+        * {@link CmsView#sendEvent(String, Map)}.
+        */
+       public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(key, value);
+               sendEventOnSelect(control, topic, properties);
+       }
+
+       /*
+        * GRID LAYOUT
+        */
+       public static GridLayout noSpaceGridLayout() {
+               return noSpaceGridLayout(new GridLayout());
+       }
+
+       public static GridLayout noSpaceGridLayout(int columns) {
+               return noSpaceGridLayout(new GridLayout(columns, false));
+       }
+
+       /** @return the same layout, with spaces removed. */
+       public static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static GridData fillAll() {
+               return new GridData(SWT.FILL, SWT.FILL, true, true);
+       }
+
+       public static GridData fillWidth() {
+               return grabWidth(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+       }
+
+       public static GridData fillHeight() {
+               return grabHeight(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, false, true);
+       }
+
+       /*
+        * ROW LAYOUT
+        */
+       /** @return the same layout, with margins removed. */
+       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
+               rowLayout.marginTop = 0;
+               rowLayout.marginBottom = 0;
+               rowLayout.marginLeft = 0;
+               rowLayout.marginRight = 0;
+               return rowLayout;
+       }
+
+       public static RowLayout noMarginsRowLayout(int type) {
+               return noMarginsRowLayout(new RowLayout(type));
+       }
+
+       public static RowData rowData16px() {
+               return new RowData(16, 16);
+       }
+
+       /*
+        * FORM LAYOUT
+        */
+       public static FormData coverAll() {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               return fdLabel;
+       }
+
+       /*
+        * STYLING
+        */
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, String style) {
+               if (style == null)
+                       return widget;// does nothing
+               EclipseUiSpecificUtils.setStyleData(widget, style);
+               if (widget instanceof Control) {
+                       CmsView cmsView = getCmsView((Control) widget);
+                       if (cmsView != null)
+                               cmsView.applyStyles(widget);
+               }
+               return widget;
+       }
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, CmsStyle style) {
+               return style(widget, style.style());
+       }
+
+       /** Enable markups on widget */
+       public static <T extends Widget> T markup(T widget) {
+               EclipseUiSpecificUtils.setMarkupData(widget);
+               return widget;
+       }
+
+       /** Disable markup validation. */
+       public static <T extends Widget> T disableMarkupValidation(T widget) {
+               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
+               return widget;
+       }
+
+       /**
+        * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
+        * 
+        * @param widget the widget to style and to use in order to display text
+        * @param txt    the object to display via its <code>toString()</code> method.
+        *               This argument should not be null, but if it is null and
+        *               assertions are disabled "<null>" is displayed instead; if
+        *               assertions are enabled the call will fail.
+        * 
+        * @see markup
+        */
+       public static <T extends Widget> T text(T widget, Object txt) {
+               assert txt != null;
+               String str = txt != null ? txt.toString() : "<null>";
+               markup(widget);
+               if (widget instanceof Label)
+                       ((Label) widget).setText(str);
+               else if (widget instanceof Button)
+                       ((Button) widget).setText(str);
+               else if (widget instanceof Text)
+                       ((Text) widget).setText(str);
+               else
+                       throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
+               return widget;
+       }
+
+       /** A {@link Label} with markup activated. */
+       public static Label lbl(Composite parent, Object txt) {
+               return text(new Label(parent, SWT.NONE), txt);
+       }
+
+       /** A read-only {@link Text} whose content can be copy/pasted. */
+       public static Text txt(Composite parent, Object txt) {
+               return text(new Text(parent, SWT.NONE), txt);
+       }
+
+       /** Dispose all children of a Composite */
+       public static void clear(Composite composite) {
+               if (composite.isDisposed())
+                       return;
+               for (Control child : composite.getChildren())
+                       child.dispose();
+       }
+}
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
new file mode 100644 (file)
index 0000000..b818b06
--- /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#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
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/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
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/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
new file mode 100644 (file)
index 0000000..9c55e8b
--- /dev/null
@@ -0,0 +1,50 @@
+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
new file mode 100644 (file)
index 0000000..9c8680c
--- /dev/null
@@ -0,0 +1,336 @@
+package org.argeo.cms.swt.auth;
+
+import static org.argeo.cms.CmsMsg.password;
+import static org.argeo.cms.CmsMsg.username;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class CmsLogin implements CmsStyles, CallbackHandler {
+       private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
+
+       private Composite parent;
+       private Text usernameT, passwordT;
+       private Composite credentialsBlock;
+       private final SelectionListener loginSelectionListener;
+
+       private final Locale defaultLocale;
+       private LocaleChoice localeChoice = null;
+
+       private final CmsView cmsView;
+
+       // optional subject to be set explicitly
+       private Subject subject = null;
+
+       public CmsLogin(CmsView cmsView) {
+               this.cmsView = cmsView;
+               CmsContext nodeState = null;// = Activator.getNodeState();
+               // FIXME reactivate locales
+               if (nodeState != null) {
+                       defaultLocale = nodeState.getDefaultLocale();
+                       List<Locale> locales = nodeState.getLocales();
+                       if (locales != null)
+                               localeChoice = new LocaleChoice(locales, defaultLocale);
+               } else {
+                       defaultLocale = Locale.getDefault();
+               }
+               loginSelectionListener = new SelectionListener() {
+                       private static final long serialVersionUID = -8832133363830973578L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               login();
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                       }
+               };
+       }
+
+       protected boolean isAnonymous() {
+               return cmsView.isAnonymous();
+       }
+
+       public final void createUi(Composite parent) {
+               this.parent = parent;
+               createContents(parent);
+       }
+
+       protected void createContents(Composite parent) {
+               defaultCreateContents(parent);
+       }
+
+       public final void defaultCreateContents(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite credentialsBlock = createCredentialsBlock(parent);
+               if (parent instanceof Shell) {
+                       credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               }
+       }
+
+       public final Composite createCredentialsBlock(Composite parent) {
+               if (isAnonymous()) {
+                       return anonymousUi(parent);
+               } else {
+                       return userUi(parent);
+               }
+       }
+
+       public Composite getCredentialsBlock() {
+               return credentialsBlock;
+       }
+
+       protected Composite userUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+
+               specificUserUi(credentialsBlock);
+
+               Label l = new Label(credentialsBlock, SWT.NONE);
+               CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
+               l.setText(CmsMsg.logout.lead(locale));
+               GridData lData = CmsSwtUtils.fillWidth();
+               lData.widthHint = 120;
+               l.setLayoutData(lData);
+
+               l.addMouseListener(new MouseAdapter() {
+                       private static final long serialVersionUID = 6444395812777413116L;
+
+                       public void mouseDown(MouseEvent e) {
+                               logout();
+                       }
+               });
+               return credentialsBlock;
+       }
+
+       /** To be overridden */
+       protected void specificUserUi(Composite parent) {
+
+       }
+
+       protected Composite anonymousUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               // We need a composite for the traversal
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+               CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
+
+               Integer textWidth = 120;
+               if (parent instanceof Shell)
+                       CmsSwtUtils.style(parent, CMS_USER_MENU);
+               // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
+               usernameT = new Text(credentialsBlock, SWT.BORDER);
+               usernameT.setMessage(username.lead(locale));
+               CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
+               GridData gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               usernameT.setLayoutData(gd);
+
+               // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
+               passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
+               passwordT.setMessage(password.lead(locale));
+               CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
+               gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               passwordT.setLayoutData(gd);
+
+               TraverseListener tl = new TraverseListener() {
+                       private static final long serialVersionUID = -1158892811534971856L;
+
+                       public void keyTraversed(TraverseEvent e) {
+                               if (e.detail == SWT.TRAVERSE_RETURN)
+                                       login();
+                       }
+               };
+               credentialsBlock.addTraverseListener(tl);
+               usernameT.addTraverseListener(tl);
+               passwordT.addTraverseListener(tl);
+               parent.setTabList(new Control[] { credentialsBlock });
+               credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
+
+               // Button
+               Button loginButton = new Button(credentialsBlock, SWT.PUSH);
+               loginButton.setText(CmsMsg.login.lead(locale));
+               loginButton.setLayoutData(CmsSwtUtils.fillWidth());
+               loginButton.addSelectionListener(loginSelectionListener);
+
+               extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
+               if (localeChoice != null)
+                       createLocalesBlock(credentialsBlock);
+               return credentialsBlock;
+       }
+
+       /**
+        * To be overridden in order to provide custom login button and other links.
+        */
+       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
+                       SelectionListener loginSelectionListener) {
+
+       }
+
+       protected void updateLocale(Locale selectedLocale) {
+               // save already entered values
+               String usernameStr = usernameT.getText();
+               char[] pwd = passwordT.getTextChars();
+
+               for (Control child : parent.getChildren())
+                       child.dispose();
+               createContents(parent);
+               if (parent.getParent() != null)
+                       parent.getParent().layout(true, true);
+               else
+                       parent.layout();
+               usernameT.setText(usernameStr);
+               passwordT.setTextChars(pwd);
+       }
+
+       protected Composite createLocalesBlock(final Composite parent) {
+               Composite c = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
+               c.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               c.setLayoutData(CmsSwtUtils.fillAll());
+
+               SelectionListener selectionListener = new SelectionAdapter() {
+                       private static final long serialVersionUID = 4891637813567806762L;
+
+                       public void widgetSelected(SelectionEvent event) {
+                               Button button = (Button) event.widget;
+                               if (button.getSelection()) {
+                                       localeChoice.setSelectedIndex((Integer) event.widget.getData());
+                                       updateLocale(localeChoice.getSelectedLocale());
+                               }
+                       };
+               };
+
+               List<Locale> locales = localeChoice.getLocales();
+               for (Integer i = 0; i < locales.size(); i++) {
+                       Locale locale = locales.get(i);
+                       Button button = new Button(c, SWT.RADIO);
+                       CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
+                       button.setData(i);
+                       button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
+                       // button.addListener(SWT.Selection, listener);
+                       button.addSelectionListener(selectionListener);
+                       if (i == localeChoice.getSelectedIndex())
+                               button.setSelection(true);
+               }
+               return c;
+       }
+
+       protected boolean login() {
+               // TODO use CmsVie in order to retrieve subject?
+               // Subject subject = cmsView.getLoginContext().getSubject();
+               // LoginContext loginContext = cmsView.getLoginContext();
+               try {
+                       //
+                       // LOGIN
+                       //
+                       // loginContext.logout();
+                       LoginContext loginContext;
+                       if (subject == null)
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
+                       else
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
+                       loginContext.login();
+                       cmsView.authChange(loginContext);
+                       return true;
+               } catch (LoginException e) {
+                       if (log.isTraceEnabled())
+                               log.warn("Login failed: " + e.getMessage(), e);
+                       else
+                               log.warn("Login failed: " + e.getMessage());
+
+                       try {
+                               Thread.sleep(3000);
+                       } catch (InterruptedException e2) {
+                               // silent
+                       }
+                       // ErrorFeedback.show("Login failed", e);
+                       return false;
+               }
+               // catch (LoginException e) {
+               // log.error("Cannot login", e);
+               // return false;
+               // }
+       }
+
+       protected void logout() {
+               cmsView.logout();
+               cmsView.navigateTo("~");
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks) {
+                       if (callback instanceof NameCallback && usernameT != null)
+                               ((NameCallback) callback).setName(usernameT.getText());
+                       else if (callback instanceof PasswordCallback && passwordT != null)
+                               ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
+                       else if (callback instanceof RemoteAuthCallback) {
+                               ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+                               ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+                       } else if (callback instanceof LanguageCallback) {
+                               Locale toUse = null;
+                               if (localeChoice != null)
+                                       toUse = localeChoice.getSelectedLocale();
+                               else if (defaultLocale != null)
+                                       toUse = defaultLocale;
+
+                               if (toUse != null) {
+                                       ((LanguageCallback) callback).setLocale(toUse);
+                                       UiContext.setLocale(toUse);
+                               }
+
+                       }
+               }
+       }
+
+       public void setSubject(Subject subject) {
+               this.subject = subject;
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..f6a35f1
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.cms.swt.auth;
+
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class CmsLoginShell extends CmsLogin {
+       private final Shell shell;
+
+       public CmsLoginShell(CmsView cmsView) {
+               super(cmsView);
+               shell = createShell();
+//             createUi(shell);
+       }
+
+       /** To be overridden. */
+       protected Shell createShell() {
+               Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
+               shell.setMaximized(true);
+               return shell;
+       }
+
+       /** To be overridden. */
+       public void open() {
+               CmsSwtUtils.style(shell, CMS_USER_MENU);
+               shell.open();
+       }
+
+       @Override
+       protected boolean login() {
+               boolean success = false;
+               try {
+                       success = super.login();
+                       return success;
+               } finally {
+                       if (success)
+                               closeShell();
+                       else {
+                               for (Control child : shell.getChildren())
+                                       child.dispose();
+                               createUi(shell);
+                               shell.layout();
+                               // TODO error message
+                       }
+               }
+       }
+
+       @Override
+       protected void logout() {
+               closeShell();
+               super.logout();
+       }
+
+       protected void closeShell() {
+               if (!shell.isDisposed()) {
+                       shell.close();
+                       shell.dispose();
+               }
+       }
+
+       public Shell getShell() {
+               return shell;
+       }
+       
+       public void createUi(){
+               createUi(shell);
+       }
+}
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
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/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
new file mode 100644 (file)
index 0000000..b0c36c6
--- /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.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
new file mode 100644 (file)
index 0000000..e98e390
--- /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.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
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/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
new file mode 100644 (file)
index 0000000..8ff0862
--- /dev/null
@@ -0,0 +1,83 @@
+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
new file mode 100644 (file)
index 0000000..a01c919
--- /dev/null
@@ -0,0 +1,100 @@
+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
new file mode 100644 (file)
index 0000000..66e6405
--- /dev/null
@@ -0,0 +1,167 @@
+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
new file mode 100644 (file)
index 0000000..59d9ab7
--- /dev/null
@@ -0,0 +1,220 @@
+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
new file mode 100644 (file)
index 0000000..bf6417b
--- /dev/null
@@ -0,0 +1,255 @@
+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
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/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
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/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
new file mode 100644 (file)
index 0000000..4039f2b
--- /dev/null
@@ -0,0 +1,130 @@
+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
new file mode 100644 (file)
index 0000000..8109c40
--- /dev/null
@@ -0,0 +1,11 @@
+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
new file mode 100644 (file)
index 0000000..b9b2751
--- /dev/null
@@ -0,0 +1,93 @@
+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
new file mode 100644 (file)
index 0000000..ed1bfd8
--- /dev/null
@@ -0,0 +1,246 @@
+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
new file mode 100644 (file)
index 0000000..d1c90a4
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.swt.useradmin;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.auth.UserAdminUtils;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Centralize label providers for the group table */
+class UserLP extends ColumnLabelProvider {
+       private static final long serialVersionUID = -4645930210988368571L;
+
+       final static String COL_ICON = "colID.icon";
+       final static String COL_DN = "colID.dn";
+       final static String COL_DISPLAY_NAME = "colID.displayName";
+       final static String COL_DOMAIN = "colID.domain";
+
+       final String currType;
+
+       // private Font italic;
+       private Font bold;
+
+       UserLP(String colId) {
+               this.currType = colId;
+       }
+
+       @Override
+       public Font getFont(Object element) {
+               // Current user as bold
+               if (UserAdminUtils.isCurrentUser(((User) element))) {
+                       if (bold == null)
+                               bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD)
+                                               .createFont(Display.getCurrent());
+                       return bold;
+               }
+               return null;
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               if (COL_ICON.equals(currType)) {
+                       User user = (User) element;
+                       String dn = user.getName();
+                       if (dn.endsWith(CmsConstants.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
new file mode 100644 (file)
index 0000000..21fc5af
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.swt.useradmin;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Specific users icons. */
+public class UsersImages {
+       private final static String PREFIX = "icons/";
+
+       public final static Image ICON_USER = CmsImages.createImg(PREFIX + "person.png");
+       public final static Image ICON_GROUP = CmsImages.createImg(PREFIX + "group.png");
+       public final static Image ICON_ROLE = CmsImages.createImg(PREFIX + "role.gif");
+       public final static Image ICON_CHANGE_PASSWORD = CmsImages.createImg(PREFIX + "security.gif");
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..3597bfc
--- /dev/null
@@ -0,0 +1,2 @@
+/** SWT/JFace users management components. */
+package org.argeo.cms.swt.useradmin;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..1c4d79e
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.cms.ui.theme;
+
+import java.net.URL;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public class CmsImages {
+       private static BundleContext themeBc = FrameworkUtil.getBundle(CmsImages.class).getBundleContext();
+
+       final public static String ICONS_BASE = "icons/";
+       final public static String TYPES_BASE = ICONS_BASE + "types/";
+       final public static String ACTIONS_BASE = ICONS_BASE + "actions/";
+
+       public static Image createIcon(String name) {
+               return createImg(CmsImages.ICONS_BASE + name);
+       }
+
+       public static Image createAction(String name) {
+               return createImg(CmsImages.ACTIONS_BASE + name);
+       }
+
+       public static Image createType(String name) {
+               return createImg(CmsImages.TYPES_BASE + name);
+       }
+
+       public static Image createImg(String name) {
+               return CmsImages.createDesc(name).createImage(Display.getDefault());
+       }
+
+       public static ImageDescriptor createDesc(String name) {
+               return createDesc(themeBc, name);
+       }
+
+       public static ImageDescriptor createDesc(BundleContext bc, String name) {
+               URL url = bc.getBundle().getResource(name);
+               if (url == null)
+                       return ImageDescriptor.getMissingImageDescriptor();
+               return ImageDescriptor.createFromURL(url);
+       }
+
+       public static Image createImg(BundleContext bc, String name) {
+               return createDesc(bc, name).createImage(Display.getDefault());
+       }
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..7d3a260
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS core theme images. */
+package org.argeo.cms.ui.theme;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..c882eb7
--- /dev/null
@@ -0,0 +1,42 @@
+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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
new file mode 100644 (file)
index 0000000..cf3c157
--- /dev/null
@@ -0,0 +1,133 @@
+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
new file mode 100644 (file)
index 0000000..a388e74
--- /dev/null
@@ -0,0 +1,106 @@
+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
new file mode 100644 (file)
index 0000000..f2715bc
--- /dev/null
@@ -0,0 +1,141 @@
+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
new file mode 100644 (file)
index 0000000..615e141
--- /dev/null
@@ -0,0 +1,256 @@
+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
new file mode 100644 (file)
index 0000000..8ce9b44
--- /dev/null
@@ -0,0 +1,130 @@
+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
new file mode 100644 (file)
index 0000000..d6ab148
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
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/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
new file mode 100644 (file)
index 0000000..ce2f2a8
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png 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
new file mode 100644 (file)
index 0000000..c31f37e
Binary files /dev/null and b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png 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
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/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
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/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
new file mode 100644 (file)
index 0000000..5713905
--- /dev/null
@@ -0,0 +1,402 @@
+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
new file mode 100644 (file)
index 0000000..9e93b11
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..8f4df17
--- /dev/null
@@ -0,0 +1,58 @@
+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
new file mode 100644 (file)
index 0000000..798d174
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic SWT/JFace JCR helpers. */
+package org.argeo.eclipse.ui.util;
\ No newline at end of file
diff --git a/eclipse/pom.xml b/eclipse/pom.xml
new file mode 100644 (file)
index 0000000..abd7878
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>argeo-commons</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <groupId>org.argeo.commons</groupId>
+       <artifactId>eclipse</artifactId>
+       <name>Eclipse Specific</name>
+       <packaging>pom</packaging>
+       <modules>
+               <module>org.argeo.cms.servlet</module>
+               <module>org.argeo.cms.swt</module>
+               <module>org.argeo.cms.e4</module>
+       </modules>
+</project>
\ No newline at end of file
diff --git a/jcr/cnf/maven.bnd b/jcr/cnf/maven.bnd
new file mode 100644 (file)
index 0000000..4bd5c0c
--- /dev/null
@@ -0,0 +1 @@
+-include: ../../cnf/maven.bnd
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/.classpath b/jcr/org.argeo.cms.jcr/.classpath
new file mode 100644 (file)
index 0000000..4a00bec
--- /dev/null
@@ -0,0 +1,11 @@
+<?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
new file mode 100644 (file)
index 0000000..3e470f8
--- /dev/null
@@ -0,0 +1,33 @@
+<?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
new file mode 100644 (file)
index 0000000..7e2e119
--- /dev/null
@@ -0,0 +1,101 @@
+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
new file mode 100644 (file)
index 0000000..f5fc8de
--- /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.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
new file mode 100644 (file)
index 0000000..a283ef0
--- /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" 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
new file mode 100644 (file)
index 0000000..5fb56e3
--- /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.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
new file mode 100644 (file)
index 0000000..a94b151
--- /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" 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
new file mode 100644 (file)
index 0000000..e26453b
--- /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="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
new file mode 100644 (file)
index 0000000..b43b519
--- /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="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
new file mode 100644 (file)
index 0000000..a0885bb
--- /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.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
new file mode 100644 (file)
index 0000000..db2bfaa
--- /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="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
new file mode 100644 (file)
index 0000000..71071f6
--- /dev/null
@@ -0,0 +1,37 @@
+Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator
+
+Provide-Capability:\
+cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\
+cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\
+cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\
+osgi.service;objectClass="javax.jcr.Repository"
+
+Import-Package:\
+org.argeo.cms.servlet,\
+javax.jcr.security,\
+org.h2;resolution:=optional,\
+org.postgresql;version="[42,43)";resolution:=optional,\
+org.apache.jackrabbit.webdav.server,\
+org.apache.jackrabbit.webdav.jcr,\
+org.apache.commons.httpclient.cookie;resolution:=optional,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.osgi.*;version=0.0.0,\
+org.osgi.service.http.whiteboard,\
+org.apache.jackrabbit.api,\
+org.apache.jackrabbit.commons,\
+org.apache.jackrabbit.spi,\
+org.apache.jackrabbit.spi2dav,\
+org.apache.jackrabbit.spi2davex,\
+org.apache.jackrabbit.webdav,\
+junit.*;resolution:=optional,\
+*
+
+Service-Component:\
+OSGI-INF/repositoryContextsFactory.xml,\
+OSGI-INF/jcrRepositoryFactory.xml,\
+OSGI-INF/jcrFsProvider.xml,\
+OSGI-INF/jcrDeployment.xml,\
+OSGI-INF/jcrServletContext.xml,\
+OSGI-INF/dataServletContext.xml,\
+OSGI-INF/filesServletContext.xml,\
+OSGI-INF/filesServlet.xml
diff --git a/jcr/org.argeo.cms.jcr/build.properties b/jcr/org.argeo.cms.jcr/build.properties
new file mode 100644 (file)
index 0000000..3ddcf97
--- /dev/null
@@ -0,0 +1,28 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/jcrDeployment.xml,\
+               OSGI-INF/repositoryContextsFactory.xml,\
+               OSGI-INF/jcrRepositoryFactory.xml,\
+               OSGI-INF/jcrFsProvider.xml
+source.. = src/
+additional.bundles = org.apache.jackrabbit.core,\
+                     javax.jcr,\
+                     org.apache.jackrabbit.api,\
+                     org.apache.jackrabbit.data,\
+                     org.apache.jackrabbit.jcr.commons,\
+                     org.apache.jackrabbit.spi,\
+                     org.apache.jackrabbit.spi.commons,\
+                     org.slf4j.api,\
+                     org.apache.commons.collections,\
+                     EDU.oswego.cs.dl.util.concurrent,\
+                     org.apache.lucene,\
+                     org.apache.tika.core,\
+                     org.apache.commons.dbcp,\
+                     org.apache.commons.pool,\
+                     com.google.guava,\
+                     org.apache.jackrabbit.jcr2spi,\
+                     org.apache.jackrabbit.spi2dav,\
+                     org.apache.httpcomponents.httpclient,\
+                     org.apache.httpcomponents.httpcore,\
+                     org.apache.tika.parsers
diff --git a/jcr/org.argeo.cms.jcr/pom.xml b/jcr/org.argeo.cms.jcr/pom.xml
new file mode 100644 (file)
index 0000000..b6f63c5
--- /dev/null
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>jcr</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.cms.jcr</artifactId>
+       <name>CMS JCR</name>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.servlet</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+       </dependencies>
+</project>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..40d38ee
--- /dev/null
@@ -0,0 +1,88 @@
+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
new file mode 100644 (file)
index 0000000..c289857
--- /dev/null
@@ -0,0 +1,58 @@
+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
new file mode 100644 (file)
index 0000000..40c83f6
--- /dev/null
@@ -0,0 +1,21 @@
+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
new file mode 100644 (file)
index 0000000..0536fb6
--- /dev/null
@@ -0,0 +1,123 @@
+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
new file mode 100644 (file)
index 0000000..dba005c
--- /dev/null
@@ -0,0 +1,68 @@
+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
new file mode 100644 (file)
index 0000000..a45656c
--- /dev/null
@@ -0,0 +1,72 @@
+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
new file mode 100644 (file)
index 0000000..3db9716
--- /dev/null
@@ -0,0 +1,225 @@
+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
new file mode 100644 (file)
index 0000000..ace0fa5
--- /dev/null
@@ -0,0 +1,86 @@
+<?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
new file mode 100644 (file)
index 0000000..4303676
--- /dev/null
@@ -0,0 +1,86 @@
+<?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
new file mode 100644 (file)
index 0000000..b889079
--- /dev/null
@@ -0,0 +1,60 @@
+<?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
new file mode 100644 (file)
index 0000000..3630a14
--- /dev/null
@@ -0,0 +1,59 @@
+<?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
new file mode 100644 (file)
index 0000000..de2f245
--- /dev/null
@@ -0,0 +1,79 @@
+<?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
new file mode 100644 (file)
index 0000000..488ad6b
--- /dev/null
@@ -0,0 +1,87 @@
+<?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
new file mode 100644 (file)
index 0000000..b430674
--- /dev/null
@@ -0,0 +1,103 @@
+<?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
new file mode 100644 (file)
index 0000000..5229d16
--- /dev/null
@@ -0,0 +1,82 @@
+<?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
new file mode 100644 (file)
index 0000000..b5d9adf
--- /dev/null
@@ -0,0 +1,276 @@
+package org.argeo.cms.jcr;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+
+/** Utilities related to Argeo model in JCR */
+public class CmsJcrUtils {
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#CN} in order to simplify it and protect against future
+        * API changes.
+        */
+       public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(CmsConstants.CN, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias,
+                                       e);
+               }
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+        * future API changes.
+        */
+       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) {
+               return getRepositoryByUri(repositoryFactory, uri, null);
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+        * future API changes.
+        */
+       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(CmsConstants.LABELED_URI, uri);
+                       if (alias != null)
+                               parameters.put(CmsConstants.CN, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e);
+               }
+       }
+
+       /**
+        * Returns the home node of the user or null if none was found.
+        * 
+        * @param session  the session to use in order to perform the search, this can
+        *                 be a session with a different user ID than the one searched,
+        *                 typically when a system or admin session is used.
+        * @param username the username of the user
+        */
+       public static Node getUserHome(Session session, String username) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for user " + username, e);
+//             }
+
+               try {
+                       checkUserWorkspace(session, username);
+                       String homePath = getHomePath(username);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/home/" + username;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Cannot find home for user " + username, e);
+               }
+       }
+
+       private static String getHomePath(String username) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid name " + username, e);
+               }
+               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+               return '/' + userId;
+       }
+
+       private static void checkUserWorkspace(Session session, String username) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
+       }
+
+       /**
+        * Returns the home node of the user or null if none was found.
+        * 
+        * @param session   the session to use in order to perform the search, this can
+        *                  be a session with a different user ID than the one searched,
+        *                  typically when a system or admin session is used.
+        * @param groupname the name of the group
+        */
+       public static Node getGroupHome(Session session, String groupname) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for group " + cn, e);
+//             }
+
+               try {
+                       checkGroupWorkspace(session, groupname);
+                       String homePath = getGroupPath(groupname);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/groups/" + groupname;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Cannot find home for group " + groupname, e);
+               }
+
+       }
+
+       private static String getGroupPath(String groupname) {
+               String cn;
+               try {
+                       LdapName dn = new LdapName(groupname);
+                       cn = dn.getRdn(dn.size() - 1).getValue().toString();
+               } catch (InvalidNameException e) {
+                       cn = groupname;
+               }
+               return '/' + cn;
+       }
+
+       private static void checkGroupWorkspace(Session session, String groupname) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
+       }
+
+       /**
+        * Queries one single node.
+        * 
+        * @return one single node or null if none was found
+        * @throws ArgeoJcrException if more than one node was found
+        */
+//     private static Node querySingleNode(Query query) {
+//             NodeIterator nodeIterator;
+//             try {
+//                     QueryResult queryResult = query.execute();
+//                     nodeIterator = queryResult.getNodes();
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot execute query " + query, e);
+//             }
+//             Node node;
+//             if (nodeIterator.hasNext())
+//                     node = nodeIterator.nextNode();
+//             else
+//                     return null;
+//
+//             if (nodeIterator.hasNext())
+//                     throw new RuntimeException("Query returned more than one node.");
+//             return node;
+//     }
+
+       /** Returns the home node of the session user or null if none was found. */
+       public static Node getUserHome(Session session) {
+               String userID = session.getUserID();
+               return getUserHome(session, userID);
+       }
+
+       /** Whether this node is the home of the user of the underlying session. */
+       public static boolean isUserHome(Node node) {
+               try {
+                       String userID = node.getSession().getUserID();
+                       return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID);
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       /**
+        * Translate the path to this node into a path containing the name of the
+        * repository and the name of the workspace.
+        */
+       public static String getDataPath(String cn, Node node) {
+               assert node != null;
+               StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
+               try {
+                       return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName())
+                                       .append(node.getPath()).toString();
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e);
+               }
+       }
+
+       /**
+        * Translate the path to this node into a path containing the name of the
+        * repository and the name of the workspace.
+        */
+       public static String getDataPath(Node node) {
+               return getDataPath(CmsConstants.NODE, node);
+       }
+
+       /**
+        * Open a JCR session with full read/write rights on the data, as
+        * {@link CmsConstants#ROLE_USER_ADMIN}, using the
+        * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security
+        * hardened deployement, use {@link AuthPermission} on this login context.
+        */
+       public static Session openDataAdminSession(Repository repository, String workspaceName) {
+               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+               LoginContext loginContext;
+               try {
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCl);
+               }
+               return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+
+                       @Override
+                       public Session run() {
+                               try {
+                                       return repository.login(workspaceName);
+                               } catch (NoSuchWorkspaceException e) {
+                                       throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
+                               } catch (RepositoryException e) {
+                                       throw new RuntimeException("Cannot open data admin session", e);
+                               }
+                       }
+
+               });
+       }
+
+       /** Singleton. */
+       private CmsJcrUtils() {
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..04c5d2d
--- /dev/null
@@ -0,0 +1,201 @@
+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
new file mode 100644 (file)
index 0000000..ef8e375
--- /dev/null
@@ -0,0 +1,70 @@
+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
new file mode 100644 (file)
index 0000000..c9e6ee7
--- /dev/null
@@ -0,0 +1,34 @@
+<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
new file mode 100644 (file)
index 0000000..80849be
--- /dev/null
@@ -0,0 +1,10 @@
+// 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
new file mode 100644 (file)
index 0000000..340d137
--- /dev/null
@@ -0,0 +1,481 @@
+package org.argeo.cms.jcr.internal;
+
+import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.callback.CallbackHandler;
+import javax.servlet.Servlet;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.internal.jcr.JcrInitUtils;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet;
+import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet;
+import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils;
+import org.argeo.cms.osgi.DataModelNamespace;
+import org.argeo.cms.security.CryptoKeyring;
+import org.argeo.cms.security.Keyring;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.LangUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** Implementation of a CMS deployment. */
+public class CmsJcrDeployment {
+       private final CmsLog log = CmsLog.getLog(getClass());
+       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+       private DataModels dataModels;
+       private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG;
+
+       private boolean argeoDataModelExtensionsAvailable = false;
+
+       // Readiness
+       private boolean nodeAvailable = false;
+
+       CmsDeployment cmsDeployment;
+
+       public CmsJcrDeployment() {
+//             initTrackers();
+       }
+
+       public void start() {
+               dataModels = new DataModels(bc);
+
+               ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
+               repoContextSt.open();
+               //KernelUtils.asyncOpen(repoContextSt);
+
+//             nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
+
+               JcrInitUtils.addToDeployment(cmsDeployment);
+
+       }
+
+       public void stop() {
+//             if (nodeHttp != null)
+//                     nodeHttp.destroy();
+
+               try {
+                       for (ServiceReference<JackrabbitLocalRepository> sr : bc
+                                       .getServiceReferences(JackrabbitLocalRepository.class, null)) {
+                               bc.getService(sr).destroy();
+                       }
+               } catch (InvalidSyntaxException e1) {
+                       log.error("Cannot clean repositories", e1);
+               }
+
+       }
+
+       public void setCmsDeployment(CmsDeployment cmsDeployment) {
+               this.cmsDeployment = cmsDeployment;
+       }
+
+       /**
+        * Checks whether the deployment is available according to expectations, and
+        * mark it as available.
+        */
+//     private synchronized void checkReadiness() {
+//             if (isAvailable())
+//                     return;
+//             if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
+//                     String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
+//                     String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
+//                     availableSince = System.currentTimeMillis();
+//                     long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+//                     String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
+//                     log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+//                     if (log.isDebugEnabled()) {
+//                             log.debug("## state: " + state);
+//                             if (data != null)
+//                                     log.debug("## data: " + data);
+//                     }
+//                     long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
+//                     long initDuration = System.currentTimeMillis() - begin;
+//                     if (log.isTraceEnabled())
+//                             log.trace("Kernel initialization took " + initDuration + "ms");
+//                     tributeToFreeSoftware(initDuration);
+//             }
+//     }
+
+       private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
+//             if (availableSince != null) {
+//                     throw new IllegalStateException("Deployment is already available");
+//             }
+
+               // home
+               prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
+
+               // init from backup
+//             if (deployConfig.isFirstInit()) {
+//                     Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
+//                     if (Files.exists(restorePath)) {
+//                             if (log.isDebugEnabled())
+//                                     log.debug("Found backup " + restorePath + ", restoring it...");
+//                             LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
+//                             KernelUtils.doAsDataAdmin(logicalRestore);
+//                             log.info("Restored backup from " + restorePath);
+//                     }
+//             }
+
+               // init from repository
+               Collection<ServiceReference<Repository>> initRepositorySr;
+               try {
+                       initRepositorySr = bc.getServiceReferences(Repository.class,
+                                       "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
+               } catch (InvalidSyntaxException e1) {
+                       throw new IllegalArgumentException(e1);
+               }
+               Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
+               while (it.hasNext()) {
+                       ServiceReference<Repository> sr = it.next();
+                       Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
+                       Repository initRepository = bc.getService(sr);
+                       if (log.isDebugEnabled())
+                               log.debug("Found init repository " + labeledUri + ", copying it...");
+                       initFromRepository(deployedNodeRepository, initRepository);
+                       log.info("Node repository initialised from " + labeledUri);
+               }
+       }
+
+       /** Init from a (typically remote) repository. */
+       private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
+               Session initSession = null;
+               try {
+                       initSession = initRepository.login();
+                       workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
+                               if ("security".equals(workspaceName))
+                                       continue workspaces;
+                               if (log.isDebugEnabled())
+                                       log.debug("Copying workspace " + workspaceName + " from init repository...");
+                               long begin = System.currentTimeMillis();
+                               Session targetSession = null;
+                               Session sourceSession = null;
+                               try {
+                                       try {
+                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+                                       } catch (IllegalArgumentException e) {// no such workspace
+                                               Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
+                                               try {
+                                                       adminSession.getWorkspace().createWorkspace(workspaceName);
+                                               } finally {
+                                                       Jcr.logout(adminSession);
+                                               }
+                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+                                       }
+                                       sourceSession = initRepository.login(workspaceName);
+//                                     JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
+                                       // TODO deal with referenceable nodes
+                                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
+                                       targetSession.save();
+                                       long duration = System.currentTimeMillis() - begin;
+                                       if (log.isDebugEnabled())
+                                               log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
+                                                               + " s");
+                               } catch (Exception e) {
+                                       log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
+                               } finally {
+                                       Jcr.logout(sourceSession);
+                                       Jcr.logout(targetSession);
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               } finally {
+                       Jcr.logout(initSession);
+               }
+       }
+
+       private void prepareHomeRepository(RepositoryImpl deployedRepository) {
+               Session adminSession = KernelUtils.openAdminSession(deployedRepository);
+               try {
+                       argeoDataModelExtensionsAvailable = Arrays
+                                       .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
+                                       .contains(ArgeoNames.ARGEO_NAMESPACE);
+               } catch (RepositoryException e) {
+                       log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
+                       argeoDataModelExtensionsAvailable = false;
+               } finally {
+                       JcrUtils.logoutQuietly(adminSession);
+               }
+
+               // Publish home with the highest service ranking
+               Hashtable<String, Object> regProps = new Hashtable<>();
+               regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
+               regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+               Repository egoRepository = new EgoRepository(deployedRepository, false);
+               bc.registerService(Repository.class, egoRepository, regProps);
+               registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
+
+               // Keyring only if Argeo extensions are available
+               if (argeoDataModelExtensionsAvailable) {
+                       new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
+
+                               @Override
+                               public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
+                                       NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
+                                       CallbackHandler callbackHandler = bc.getService(reference);
+                                       nodeKeyring.setDefaultCallbackHandler(callbackHandler);
+                                       bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
+                                                       nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
+                                       return callbackHandler;
+                               }
+
+                       }.open();
+               }
+       }
+
+       /** Session is logged out. */
+       private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
+               Session adminSession = KernelUtils.openAdminSession(repository);
+               try {
+                       Set<String> processed = new HashSet<String>();
+                       bundles: for (Bundle bundle : bc.getBundles()) {
+                               BundleWiring wiring = bundle.adapt(BundleWiring.class);
+                               if (wiring == null)
+                                       continue bundles;
+                               if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
+                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+                               else {
+                                       List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+                                       for (BundleCapability capability : capabilities) {
+                                               String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
+                                               if (dataModelName.equals(cn))// process only own data model
+                                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+                                       }
+                               }
+                       }
+               } finally {
+                       JcrUtils.logoutQuietly(adminSession);
+               }
+       }
+
+       private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
+                       boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
+               // recursively process requirements first
+               List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
+               for (BundleWire wire : requiredWires) {
+                       processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
+               }
+
+               List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+               capabilities: for (BundleCapability capability : capabilities) {
+                       if (!importListedAbstractModels
+                                       && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
+                               continue capabilities;
+                       }
+                       boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
+                       if (publish)
+                               publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
+               }
+       }
+
+       private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
+                       Set<String> processed) {
+               Map<String, Object> attrs = capability.getAttributes();
+               String name = (String) attrs.get(DataModelNamespace.NAME);
+               if (processed.contains(name)) {
+                       if (log.isTraceEnabled())
+                               log.trace("Data model " + name + " has already been processed");
+                       return false;
+               }
+
+               // CND
+               String path = (String) attrs.get(DataModelNamespace.CND);
+               if (path != null) {
+                       File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
+                       if (!dataModel.exists()) {
+                               URL url = capability.getRevision().getBundle().getResource(path);
+                               if (url == null)
+                                       throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
+                               try (Reader reader = new InputStreamReader(url.openStream())) {
+                                       CndImporter.registerNodeTypes(reader, adminSession, true);
+                                       processed.add(name);
+                                       dataModel.getParentFile().mkdirs();
+                                       dataModel.createNewFile();
+                                       if (log.isDebugEnabled())
+                                               log.debug("Registered CND " + url);
+                               } catch (Exception e) {
+                                       log.error("Cannot import CND " + url, e);
+                               }
+                       }
+               }
+
+               if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
+                       return false;
+               // Non abstract
+               boolean isStandalone = isStandalone(name);
+               boolean publishLocalRepo;
+               if (isStandalone && name.equals(cn))// includes the node itself
+                       publishLocalRepo = true;
+               else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
+                       publishLocalRepo = true;
+               else
+                       publishLocalRepo = false;
+
+               return publishLocalRepo;
+       }
+
+       boolean isStandalone(String dataModelName) {
+               return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
+       }
+
+       private void publishLocalRepo(String dataModelName, Repository repository) {
+               Hashtable<String, Object> properties = new Hashtable<>();
+               properties.put(CmsConstants.CN, dataModelName);
+               LocalRepository localRepository;
+               String[] classes;
+               if (repository instanceof RepositoryImpl) {
+                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
+                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
+                                       JackrabbitLocalRepository.class.getName() };
+               } else {
+                       localRepository = new LocalRepository(repository, dataModelName);
+                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
+               }
+               bc.registerService(classes, localRepository, properties);
+
+               // TODO make it configurable
+               registerRepositoryServlets(dataModelName, localRepository);
+               if (log.isTraceEnabled())
+                       log.trace("Published data model " + dataModelName);
+       }
+
+//     @Override
+//     public synchronized Long getAvailableSince() {
+//             return availableSince;
+//     }
+//
+//     public synchronized boolean isAvailable() {
+//             return availableSince != null;
+//     }
+
+       protected void registerRepositoryServlets(String alias, Repository repository) {
+               // FIXME re-enable it with a proper class loader
+//             registerRemotingServlet(alias, repository);
+//             registerWebdavServlet(alias, repository);
+       }
+
+       protected void registerWebdavServlet(String alias, Repository repository) {
+               CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
+               Hashtable<String, String> ip = new Hashtable<>();
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+                               "/" + alias);
+
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
+               bc.registerService(Servlet.class, webdavServlet, ip);
+       }
+
+       protected void registerRemotingServlet(String alias, Repository repository) {
+               CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
+               Hashtable<String, String> ip = new Hashtable<>();
+               ip.put(CmsConstants.CN, alias);
+               // Properties ip = new Properties();
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+                               "/" + alias);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
+                               "Negotiate");
+
+               // Looks like a bug in Jackrabbit remoting init
+               Path tmpDir;
+               try {
+                       tmpDir = Files.createTempDirectory("remoting_" + alias);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
+               }
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
+                               "remoting_" + alias);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
+                               JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
+
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
+               bc.registerService(Servlet.class, remotingServlet, ip);
+       }
+
+       private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
+
+               public RepositoryContextStc() {
+                       super(bc, RepositoryContext.class, null);
+               }
+
+               @Override
+               public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
+                       RepositoryContext repoContext = bc.getService(reference);
+                       String cn = (String) reference.getProperty(CmsConstants.CN);
+                       if (cn != null) {
+                               List<String> publishAsLocalRepo = new ArrayList<>();
+                               if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
+//                                     JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
+                                       prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
+                                       // TODO separate home repository
+                                       prepareHomeRepository(repoContext.getRepository());
+                                       registerRepositoryServlets(cn, repoContext.getRepository());
+                                       nodeAvailable = true;
+//                                     checkReadiness();
+                               } else {
+                                       prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
+                               }
+                               // Publish all at once, so that bundles with multiple CNDs are consistent
+                               for (String dataModelName : publishAsLocalRepo)
+                                       publishLocalRepo(dataModelName, repoContext.getRepository());
+                       }
+                       return repoContext;
+               }
+
+               @Override
+               public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
+               @Override
+               public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..0099b3b
--- /dev/null
@@ -0,0 +1,133 @@
+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
new file mode 100644 (file)
index 0000000..e7f5a55
--- /dev/null
@@ -0,0 +1,18 @@
+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
new file mode 100644 (file)
index 0000000..69b98dc
--- /dev/null
@@ -0,0 +1,342 @@
+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
new file mode 100644 (file)
index 0000000..f2196bd
--- /dev/null
@@ -0,0 +1,190 @@
+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
new file mode 100644 (file)
index 0000000..2980250
--- /dev/null
@@ -0,0 +1,265 @@
+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
new file mode 100644 (file)
index 0000000..bad9fdf
--- /dev/null
@@ -0,0 +1,71 @@
+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
new file mode 100644 (file)
index 0000000..17625f5
--- /dev/null
@@ -0,0 +1,397 @@
+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
new file mode 100644 (file)
index 0000000..342c1ad
--- /dev/null
@@ -0,0 +1,191 @@
+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
new file mode 100644 (file)
index 0000000..93f29fb
--- /dev/null
@@ -0,0 +1,53 @@
+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
new file mode 100644 (file)
index 0000000..edfe87a
--- /dev/null
@@ -0,0 +1,262 @@
+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
new file mode 100644 (file)
index 0000000..0bac94c
--- /dev/null
@@ -0,0 +1,23 @@
+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
new file mode 100644 (file)
index 0000000..9cd1f72
--- /dev/null
@@ -0,0 +1,19 @@
+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
new file mode 100644 (file)
index 0000000..e05a002
--- /dev/null
@@ -0,0 +1,143 @@
+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
new file mode 100644 (file)
index 0000000..5a2cd5b
--- /dev/null
@@ -0,0 +1,123 @@
+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
new file mode 100644 (file)
index 0000000..57860d8
--- /dev/null
@@ -0,0 +1,91 @@
+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
new file mode 100644 (file)
index 0000000..fa3f87f
--- /dev/null
@@ -0,0 +1,44 @@
+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
new file mode 100644 (file)
index 0000000..0f27fd0
--- /dev/null
@@ -0,0 +1,174 @@
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.io.Serializable;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * Implements an open session in view patter: a new JCR session is created for
+ * each request
+ */
+public class CmsSessionProvider implements SessionProvider, Serializable {
+       private static final long serialVersionUID = -1358136599534938466L;
+
+       private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
+
+       private final String alias;
+
+       private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
+
+       public CmsSessionProvider(String alias) {
+               this.alias = alias;
+       }
+
+       public Session getSession(HttpServletRequest request, Repository rep, String workspace)
+                       throws javax.jcr.LoginException, ServletException, RepositoryException {
+
+               // a client is scanning parent URLs.
+//             if (workspace == null)
+//                     return null;
+
+//             CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
+               // FIXME retrieve CMS session
+               CmsSession cmsSession = null;
+               if (log.isTraceEnabled()) {
+                       log.trace("Get JCR session from " + cmsSession);
+               }
+               if (cmsSession == null)
+                       throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
+               CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
+               Session session = cmsDataSession.getDataSession(alias, workspace, rep);
+               cmsSessions.put(session, cmsDataSession);
+               return session;
+       }
+
+       public void releaseSession(Session session) {
+//             JcrUtils.logoutQuietly(session);
+               if (cmsSessions.containsKey(session)) {
+                       CmsDataSession cmsDataSession = cmsSessions.get(session);
+                       cmsDataSession.releaseDataSession(alias, session);
+               } else {
+                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       static class CmsDataSession {
+               private CmsSession cmsSession;
+
+               private Map<String, Session> dataSessions = new HashMap<>();
+               private Set<String> dataSessionsInUse = new HashSet<>();
+               private Set<Session> additionalDataSessions = new HashSet<>();
+
+               private CmsDataSession(CmsSession cmsSession) {
+                       this.cmsSession = cmsSession;
+               }
+
+               public Session newDataSession(String cn, String workspace, Repository repository) {
+                       checkValid();
+                       return login(repository, workspace);
+               }
+
+               public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
+                       checkValid();
+                       // FIXME make it more robust
+                       if (workspace == null)
+                               workspace = CmsConstants.SYS_WORKSPACE;
+                       String path = cn + '/' + workspace;
+                       if (dataSessionsInUse.contains(path)) {
+                               try {
+                                       wait(1000);
+                                       if (dataSessionsInUse.contains(path)) {
+                                               Session session = login(repository, workspace);
+                                               additionalDataSessions.add(session);
+                                               if (log.isTraceEnabled())
+                                                       log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
+                                               return session;
+                                       }
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+
+                       Session session = null;
+                       if (dataSessions.containsKey(path)) {
+                               session = dataSessions.get(path);
+                       } else {
+                               session = login(repository, workspace);
+                               dataSessions.put(path, session);
+                               if (log.isTraceEnabled())
+                                       log.trace("New data session " + path + " for " + cmsSession.getUserDn());
+                       }
+                       dataSessionsInUse.add(path);
+                       return session;
+               }
+
+               private Session login(Repository repository, String workspace) {
+                       try {
+                               return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
+                                       @Override
+                                       public Session run() throws Exception {
+                                               return repository.login(workspace);
+                                       }
+                               });
+                       } catch (PrivilegedActionException e) {
+                               throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
+                       }
+               }
+
+               public synchronized void releaseDataSession(String cn, Session session) {
+                       if (additionalDataSessions.contains(session)) {
+                               JcrUtils.logoutQuietly(session);
+                               additionalDataSessions.remove(session);
+                               if (log.isTraceEnabled())
+                                       log.trace("Remove additional data session " + session);
+                               return;
+                       }
+                       String path = cn + '/' + session.getWorkspace().getName();
+                       if (!dataSessionsInUse.contains(path))
+                               log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
+                       dataSessionsInUse.remove(path);
+                       Session registeredSession = dataSessions.get(path);
+                       if (session != registeredSession)
+                               log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
+                       if (log.isTraceEnabled())
+                               log.trace("Released data session " + session + " for " + path);
+                       notifyAll();
+               }
+
+               private void checkValid() {
+                       if (!cmsSession.isValid())
+                               throw new IllegalStateException(
+                                               "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
+               }
+
+               private void close() {
+                       // FIXME class this when CMS session is closed
+                       synchronized (this) {
+                               // TODO check data session in use ?
+                               for (String path : dataSessions.keySet())
+                                       JcrUtils.logoutQuietly(dataSessions.get(path));
+                               for (Session session : additionalDataSessions)
+                                       JcrUtils.logoutQuietly(session);
+                       }
+               }
+       }
+}
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
new file mode 100644 (file)
index 0000000..0f0858f
--- /dev/null
@@ -0,0 +1,37 @@
+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
new file mode 100644 (file)
index 0000000..2f60e97
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..11e903d
--- /dev/null
@@ -0,0 +1,73 @@
+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
new file mode 100644 (file)
index 0000000..21046f3
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..62cdc5f
--- /dev/null
@@ -0,0 +1,259 @@
+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
new file mode 100644 (file)
index 0000000..59f22cd
--- /dev/null
@@ -0,0 +1,5 @@
+<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
new file mode 100644 (file)
index 0000000..4363898
--- /dev/null
@@ -0,0 +1,207 @@
+<?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
new file mode 100644 (file)
index 0000000..a2306c6
--- /dev/null
@@ -0,0 +1 @@
+<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
new file mode 100644 (file)
index 0000000..d8a26b6
--- /dev/null
@@ -0,0 +1,9 @@
+<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
new file mode 100644 (file)
index 0000000..ccd543f
--- /dev/null
@@ -0,0 +1,23 @@
+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
new file mode 100644 (file)
index 0000000..d1d9b58
--- /dev/null
@@ -0,0 +1,170 @@
+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
new file mode 100644 (file)
index 0000000..cc3e0d7
--- /dev/null
@@ -0,0 +1,82 @@
+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
new file mode 100644 (file)
index 0000000..506a6ac
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..7396c87
--- /dev/null
@@ -0,0 +1,48 @@
+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
new file mode 100644 (file)
index 0000000..8c267e3
--- /dev/null
@@ -0,0 +1,172 @@
+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
new file mode 100644 (file)
index 0000000..77ad527
--- /dev/null
@@ -0,0 +1,26 @@
+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
new file mode 100644 (file)
index 0000000..0f9db87
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.jackrabbit.client;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+
+/**
+ * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
+ * {@link HttpClientContext}.
+ */
+public class ClientDavexRepositoryService extends RepositoryServiceImpl {
+
+       public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
+                       throws RepositoryException {
+               super(jcrServerURI, batchReadConfig);
+       }
+
+       public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+                       BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
+                       throws RepositoryException {
+               super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
+       }
+
+       public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+                       BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
+               super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
+       }
+
+       @Override
+       protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+               HttpClientContext result = HttpClientContext.create();
+               result.setAuthCache(new NonSerialBasicAuthCache());
+               return result;
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..4b240f0
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+
+/**
+ * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
+ * {@link ClientDavexRepositoryService}.
+ */
+public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
+       @Override
+       public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
+               // retrieve the repository uri
+               String uri;
+               if (parameters == null) {
+                       uri = System.getProperty(PARAM_REPOSITORY_URI);
+               } else {
+                       Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
+                       uri = (repoUri == null) ? null : repoUri.toString();
+               }
+               if (uri == null) {
+                       uri = DEFAULT_REPOSITORY_URI;
+               }
+
+               // load other optional configuration parameters
+               BatchReadConfig brc = null;
+               int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
+               int maximumHttpConnections = 0;
+
+               // since JCR-4120 the default workspace name is no longer set to 'default'
+               // note: if running with JCR Server < 1.5 a default workspace name must
+               // therefore be configured
+               String workspaceNameDefault = null;
+
+               if (parameters != null) {
+                       // batchRead config
+                       Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
+                       if (param != null && param instanceof BatchReadConfig) {
+                               brc = (BatchReadConfig) param;
+                       }
+
+                       // itemCache size config
+                       param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
+                       if (param != null) {
+                               try {
+                                       itemInfoCacheSize = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // ignore, use default
+                               }
+                       }
+
+                       // max connections config
+                       param = parameters.get(PARAM_MAX_CONNECTIONS);
+                       if (param != null) {
+                               try {
+                                       maximumHttpConnections = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // using default
+                               }
+                       }
+
+                       param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
+                       if (param != null) {
+                               workspaceNameDefault = param.toString();
+                       }
+               }
+
+               if (maximumHttpConnections > 0) {
+                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
+                                       maximumHttpConnections);
+               } else {
+                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
+               }
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..e08f4d6
--- /dev/null
@@ -0,0 +1,125 @@
+package org.argeo.jackrabbit.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+import org.argeo.jcr.JcrUtils;
+
+/** Minimal client to test JCR DAVEX connectivity. */
+public class JackrabbitClient {
+       final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+       final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
+       final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+
+       public static void main(String[] args) {
+               String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
+               String workspace = args.length < 2 ? "home" : args[1];
+
+               Repository repository = null;
+               Session session = null;
+
+               URI uri;
+               try {
+                       uri = new URI(repoUri);
+               } catch (URISyntaxException e1) {
+                       throw new IllegalArgumentException(e1);
+               }
+
+               if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
+
+                       RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
+                               @SuppressWarnings("rawtypes")
+                               public Repository getRepository(Map parameters) throws RepositoryException {
+                                       RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
+
+                                               @Override
+                                               public RepositoryService createRepositoryService(Map<?, ?> parameters)
+                                                               throws RepositoryException {
+                                                       Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
+                                                       Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+                                                       BatchReadConfig brc = null;
+                                                       return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
+                                                                       ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
+
+                                                               @Override
+                                                               protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+                                                                       HttpClientContext result = HttpClientContext.create();
+                                                                       result.setAuthCache(new NonSerialBasicAuthCache());
+                                                                       return result;
+                                                               }
+
+                                                       };
+                                               }
+                                       };
+                                       return RepositoryImpl.create(
+                                                       new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+                               }
+                       };
+                       Map<String, String> params = new HashMap<String, String>();
+                       params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
+                       // FIXME make it configurable
+                       params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
+
+                       try {
+                               repository = repositoryFactory.getRepository(params);
+                               if (repository != null)
+                                       session = repository.login(workspace);
+                               else
+                                       throw new IllegalArgumentException("Repository " + repoUri + " not found");
+                       } catch (RepositoryException e) {
+                               e.printStackTrace();
+                       }
+
+               } else {
+                       Path path = Paths.get(uri.getPath());
+               }
+
+               try {
+                       Node rootNode = session.getRootNode();
+                       NodeIterator nit = rootNode.getNodes();
+                       while (nit.hasNext()) {
+                               System.out.println(nit.nextNode().getPath());
+                       }
+
+                       Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
+                       System.out.println("Created folder " + newNode.getPath());
+                       Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
+                       System.out.println("Created file " + newFile.getPath());
+                       try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
+                               System.out.println("Read " + reader.readLine());
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       }
+                       newNode.getParent().remove();
+                       System.out.println("Removed new nodes");
+               } catch (RepositoryException e) {
+                       e.printStackTrace();
+               }
+       }
+}
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
new file mode 100644 (file)
index 0000000..3fb0db9
--- /dev/null
@@ -0,0 +1,41 @@
+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
new file mode 100644 (file)
index 0000000..a2eb983
--- /dev/null
@@ -0,0 +1,7 @@
+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
new file mode 100644 (file)
index 0000000..1cae6e4
--- /dev/null
@@ -0,0 +1,149 @@
+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
new file mode 100644 (file)
index 0000000..e3a70d0
--- /dev/null
@@ -0,0 +1,87 @@
+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
new file mode 100644 (file)
index 0000000..f2541fb
--- /dev/null
@@ -0,0 +1,57 @@
+<?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
new file mode 100644 (file)
index 0000000..c9ec2c3
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..17497d6
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..0526762
--- /dev/null
@@ -0,0 +1,82 @@
+<?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
new file mode 100644 (file)
index 0000000..3d24708
--- /dev/null
@@ -0,0 +1,60 @@
+<?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
new file mode 100644 (file)
index 0000000..ecee5bd
--- /dev/null
@@ -0,0 +1,55 @@
+<?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
new file mode 100644 (file)
index 0000000..07a0d04
--- /dev/null
@@ -0,0 +1,82 @@
+<?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
new file mode 100644 (file)
index 0000000..9677828
--- /dev/null
@@ -0,0 +1,79 @@
+<?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
new file mode 100644 (file)
index 0000000..f98cf99
--- /dev/null
@@ -0,0 +1,79 @@
+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
new file mode 100644 (file)
index 0000000..f3a282c
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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/jackrabbit/unit/AbstractJackrabbitTestCase.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java
new file mode 100644 (file)
index 0000000..f65432e
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.jackrabbit.unit;
+
+import java.net.URL;
+
+import javax.jcr.Repository;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.argeo.jcr.unit.AbstractJcrTestCase;
+
+/** Factorizes configuration of an in memory transient repository */
+public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
+       protected RepositoryImpl repositoryImpl;
+
+       // protected File getRepositoryFile() throws Exception {
+       // Resource res = new ClassPathResource(
+       // "org/argeo/jackrabbit/unit/repository-memory.xml");
+       // return res.getFile();
+       // }
+
+       public AbstractJackrabbitTestCase() {
+               URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
+               assert url != null;
+               System.setProperty("java.security.auth.login.config", url.toString());
+       }
+
+       protected Repository createRepository() throws Exception {
+               // Repository repository = new TransientRepository(getRepositoryFile(),
+               // getHomeDir());
+               RepositoryConfig repositoryConfig = RepositoryConfig.create(
+                               AbstractJackrabbitTestCase.class
+                                               .getResourceAsStream(getRepositoryConfigResource()),
+                               getHomeDir().getAbsolutePath());
+               RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
+               return repositoryImpl;
+       }
+
+       protected String getRepositoryConfigResource() {
+               return "repository-memory.xml";
+       }
+
+       @Override
+       protected void clearRepository(Repository repository) throws Exception {
+               RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
+               if (repositoryImpl != null)
+                       repositoryImpl.shutdown();
+               FileUtils.deleteDirectory(getHomeDir());
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config
new file mode 100644 (file)
index 0000000..0313f91
--- /dev/null
@@ -0,0 +1,7 @@
+TEST_JACKRABBIT_ADMIN {
+   org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java
new file mode 100644 (file)
index 0000000..3b6143b
--- /dev/null
@@ -0,0 +1,2 @@
+/** Helpers for unit tests with Jackrabbit repositories. */
+package org.argeo.jackrabbit.unit;
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml
new file mode 100644 (file)
index 0000000..348dc28
--- /dev/null
@@ -0,0 +1,81 @@
+<?xml version="1.0"?>
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- Shared datasource -->
+       <DataSources>
+               <DataSource name="dataSource">
+                       <param name="driver" value="org.h2.Driver" />
+                       <param name="url" value="jdbc:h2:mem:jackrabbit" />
+                       <param name="user" value="sa" />
+                       <param name="password" value="" />
+                       <param name="databaseType" value="h2" />
+                       <param name="maxPoolSize" value="10" />
+               </DataSource>
+       </DataSources>
+
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+               <param name="dataSourceName" value="dataSource" />
+               <param name="schema" value="default" />
+               <param name="schemaObjectPrefix" value="fs_" />
+       </FileSystem>
+       <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+               <param name="dataSourceName" value="dataSource" />
+               <param name="schemaObjectPrefix" value="ds_" />
+       </DataStore>
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="dev" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schema" value="default" />
+                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${wsp.home}/index" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schema" value="default" />
+                       <param name="schemaObjectPrefix" value="fs_ver_" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schemaObjectPrefix" value="pm_ver_" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="extractorPoolSize" value="2" />
+               <param name="supportHighlighting" value="true" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+                       workspaceName="security" />
+               <AccessManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+               <LoginModule
+                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+                       <param name="anonymousId" value="anonymous" />
+                       <param name="adminId" value="admin" />
+               </LoginModule>
+       </Security>
+</Repository>
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml
new file mode 100644 (file)
index 0000000..8395424
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2007-2012 Argeo GmbH
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="main" configRootPath="/workspaces" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${rep.home}/repository/index" />
+                       <param name="directoryManagerClass"
+                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="directoryManagerClass"
+                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+                       workspaceName="security" />
+               <AccessManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+               <LoginModule
+                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+                       <param name="anonymousId" value="anonymous" />
+                       <param name="adminId" value="admin" />
+               </LoginModule>
+       </Security>
+</Repository>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..0418810
--- /dev/null
@@ -0,0 +1,60 @@
+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
new file mode 100644 (file)
index 0000000..b4124ee
--- /dev/null
@@ -0,0 +1,61 @@
+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
new file mode 100644 (file)
index 0000000..d873ef6
--- /dev/null
@@ -0,0 +1,77 @@
+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
new file mode 100644 (file)
index 0000000..bf5de12
--- /dev/null
@@ -0,0 +1,985 @@
+package org.argeo.jcr;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Utility class whose purpose is to make using JCR less verbose by
+ * systematically using unchecked exceptions and returning <code>null</code>
+ * when something is not found. This is especially useful when writing user
+ * interfaces (such as with SWT) where listeners and callbacks expect unchecked
+ * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
+ */
+public class Jcr {
+       /**
+        * The name of a node which will be serialized as XML text, as per section 7.3.1
+        * of the JCR 2.0 specifications.
+        */
+       public final static String JCR_XMLTEXT = "jcr:xmltext";
+       /**
+        * The name of a property which will be serialized as XML text, as per section
+        * 7.3.1 of the JCR 2.0 specifications.
+        */
+       public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
+       /**
+        * <code>jcr:name</code>, when used in another context than
+        * {@link Property#JCR_NAME}, typically to name a node rather than a property.
+        */
+       public final static String JCR_NAME = "jcr:name";
+       /**
+        * <code>jcr:path</code>, when used in another context than
+        * {@link Property#JCR_PATH}, typically to name a node rather than a property.
+        */
+       public final static String JCR_PATH = "jcr:path";
+       /**
+        * <code>jcr:primaryType</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_PRIMARY_TYPE}.
+        */
+       public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
+       /**
+        * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_MIXIN_TYPES}.
+        */
+       public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
+       /**
+        * <code>jcr:uuid</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_UUID}.
+        */
+       public final static String JCR_UUID = "jcr:uuid";
+       /**
+        * <code>jcr:created</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_CREATED}.
+        */
+       public final static String JCR_CREATED = "jcr:created";
+       /**
+        * <code>jcr:createdBy</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_CREATED_BY}.
+        */
+       public final static String JCR_CREATED_BY = "jcr:createdBy";
+       /**
+        * <code>jcr:lastModified</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_LAST_MODIFIED}.
+        */
+       public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+       /**
+        * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_LAST_MODIFIED_BY}.
+        */
+       public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+
+       /**
+        * @see Node#isNodeType(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean isNodeType(Node node, String nodeTypeName) {
+               try {
+                       return node.isNodeType(nodeTypeName);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
+               }
+       }
+
+       /**
+        * @see Node#hasNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean hasNodes(Node node) {
+               try {
+                       return node.hasNodes();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get whether " + node + " has children.", e);
+               }
+       }
+
+       /**
+        * @see Node#getParent()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getParent(Node node) {
+               try {
+                       return isRoot(node) ? null : node.getParent();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get parent of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getParent()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getParentPath(Node node) {
+               return getPath(getParent(node));
+       }
+
+       /**
+        * Whether this node is the root node.
+        * 
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean isRoot(Node node) {
+               try {
+                       return node.getDepth() == 0;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get depth of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getPath()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getPath(Node node) {
+               try {
+                       return node.getPath();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get path of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getSession()
+        * @see Session#getWorkspace()
+        * @see Workspace#getName()
+        */
+       public static String getWorkspaceName(Node node) {
+               return session(node).getWorkspace().getName();
+       }
+
+       /**
+        * @see Node#getIdentifier()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getIdentifier(Node node) {
+               try {
+                       return node.getIdentifier();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get identifier of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getName()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getName(Node node) {
+               try {
+                       return node.getName();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get name of " + node, e);
+               }
+       }
+
+       /**
+        * Returns the node name with its current index (useful for re-ordering).
+        * 
+        * @see Node#getName()
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getIndexedName(Node node) {
+               try {
+                       return node.getName() + "[" + node.getIndex() + "]";
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get name of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getProperty(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Property getProperty(Node node, String property) {
+               try {
+                       if (node.hasProperty(property))
+                               return node.getProperty(property);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get property " + property + " of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static int getIndex(Node node) {
+               try {
+                       return node.getIndex();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get index of " + node, e);
+               }
+       }
+
+       /**
+        * If node has mixin {@link NodeType#MIX_TITLE}, return
+        * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
+        */
+       public static String getTitle(Node node) {
+               if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
+                       return get(node, Property.JCR_TITLE);
+               else
+                       return Jcr.getName(node);
+       }
+
+       /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
+       @SuppressWarnings("unchecked")
+       public static Iterable<Node> iterate(NodeIterator nodeIterator) {
+               return new Iterable<Node>() {
+
+                       @Override
+                       public Iterator<Node> iterator() {
+                               return nodeIterator;
+                       }
+               };
+       }
+
+       /**
+        * @return the children as an {@link Iterable} for use in for-each llops.
+        * @see Node#getNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Iterable<Node> nodes(Node node) {
+               try {
+                       return iterate(node.getNodes());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get children of " + node, e);
+               }
+       }
+
+       /**
+        * @return the children as a (possibly empty) {@link List}.
+        * @see Node#getNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static List<Node> getNodes(Node node) {
+               List<Node> nodes = new ArrayList<>();
+               try {
+                       if (node.hasNodes()) {
+                               NodeIterator nit = node.getNodes();
+                               while (nit.hasNext())
+                                       nodes.add(nit.nextNode());
+                               return nodes;
+                       } else
+                               return nodes;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get children of " + node, e);
+               }
+       }
+
+       /**
+        * @return the child or <code>null</node> if not found
+        * @see Node#getNode(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNode(Node node, String child) {
+               try {
+                       if (node.hasNode(child))
+                               return node.getNode(child);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get child of " + node, e);
+               }
+       }
+
+       /**
+        * @return the node at this path or <code>null</node> if not found
+        * @see Session#getNode(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNode(Session session, String path) {
+               try {
+                       if (session.nodeExists(path))
+                               return session.getNode(path);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get node " + path, e);
+               }
+       }
+
+       /**
+        * Add a node to this parent, setting its primary type and its mixins.
+        * 
+        * @param parent      the parent node
+        * @param name        the name of the node, if <code>null</code>, the primary
+        *                    type will be used (typically for XML structures)
+        * @param primaryType the primary type, if <code>null</code>
+        *                    {@link NodeType#NT_UNSTRUCTURED} will be used.
+        * @param mixins      the mixins
+        * @return the created node
+        * @see Node#addNode(String, String)
+        * @see Node#addMixin(String)
+        */
+       public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
+               if (name == null && primaryType == null)
+                       throw new IllegalArgumentException("Both node name and primary type cannot be null");
+               try {
+                       Node newNode = parent.addNode(name == null ? primaryType : name,
+                                       primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
+                       for (String mixin : mixins) {
+                               newNode.addMixin(mixin);
+                       }
+                       return newNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
+               }
+       }
+
+       /**
+        * Add an {@link NodeType#NT_BASE} node to this parent.
+        * 
+        * @param parent the parent node
+        * @param name   the name of the node, cannot be <code>null</code>
+        * @return the created node
+        * 
+        * @see Node#addNode(String)
+        */
+       public static Node addNode(Node parent, String name) {
+               if (name == null)
+                       throw new IllegalArgumentException("Node name cannot be null");
+               try {
+                       Node newNode = parent.addNode(name);
+                       return newNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
+               }
+       }
+
+       /**
+        * Add mixins to a node.
+        * 
+        * @param node   the node
+        * @param mixins the mixins
+        * @return the created node
+        * @see Node#addMixin(String)
+        */
+       public static void addMixin(Node node, String... mixins) {
+               try {
+                       for (String mixin : mixins) {
+                               node.addMixin(mixin);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
+               }
+       }
+
+       /**
+        * Removes this node.
+        * 
+        * @see Node#remove()
+        */
+       public static void remove(Node node) {
+               try {
+                       node.remove();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot remove node " + node, e);
+               }
+       }
+
+       /**
+        * @return the node with htis id or <code>null</node> if not found
+        * @see Session#getNodeByIdentifier(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNodeById(Session session, String id) {
+               try {
+                       return session.getNodeByIdentifier(id);
+               } catch (ItemNotFoundException e) {
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get node with id " + id, e);
+               }
+       }
+
+       /**
+        * Set a property to the given value, or remove it if the value is
+        * <code>null</code>.
+        * 
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static void set(Node node, String property, Object value) {
+               try {
+                       if (!node.hasProperty(property)) {
+                               if (value != null) {
+                                       if (value instanceof List) {// multiple
+                                               List<?> lst = (List<?>) value;
+                                               String[] values = new String[lst.size()];
+                                               for (int i = 0; i < lst.size(); i++) {
+                                                       values[i] = lst.get(i).toString();
+                                               }
+                                               node.setProperty(property, values);
+                                       } else {
+                                               node.setProperty(property, value.toString());
+                                       }
+                               }
+                               return;
+                       }
+                       Property prop = node.getProperty(property);
+                       if (value == null) {
+                               prop.remove();
+                               return;
+                       }
+
+                       // multiple
+                       if (value instanceof List) {
+                               List<?> lst = (List<?>) value;
+                               String[] values = new String[lst.size()];
+                               // TODO better cast?
+                               for (int i = 0; i < lst.size(); i++) {
+                                       values[i] = lst.get(i).toString();
+                               }
+                               if (!prop.isMultiple())
+                                       prop.remove();
+                               node.setProperty(property, values);
+                               return;
+                       }
+
+                       // single
+                       if (prop.isMultiple()) {
+                               prop.remove();
+                               node.setProperty(property, value.toString());
+                               return;
+                       }
+
+                       if (value instanceof String)
+                               prop.setValue((String) value);
+                       else if (value instanceof Long)
+                               prop.setValue((Long) value);
+                       else if (value instanceof Integer)
+                               prop.setValue(((Integer) value).longValue());
+                       else if (value instanceof Double)
+                               prop.setValue((Double) value);
+                       else if (value instanceof Float)
+                               prop.setValue(((Float) value).doubleValue());
+                       else if (value instanceof Calendar)
+                               prop.setValue((Calendar) value);
+                       else if (value instanceof BigDecimal)
+                               prop.setValue((BigDecimal) value);
+                       else if (value instanceof Boolean)
+                               prop.setValue((Boolean) value);
+                       else if (value instanceof byte[])
+                               JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
+                       else if (value instanceof Instant) {
+                               Instant instant = (Instant) value;
+                               GregorianCalendar calendar = new GregorianCalendar();
+                               calendar.setTime(Date.from(instant));
+                               prop.setValue(calendar);
+                       } else // try with toString()
+                               prop.setValue(value.toString());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
+               }
+       }
+
+       /**
+        * Get property as {@link String}.
+        * 
+        * @return the value of
+        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
+        *         <code>null</code> if the property does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String get(Node node, String property) {
+               return get(node, property, null);
+       }
+
+       /**
+        * Get property as a {@link String}. If the property is multiple it returns the
+        * first value.
+        * 
+        * @return the value of
+        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
+        *         <code>defaultValue</code> if the property does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String get(Node node, String property, String defaultValue) {
+               try {
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               if (!p.isMultiple())
+                                       return p.getString();
+                               else {
+                                       Value[] values = p.getValues();
+                                       if (values.length == 0)
+                                               return defaultValue;
+                                       else
+                                               return values[0].getString();
+                               }
+                       } else
+                               return defaultValue;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get property as a {@link Value}.
+        * 
+        * @return {@link Node#getProperty(String)} or <code>null</code> if the property
+        *         does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Value getValue(Node node, String property) {
+               try {
+                       if (node.hasProperty(property))
+                               return node.getProperty(property).getValue();
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get property doing a best effort to cast it as the target object.
+        * 
+        * @return the value of {@link Node#getProperty(String)} or
+        *         <code>defaultValue</code> if the property does not exist.
+        * @throws IllegalArgumentException if the value could not be cast
+        * @throws JcrException             in case of unexpected
+        *                                  {@link RepositoryException}
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> T getAs(Node node, String property, T defaultValue) {
+               try {
+                       // TODO deal with multiple
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               try {
+                                       if (p.isMultiple()) {
+                                               throw new UnsupportedOperationException("Multiple values properties are not supported");
+                                       }
+                                       Value value = p.getValue();
+                                       return (T) get(value);
+                               } catch (ClassCastException e) {
+                                       throw new IllegalArgumentException(
+                                                       "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
+                               }
+                       } else {
+                               return defaultValue;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+       
+       public static <T> T getAs(Node node, String property, Class<T> clss) {
+               if(String.class.isAssignableFrom(clss)) {
+                       return (T)get(node,property);
+               }       else    if(Long.class.isAssignableFrom(clss)) {
+                       return (T)get(node,property);
+               }else {
+                       throw new IllegalArgumentException("Unsupported format "+clss);
+               }
+       }
+
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        * 
+        * @return the value of {@link Node#getProperty(String)}.
+        * @throws IllegalArgumentException if the value could not be cast
+        * @throws JcrException             in case of unexpected
+        *                                  {@link RepositoryException}
+        */
+       public static <T> List<T> getMultiple(Node node, String property) {
+               try {
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               return getMultiple(p);
+                       } else {
+                               return null;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> List<T> getMultiple(Property p) {
+               try {
+                       List<T> res = new ArrayList<>();
+                       if (!p.isMultiple()) {
+                               res.add((T) get(p.getValue()));
+                               return res;
+                       }
+                       Value[] values = p.getValues();
+                       for (Value value : values) {
+                               res.add((T) get(value));
+                       }
+                       return res;
+               } catch (ClassCastException | RepositoryException e) {
+                       throw new IllegalArgumentException("Cannot get property " + p, e);
+               }
+       }
+
+       /** Cast a {@link Value} to a standard Java object. */
+       public static Object get(Value value) {
+               Binary binary = null;
+               try {
+                       switch (value.getType()) {
+                       case PropertyType.STRING:
+                               return value.getString();
+                       case PropertyType.DOUBLE:
+                               return (Double) value.getDouble();
+                       case PropertyType.LONG:
+                               return (Long) value.getLong();
+                       case PropertyType.BOOLEAN:
+                               return (Boolean) value.getBoolean();
+                       case PropertyType.DATE:
+                               return value.getDate();
+                       case PropertyType.BINARY:
+                               binary = value.getBinary();
+                               byte[] arr = null;
+                               try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                                       IOUtils.copy(in, out);
+                                       arr = out.toByteArray();
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot read binary from " + value, e);
+                               }
+                               return arr;
+                       default:
+                               return value.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot cast value from " + value, e);
+               } finally {
+                       if (binary != null)
+                               binary.dispose();
+               }
+       }
+
+       /**
+        * Retrieves the {@link Session} related to this node.
+        * 
+        * @deprecated Use {@link #getSession(Node)} instead.
+        */
+       @Deprecated
+       public static Session session(Node node) {
+               return getSession(node);
+       }
+
+       /** Retrieves the {@link Session} related to this node. */
+       public static Session getSession(Node node) {
+               try {
+                       return node.getSession();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve session related to " + node, e);
+               }
+       }
+
+       /** Retrieves the root node related to this session. */
+       public static Node getRootNode(Session session) {
+               try {
+                       return session.getRootNode();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get root node for " + session, e);
+               }
+       }
+
+       /** Whether this item exists. */
+       public static boolean itemExists(Session session, String path) {
+               try {
+                       return session.itemExists(path);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check whether " + path + " exists", e);
+               }
+       }
+
+       /**
+        * Saves the {@link Session} related to this node. Note that all other unrelated
+        * modifications in this session will also be saved.
+        */
+       public static void save(Node node) {
+               try {
+                       Session session = node.getSession();
+//                     if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+//                             set(node, Property.JCR_LAST_MODIFIED, Instant.now());
+//                             set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
+//                     }
+                       if (session.hasPendingChanges())
+                               session.save();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot save session related to " + node + " in workspace "
+                                       + session(node).getWorkspace().getName(), e);
+               }
+       }
+
+       /** Login to a JCR repository. */
+       public static Session login(Repository repository, String workspace) {
+               try {
+                       return repository.login(workspace);
+               } catch (RepositoryException e) {
+                       throw new IllegalArgumentException("Cannot login to repository", e);
+               }
+       }
+
+       /** Safely and silently logs out a session. */
+       public static void logout(Session session) {
+               try {
+                       if (session != null)
+                               if (session.isLive())
+                                       session.logout();
+               } catch (Exception e) {
+                       // silent
+               }
+       }
+
+       /** Safely and silently logs out the underlying session. */
+       public static void logout(Node node) {
+               Jcr.logout(session(node));
+       }
+
+       /*
+        * SECURITY
+        */
+       /**
+        * Add a single privilege to a node.
+        * 
+        * @see Privilege
+        */
+       public static void addPrivilege(Node node, String principal, String privilege) {
+               try {
+                       Session session = node.getSession();
+                       JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
+               }
+       }
+
+       /*
+        * VERSIONING
+        */
+       /** Get checked out status. */
+       public static boolean isCheckedOut(Node node) {
+               try {
+                       return node.isCheckedOut();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve checked out status of " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkpoint(String) */
+       public static void checkpoint(Node node) {
+               try {
+                       versionManager(node).checkpoint(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check in " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkin(String) */
+       public static void checkin(Node node) {
+               try {
+                       versionManager(node).checkin(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check in " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkout(String) */
+       public static void checkout(Node node) {
+               try {
+                       versionManager(node).checkout(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check out " + node, e);
+               }
+       }
+
+       /** Get the {@link VersionManager} related to this node. */
+       public static VersionManager versionManager(Node node) {
+               try {
+                       return node.getSession().getWorkspace().getVersionManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get version manager from " + node, e);
+               }
+       }
+
+       /** Get the {@link VersionHistory} related to this node. */
+       public static VersionHistory getVersionHistory(Node node) {
+               try {
+                       return versionManager(node).getVersionHistory(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get version history from " + node, e);
+               }
+       }
+
+       /**
+        * The linear versions of this version history in reverse order and without the
+        * root version.
+        */
+       public static List<Version> getLinearVersions(VersionHistory versionHistory) {
+               try {
+                       List<Version> lst = new ArrayList<>();
+                       VersionIterator vit = versionHistory.getAllLinearVersions();
+                       while (vit.hasNext())
+                               lst.add(vit.nextVersion());
+                       lst.remove(0);
+                       Collections.reverse(lst);
+                       return lst;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get linear versions from " + versionHistory, e);
+               }
+       }
+
+       /** The frozen node related to this {@link Version}. */
+       public static Node getFrozenNode(Version version) {
+               try {
+                       return version.getFrozenNode();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get frozen node from " + version, e);
+               }
+       }
+
+       /** Get the base {@link Version} related to this node. */
+       public static Version getBaseVersion(Node node) {
+               try {
+                       return versionManager(node).getBaseVersion(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get base version from " + node, e);
+               }
+       }
+
+       /*
+        * FILES
+        */
+       /**
+        * Returns the size of this file.
+        * 
+        * @see NodeType#NT_FILE
+        */
+       public static long getFileSize(Node fileNode) {
+               try {
+                       if (!fileNode.isNodeType(NodeType.NT_FILE))
+                               throw new IllegalArgumentException(fileNode + " must be a file.");
+                       return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get file size of " + fileNode, e);
+               }
+       }
+
+       /** Returns the size of this {@link Binary}. */
+       public static long getBinarySize(Binary binaryArg) {
+               try {
+                       try (Bin binary = new Bin(binaryArg)) {
+                               return binary.getSize();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get file size of binary " + binaryArg, e);
+               }
+       }
+
+       // QUERY
+       /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
+       public static Query createQuery(QueryManager qm, String sql, Object... args) {
+               // fix single quotes
+               sql = sql.replaceAll("'", "''");
+               String query = MessageFormat.format(sql, args);
+               try {
+                       return qm.createQuery(query, Query.JCR_SQL2);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
+               }
+       }
+
+       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+       public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
+               Query query = createQuery(qm, sql, args);
+               try {
+                       return query.execute().getNodes();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
+               }
+       }
+
+       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+       public static NodeIterator executeQuery(Session session, String sql, Object... args) {
+               QueryManager queryManager;
+               try {
+                       queryManager = session.getWorkspace().getQueryManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get query manager from session " + session, e);
+               }
+               return executeQuery(queryManager, sql, args);
+       }
+
+       /**
+        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+        * single node at most.
+        * 
+        * @return the node or <code>null</code> if not found.
+        */
+       public static Node getNode(QueryManager qm, String sql, Object... args) {
+               NodeIterator nit = executeQuery(qm, sql, args);
+               if (nit.hasNext()) {
+                       Node node = nit.nextNode();
+                       if (nit.hasNext())
+                               throw new IllegalStateException(
+                                               "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
+                       return node;
+               } else {
+                       return null;
+               }
+       }
+
+       /**
+        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+        * single node at most.
+        * 
+        * @return the node or <code>null</code> if not found.
+        */
+       public static Node getNode(Session session, String sql, Object... args) {
+               QueryManager queryManager;
+               try {
+                       queryManager = session.getWorkspace().getQueryManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get query manager from session " + session, e);
+               }
+               return getNode(queryManager, sql, args);
+       }
+
+       /** Singleton. */
+       private Jcr() {
+
+       }
+}
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
new file mode 100644 (file)
index 0000000..351929f
--- /dev/null
@@ -0,0 +1,207 @@
+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
new file mode 100644 (file)
index 0000000..efbaabe
--- /dev/null
@@ -0,0 +1,15 @@
+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
new file mode 100644 (file)
index 0000000..c778743
--- /dev/null
@@ -0,0 +1,22 @@
+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
new file mode 100644 (file)
index 0000000..71cf961
--- /dev/null
@@ -0,0 +1,87 @@
+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
new file mode 100644 (file)
index 0000000..3228eee
--- /dev/null
@@ -0,0 +1,244 @@
+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
new file mode 100644 (file)
index 0000000..82a65e7
--- /dev/null
@@ -0,0 +1,70 @@
+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
new file mode 100644 (file)
index 0000000..3be8be1
--- /dev/null
@@ -0,0 +1,1778 @@
+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
new file mode 100644 (file)
index 0000000..666b259
--- /dev/null
@@ -0,0 +1,190 @@
+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
new file mode 100644 (file)
index 0000000..9dd43ad
--- /dev/null
@@ -0,0 +1,7 @@
+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
new file mode 100644 (file)
index 0000000..0cbad33
--- /dev/null
@@ -0,0 +1,17 @@
+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
new file mode 100644 (file)
index 0000000..71e76fe
--- /dev/null
@@ -0,0 +1,57 @@
+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
new file mode 100644 (file)
index 0000000..4f42f2d
--- /dev/null
@@ -0,0 +1,43 @@
+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
new file mode 100644 (file)
index 0000000..2208627
--- /dev/null
@@ -0,0 +1,279 @@
+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
new file mode 100644 (file)
index 0000000..dab5554
--- /dev/null
@@ -0,0 +1,38 @@
+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
new file mode 100644 (file)
index 0000000..d6550fe
--- /dev/null
@@ -0,0 +1,190 @@
+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
new file mode 100644 (file)
index 0000000..7c9711b
--- /dev/null
@@ -0,0 +1,138 @@
+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
new file mode 100644 (file)
index 0000000..4b32981
--- /dev/null
@@ -0,0 +1,252 @@
+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
new file mode 100644 (file)
index 0000000..74d9a19
--- /dev/null
@@ -0,0 +1,337 @@
+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
new file mode 100644 (file)
index 0000000..f214fdc
--- /dev/null
@@ -0,0 +1,15 @@
+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
new file mode 100644 (file)
index 0000000..7318b70
--- /dev/null
@@ -0,0 +1,384 @@
+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
new file mode 100644 (file)
index 0000000..eda07a5
--- /dev/null
@@ -0,0 +1,77 @@
+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
new file mode 100644 (file)
index 0000000..8054d52
--- /dev/null
@@ -0,0 +1,9 @@
+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
new file mode 100644 (file)
index 0000000..4643c8c
--- /dev/null
@@ -0,0 +1,877 @@
+/*
+ * 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
new file mode 100644 (file)
index 0000000..ce4205a
--- /dev/null
@@ -0,0 +1,192 @@
+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
new file mode 100644 (file)
index 0000000..0cdfdaf
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..3eb0e7a
--- /dev/null
@@ -0,0 +1,16 @@
+//
+// 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
new file mode 100644 (file)
index 0000000..1837749
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..0177636
--- /dev/null
@@ -0,0 +1,153 @@
+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
new file mode 100644 (file)
index 0000000..84eea1f
--- /dev/null
@@ -0,0 +1,16 @@
+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
new file mode 100644 (file)
index 0000000..a8e00df
--- /dev/null
@@ -0,0 +1,115 @@
+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
new file mode 100644 (file)
index 0000000..a578c45
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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/unit/AbstractJcrTestCase.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java
new file mode 100644 (file)
index 0000000..84e8cd3
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.jcr.unit;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.io.FileUtils;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.jcr.JcrException;
+
+import junit.framework.TestCase;
+
+/** Base for unit tests with a JCR repository. */
+public abstract class AbstractJcrTestCase extends TestCase {
+       private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class);
+
+       private Repository repository;
+       private Session session = null;
+
+       public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
+
+       // protected abstract File getRepositoryFile() throws Exception;
+
+       protected abstract Repository createRepository() throws Exception;
+
+       protected abstract void clearRepository(Repository repository) throws Exception;
+
+       @Override
+       protected void setUp() throws Exception {
+               File homeDir = getHomeDir();
+               FileUtils.deleteDirectory(homeDir);
+               repository = createRepository();
+       }
+
+       @Override
+       protected void tearDown() throws Exception {
+               if (session != null) {
+                       session.logout();
+                       if (log.isTraceEnabled())
+                               log.trace("Logout session");
+               }
+               clearRepository(repository);
+       }
+
+       protected Session session() {
+               if (session != null && session.isLive())
+                       return session;
+               Session session;
+               if (getLoginContext() != null) {
+                       LoginContext lc;
+                       try {
+                               lc = new LoginContext(getLoginContext());
+                               lc.login();
+                       } catch (LoginException e) {
+                               throw new IllegalStateException("JAAS login failed", e);
+                       }
+                       session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
+
+                               @Override
+                               public Session run() {
+                                       return login();
+                               }
+
+                       });
+               } else
+                       session = login();
+               this.session = session;
+               return this.session;
+       }
+
+       protected String getLoginContext() {
+               return null;
+       }
+
+       protected Session login() {
+               try {
+                       if (log.isTraceEnabled())
+                               log.trace("Login session");
+                       Subject subject = Subject.getSubject(AccessController.getContext());
+                       if (subject != null)
+                               return getRepository().login();
+                       else
+                               return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot login to repository", e);
+               }
+       }
+
+       protected Repository getRepository() {
+               return repository;
+       }
+
+       /**
+        * enables children class to set an existing repository in case it is not
+        * deleted on startup, to test migration by instance
+        */
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       protected File getHomeDir() {
+               File homeDir = new File(System.getProperty("java.io.tmpdir"),
+                               AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
+               return homeDir;
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java
new file mode 100644 (file)
index 0000000..c6e7415
--- /dev/null
@@ -0,0 +1,2 @@
+/** Helpers for unit tests with JCR repositories. */
+package org.argeo.jcr.unit;
\ 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
new file mode 100644 (file)
index 0000000..2adb6a9
--- /dev/null
@@ -0,0 +1,186 @@
+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
new file mode 100644 (file)
index 0000000..813d065
--- /dev/null
@@ -0,0 +1,19 @@
+<?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
new file mode 100644 (file)
index 0000000..3c8f296
--- /dev/null
@@ -0,0 +1,220 @@
+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
new file mode 100644 (file)
index 0000000..ebb8c53
--- /dev/null
@@ -0,0 +1,86 @@
+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
new file mode 100644 (file)
index 0000000..ef83c1f
--- /dev/null
@@ -0,0 +1,257 @@
+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
new file mode 100644 (file)
index 0000000..00d4be8
--- /dev/null
@@ -0,0 +1,448 @@
+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
new file mode 100644 (file)
index 0000000..122c967
--- /dev/null
@@ -0,0 +1,85 @@
+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
new file mode 100644 (file)
index 0000000..a61e19b
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..ef40ab3
--- /dev/null
@@ -0,0 +1,27 @@
+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
new file mode 100644 (file)
index 0000000..1ce974c
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..bffe531
--- /dev/null
@@ -0,0 +1,30 @@
+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
new file mode 100644 (file)
index 0000000..7464078
--- /dev/null
@@ -0,0 +1,35 @@
+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
new file mode 100644 (file)
index 0000000..d679c45
--- /dev/null
@@ -0,0 +1,37 @@
+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
new file mode 100644 (file)
index 0000000..36ee547
--- /dev/null
@@ -0,0 +1,163 @@
+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
new file mode 100644 (file)
index 0000000..0f63957
--- /dev/null
@@ -0,0 +1,67 @@
+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
new file mode 100644 (file)
index 0000000..8529cc2
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..e801ebf
--- /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-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
new file mode 100644 (file)
index 0000000..e52eb8e
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/jcr/org.argeo.cms.ui/bnd.bnd b/jcr/org.argeo.cms.ui/bnd.bnd
new file mode 100644 (file)
index 0000000..ed84241
--- /dev/null
@@ -0,0 +1,22 @@
+Bundle-Activator: org.argeo.cms.ui.internal.Activator
+Bundle-ActivationPolicy: lazy
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.core.commands,\
+javax.jcr.security,\
+org.eclipse.rap.fileupload;version="[2.1,4)",\
+org.eclipse.rap.rwt;version="[2.1,4)",\
+org.eclipse.rap.rwt.application;version="[2.1,4)",\
+org.eclipse.rap.rwt.client;version="[2.1,4)",\
+org.eclipse.rap.rwt.client.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.widgets;version="[2.1,4)",\
+org.osgi.*;version=0.0.0,\
+*
+
+## TODO: in order to enable single sourcing, we have introduced dummy RAP packages 
+# in the RCP specific bundle org.argeo.eclipse.ui.rap.
+# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, 
+# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x
+# We enable large RAP version range as a workaround that must be fixed 
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/build.properties b/jcr/org.argeo.cms.ui/build.properties
new file mode 100644 (file)
index 0000000..c6baffa
--- /dev/null
@@ -0,0 +1,5 @@
+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
new file mode 100644 (file)
index 0000000..3288d10
Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/loading.gif 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
new file mode 100644 (file)
index 0000000..0396506
Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png 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
new file mode 100644 (file)
index 0000000..8e3abb5
Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png differ
diff --git a/jcr/org.argeo.cms.ui/pom.xml b/jcr/org.argeo.cms.ui/pom.xml
new file mode 100644 (file)
index 0000000..33053d2
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>jcr</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <artifactId>org.argeo.cms.ui</artifactId>
+       <name>CMS UI</name>
+       <packaging>jar</packaging>
+       <dependencies>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.swt</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.commons</groupId>
+                       <artifactId>org.argeo.cms.jcr</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+               </dependency>
+               
+               <!-- Specific -->
+               <dependency>
+                       <groupId>org.argeo.commons.rap</groupId>
+                       <artifactId>org.argeo.swt.specific.rap</artifactId>
+                       <version>2.3-SNAPSHOT</version>
+                       <scope>provided</scope>
+               </dependency>
+
+               <!-- UI -->
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.rwt</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.core.commands</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.jface</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+
+               <!-- TODO move it to specific -->
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.filedialog</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.argeo.tp.rap.e4</groupId>
+                       <artifactId>org.eclipse.rap.fileupload</artifactId>
+                       <scope>provided</scope>
+               </dependency>
+
+       </dependencies>
+</project>
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..872142b
--- /dev/null
@@ -0,0 +1,23 @@
+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
new file mode 100644 (file)
index 0000000..9df61dc
--- /dev/null
@@ -0,0 +1,28 @@
+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
new file mode 100644 (file)
index 0000000..ec76321
--- /dev/null
@@ -0,0 +1,30 @@
+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
new file mode 100644 (file)
index 0000000..5d77c15
--- /dev/null
@@ -0,0 +1,11 @@
+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
new file mode 100644 (file)
index 0000000..4ce4688
--- /dev/null
@@ -0,0 +1,108 @@
+package org.argeo.cms.ui.eclipse.forms;
+/**
+ * AbstractFormPart implements IFormPart interface and can be used as a
+ * convenient base class for concrete form parts. If a method contains
+ * code that must be called, look for instructions to call 'super'
+ * when overriding.
+ * 
+ * @see org.eclipse.ui.forms.widgets.Section
+ * @since 1.0
+ */
+public abstract class AbstractFormPart implements IFormPart {
+       private IManagedForm managedForm;
+       private boolean dirty = false;
+       private boolean stale = true;
+       /**
+        * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm)
+        */
+       public void initialize(IManagedForm form) {
+               this.managedForm = form;
+       }
+       /**
+        * Returns the form that manages this part.
+        * 
+        * @return the managed form
+        */
+       public IManagedForm getManagedForm() {
+               return managedForm;
+       }
+       /**
+        * Disposes the part. Subclasses should override to release any system
+        * resources.
+        */
+       public void dispose() {
+       }
+       /**
+        * Commits the part. Subclasses should call 'super' when overriding.
+        * 
+        * @param onSave
+        *            <code>true</code> if the request to commit has arrived as a
+        *            result of the 'save' action.
+        */
+       public void commit(boolean onSave) {
+               dirty = false;
+       }
+       /**
+        * Sets the overall form input. Subclases may elect to override the method
+        * and adjust according to the form input.
+        * 
+        * @param input
+        *            the form input object
+        * @return <code>false</code>
+        */
+       public boolean setFormInput(Object input) {
+               return false;
+       }
+       /**
+        * Instructs the part to grab keyboard focus.
+        */
+       public void setFocus() {
+       }
+       /**
+        * Refreshes the section after becoming stale (falling behind data in the
+        * model). Subclasses must call 'super' when overriding this method.
+        */
+       public void refresh() {
+               stale = false;
+               // since we have refreshed, any changes we had in the
+               // part are gone and we are not dirty
+               dirty = false;
+       }
+       /**
+        * Marks the part dirty. Subclasses should call this method as a result of
+        * user interaction with the widgets in the section.
+        */
+       public void markDirty() {
+               dirty = true;
+               managedForm.dirtyStateChanged();
+       }
+       /**
+        * Tests whether the part is dirty i.e. its widgets have state that is
+        * newer than the data in the model.
+        * 
+        * @return <code>true</code> if the part is dirty, <code>false</code>
+        *         otherwise.
+        */
+       public boolean isDirty() {
+               return dirty;
+       }
+       /**
+        * Tests whether the part is stale i.e. its widgets have state that is
+        * older than the data in the model.
+        * 
+        * @return <code>true</code> if the part is stale, <code>false</code>
+        *         otherwise.
+        */
+       public boolean isStale() {
+               return stale;
+       }
+       /**
+        * Marks the part stale. Subclasses should call this method as a result of
+        * model notification that indicates that the content of the section is no
+        * longer in sync with the model.
+        */
+       public void markStale() {
+               stale = true;
+               managedForm.staleStateChanged();
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..32b031b
--- /dev/null
@@ -0,0 +1,730 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Manages colors that will be applied to forms and form widgets. The colors are
+ * chosen to make the widgets look correct in the editor area. If a different
+ * set of colors is needed, subclass this class and override 'initialize' and/or
+ * 'initializeColors'.
+ * 
+ * @since 1.0
+ */
+public class FormColors {
+       /**
+        * Key for the form title foreground color.
+        * 
+        * @deprecated use <code>IFormColors.TITLE</code>.
+        */
+       public static final String TITLE = IFormColors.TITLE;
+
+       /**
+        * Key for the tree/table border color.
+        * 
+        * @deprecated use <code>IFormColors.BORDER</code>
+        */
+       public static final String BORDER = IFormColors.BORDER;
+
+       /**
+        * Key for the section separator color.
+        * 
+        * @deprecated use <code>IFormColors.SEPARATOR</code>.
+        */
+       public static final String SEPARATOR = IFormColors.SEPARATOR;
+
+       /**
+        * Key for the section title bar background.
+        * 
+        * @deprecated use <code>IFormColors.TB_BG
+        */
+       public static final String TB_BG = IFormColors.TB_BG;
+
+       /**
+        * Key for the section title bar foreground.
+        * 
+        * @deprecated use <code>IFormColors.TB_FG</code>
+        */
+       public static final String TB_FG = IFormColors.TB_FG;
+
+       /**
+        * Key for the section title bar gradient.
+        * 
+        * @deprecated use <code>IFormColors.TB_GBG</code>
+        */
+       public static final String TB_GBG = IFormColors.TB_GBG;
+
+       /**
+        * Key for the section title bar border.
+        * 
+        * @deprecated use <code>IFormColors.TB_BORDER</code>.
+        */
+       public static final String TB_BORDER = IFormColors.TB_BORDER;
+
+       /**
+        * Key for the section toggle color. Since 3.1, this color is used for all
+        * section styles.
+        * 
+        * @deprecated use <code>IFormColors.TB_TOGGLE</code>.
+        */
+       public static final String TB_TOGGLE = IFormColors.TB_TOGGLE;
+
+       /**
+        * Key for the section toggle hover color.
+        * 
+        * @deprecated use <code>IFormColors.TB_TOGGLE_HOVER</code>.
+        */
+       public static final String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER;
+
+       protected Map colorRegistry = new HashMap(10);
+
+       private LocalResourceManager resources;
+
+       protected Color background;
+
+       protected Color foreground;
+
+       private boolean shared;
+
+       protected Display display;
+
+       protected Color border;
+
+       /**
+        * Creates form colors using the provided display.
+        * 
+        * @param display
+        *            the display to use
+        */
+       public FormColors(Display display) {
+               this.display = display;
+               initialize();
+       }
+
+       /**
+        * Returns the display used to create colors.
+        * 
+        * @return the display
+        */
+       public Display getDisplay() {
+               return display;
+       }
+
+       /**
+        * Initializes the colors. Subclasses can override this method to change the
+        * way colors are created. Alternatively, only the color table can be
+        * modified by overriding <code>initializeColorTable()</code>.
+        * 
+        * @see #initializeColorTable
+        */
+       protected void initialize() {
+               background = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+               foreground = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+               initializeColorTable();
+               updateBorderColor();
+       }
+
+       /**
+        * Allocates colors for the following keys: BORDER, SEPARATOR and
+        * TITLE. Subclasses can override to allocate these colors differently.
+        */
+       protected void initializeColorTable() {
+               createTitleColor();
+               createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB());
+               RGB black = getSystemColor(SWT.COLOR_BLACK);
+               RGB borderRGB = getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT);
+               createColor(IFormColors.BORDER, blend(borderRGB, black, 80));
+       }
+
+       /**
+        * Allocates colors for the section tool bar (all the keys that start with
+        * TB). Since these colors are only needed when TITLE_BAR style is used with
+        * the Section widget, they are not needed all the time and are allocated on
+        * demand. Consequently, this method will do nothing if the colors have been
+        * already initialized. Call this method prior to using colors with the TB
+        * keys to ensure they are available.
+        */
+       public void initializeSectionToolBarColors() {
+               if (colorRegistry.containsKey(IFormColors.TB_BG))
+                       return;
+               createTitleBarGradientColors();
+               createTitleBarOutlineColors();
+               createTwistieColors();
+       }
+
+       /**
+        * Allocates additional colors for the form header, namely background
+        * gradients, bottom separator keylines and DND highlights. Since these
+        * colors are only needed for clients that want to use these particular
+        * style of header rendering, they are not needed all the time and are
+        * allocated on demand. Consequently, this method will do nothing if the
+        * colors have been already initialized. Call this method prior to using
+        * color keys with the H_ prefix to ensure they are available.
+        */
+       protected void initializeFormHeaderColors() {
+               if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2))
+                       return;
+               createFormHeaderColors();
+       }
+
+       /**
+        * Returns the RGB value of the system color represented by the code
+        * argument, as defined in <code>SWT</code> class.
+        * 
+        * @param code
+        *            the system color constant as defined in <code>SWT</code>
+        *            class.
+        * @return the RGB value of the system color
+        */
+       public RGB getSystemColor(int code) {
+               return getDisplay().getSystemColor(code).getRGB();
+       }
+
+       /**
+        * Creates the color for the specified key using the provided RGB object.
+        * The color object will be returned and also put into the registry. When
+        * the class is disposed, the color will be disposed with it.
+        * 
+        * @param key
+        *            the unique color key
+        * @param rgb
+        *            the RGB object
+        * @return the allocated color object
+        */
+       public Color createColor(String key, RGB rgb) {
+               // RAP [rh] changes due to missing Color constructor
+//             Color c = getResourceManager().createColor(rgb);
+//             Color prevC = (Color) colorRegistry.get(key);
+//             if (prevC != null && !prevC.isDisposed())
+//                     getResourceManager().destroyColor(prevC.getRGB());
+//             Color c = Graphics.getColor(rgb);
+               Color c = new Color(display, rgb);
+               colorRegistry.put(key, c);        
+               return c;
+       }
+
+       /**
+        * Creates a color that can be used for areas of the form that is inactive.
+        * These areas can contain images, links, controls and other content but are
+        * considered auxilliary to the main content area.
+        * 
+        * <p>
+        * The color should not be disposed because it is managed by this class.
+        * 
+        * @return the inactive form color
+        */
+       public Color getInactiveBackground() {
+               String key = "__ncbg__"; //$NON-NLS-1$
+               Color color = getColor(key);
+               if (color == null) {
+                       RGB sel = getSystemColor(SWT.COLOR_LIST_SELECTION);
+                       // a blend of 95% white and 5% list selection system color
+                       RGB ncbg = blend(sel, getSystemColor(SWT.COLOR_WHITE), 5);
+                       color = createColor(key, ncbg);
+               }
+               return color;
+       }
+
+       /**
+        * Creates the color for the specified key using the provided RGB values.
+        * The color object will be returned and also put into the registry. If
+        * there is already another color object under the same key in the registry,
+        * the existing object will be disposed. When the class is disposed, the
+        * color will be disposed with it.
+        * 
+        * @param key
+        *            the unique color key
+        * @param r
+        *            red value
+        * @param g
+        *            green value
+        * @param b
+        *            blue value
+        * @return the allocated color object
+        */
+       public Color createColor(String key, int r, int g, int b) {
+               return createColor(key, new RGB(r,g,b));
+       }
+
+       /**
+        * Computes the border color relative to the background. Allocated border
+        * color is designed to work well with white. Otherwise, stanard widget
+        * background color will be used.
+        */
+       protected void updateBorderColor() {
+               if (isWhiteBackground())
+                       border = getColor(IFormColors.BORDER);
+               else {
+                       border = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+                       Color bg = getImpliedBackground();
+                       if (border.getRed() == bg.getRed()
+                                       && border.getGreen() == bg.getGreen()
+                                       && border.getBlue() == bg.getBlue())
+                               border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW);
+               }
+       }
+
+       /**
+        * Sets the background color. All the toolkits that use this class will
+        * share the same background.
+        * 
+        * @param bg
+        *            background color
+        */
+       public void setBackground(Color bg) {
+               this.background = bg;
+               updateBorderColor();
+               updateFormHeaderColors();
+       }
+
+       /**
+        * Sets the foreground color. All the toolkits that use this class will
+        * share the same foreground.
+        * 
+        * @param fg
+        *            foreground color
+        */
+       public void setForeground(Color fg) {
+               this.foreground = fg;
+       }
+
+       /**
+        * Returns the current background color.
+        * 
+        * @return the background color
+        */
+       public Color getBackground() {
+               return background;
+       }
+
+       /**
+        * Returns the current foreground color.
+        * 
+        * @return the foreground color
+        */
+       public Color getForeground() {
+               return foreground;
+       }
+
+       /**
+        * Returns the computed border color. Border color depends on the background
+        * and is recomputed whenever the background changes.
+        * 
+        * @return the current border color
+        */
+       public Color getBorderColor() {
+               return border;
+       }
+
+       /**
+        * Tests if the background is white. White background has RGB value
+        * 255,255,255.
+        * 
+        * @return <samp>true</samp> if background is white, <samp>false</samp>
+        *         otherwise.
+        */
+       public boolean isWhiteBackground() {
+               Color bg = getImpliedBackground();
+               return bg.getRed() == 255 && bg.getGreen() == 255
+                               && bg.getBlue() == 255;
+       }
+
+       /**
+        * Returns the color object for the provided key or <samp>null </samp> if
+        * not in the registry.
+        * 
+        * @param key
+        *            the color key
+        * @return color object if found, or <samp>null </samp> if not.
+        */
+       public Color getColor(String key) {
+               if (key.startsWith(IFormColors.TB_PREFIX))
+                       initializeSectionToolBarColors();
+               else if (key.startsWith(IFormColors.H_PREFIX))
+                       initializeFormHeaderColors();
+               return (Color) colorRegistry.get(key);
+       }
+
+       /**
+        * Disposes all the colors in the registry.
+        */
+       public void dispose() {
+               if (resources != null)
+                       resources.dispose();
+               resources = null;
+               colorRegistry = null;
+       }
+
+       /**
+        * Marks the colors shared. This prevents toolkits that share this object
+        * from disposing it.
+        */
+       public void markShared() {
+               this.shared = true;
+       }
+
+       /**
+        * Tests if the colors are shared.
+        * 
+        * @return <code>true</code> if shared, <code>false</code> otherwise.
+        */
+       public boolean isShared() {
+               return shared;
+       }
+
+       /**
+        * Blends c1 and c2 based in the provided ratio.
+        * 
+        * @param c1
+        *            first color
+        * @param c2
+        *            second color
+        * @param ratio
+        *            percentage of the first color in the blend (0-100)
+        * @return the RGB value of the blended color
+        */
+       public static RGB blend(RGB c1, RGB c2, int ratio) {
+               int r = blend(c1.red, c2.red, ratio);
+               int g = blend(c1.green, c2.green, ratio);
+               int b = blend(c1.blue, c2.blue, ratio);
+               return new RGB(r, g, b);
+       }
+
+       /**
+        * Tests the source RGB for range.
+        * 
+        * @param rgb
+        *            the tested RGB
+        * @param from
+        *            range start (excluding the value itself)
+        * @param to
+        *            range end (excluding the value itself)
+        * @return <code>true</code> if at least one of the primary colors in the
+        *         source RGB are within the provided range, <code>false</code>
+        *         otherwise.
+        */
+       public static boolean testAnyPrimaryColor(RGB rgb, int from, int to) {
+               if (testPrimaryColor(rgb.red, from, to))
+                       return true;
+               if (testPrimaryColor(rgb.green, from, to))
+                       return true;
+               if (testPrimaryColor(rgb.blue, from, to))
+                       return true;
+               return false;
+       }
+
+       /**
+        * Tests the source RGB for range.
+        * 
+        * @param rgb
+        *            the tested RGB
+        * @param from
+        *            range start (excluding the value itself)
+        * @param to
+        *            tange end (excluding the value itself)
+        * @return <code>true</code> if at least two of the primary colors in the
+        *         source RGB are within the provided range, <code>false</code>
+        *         otherwise.
+        */
+       public static boolean testTwoPrimaryColors(RGB rgb, int from, int to) {
+               int total = 0;
+               if (testPrimaryColor(rgb.red, from, to))
+                       total++;
+               if (testPrimaryColor(rgb.green, from, to))
+                       total++;
+               if (testPrimaryColor(rgb.blue, from, to))
+                       total++;
+               return total >= 2;
+       }
+
+       /**
+        * Blends two primary color components based on the provided ratio.
+        * 
+        * @param v1
+        *            first component
+        * @param v2
+        *            second component
+        * @param ratio
+        *            percentage of the first component in the blend
+        * @return
+        */
+       private static int blend(int v1, int v2, int ratio) {
+               int b = (ratio * v1 + (100 - ratio) * v2) / 100;
+               return Math.min(255, b);
+       }
+
+       private Color getImpliedBackground() {
+               if (getBackground() != null)
+                       return getBackground();
+               return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+       }
+
+       private static boolean testPrimaryColor(int value, int from, int to) {
+               return value > from && value < to;
+       }
+
+       private void createTitleColor() {
+               /*
+                * RGB rgb = getSystemColor(SWT.COLOR_LIST_SELECTION); // test too light
+                * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80);
+                * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK,
+                * 50); createColor(TITLE, rgb);
+                */
+               RGB bg = getImpliedBackground().getRGB();
+               RGB listSelection = getSystemColor(SWT.COLOR_LIST_SELECTION);
+               RGB listForeground = getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+               RGB rgb = listSelection;
+
+               // Group 1
+               // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+               // between 0 and 120, then use 100% LIST_SELECTION as it is (no
+               // additions)
+               // Examples: XP Default, Win Classic Standard, Win High Con White, Win
+               // Classic Marine
+               if (testTwoPrimaryColors(listSelection, -1, 121))
+                       rgb = listSelection;
+               // Group 2
+               // When LIST_BACKGROUND = white (255, 255, 255) or not black, text
+               // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over
+               // LIST_BACKGROUND
+               // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or
+               // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION
+               // foreground colour
+               // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX
+               // Aqua, OSX Graphite, Linux GTK
+               else if (testTwoPrimaryColors(listSelection, 120, 256)
+                               || (bg.red == 0 && bg.green == 0 && bg.blue == 0))
+                       rgb = blend(listSelection, listForeground, 50);
+               // Group 3
+               // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION
+               // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND
+               // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to
+               // LIST_SELECTION foreground colour
+               // Examples: Win High Con Black, Win High Con #1, Win High Con #2
+               // (covered in the second part of the OR clause above)
+               createColor(IFormColors.TITLE, rgb);
+       }
+
+       private void createTwistieColors() {
+               RGB rgb = getColor(IFormColors.TITLE).getRGB();
+               RGB white = getSystemColor(SWT.COLOR_WHITE);
+               createColor(TB_TOGGLE, rgb);
+               rgb = blend(rgb, white, 60);
+               createColor(TB_TOGGLE_HOVER, rgb);
+       }
+
+       private void createTitleBarGradientColors() {
+               RGB tbBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+               RGB bg = getImpliedBackground().getRGB();
+
+               // Group 1
+               // Rule: If at least 2 of the RGB values are equal to or between 180 and
+               // 255, then apply specified opacity for Group 1
+               // Examples: Vista, XP Silver, Wn High Con #2
+               // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               if (testTwoPrimaryColors(tbBg, 179, 256))
+                       tbBg = blend(tbBg, bg, 30);
+
+               // Group 2
+               // Rule: If at least 2 of the RGB values are equal to or between 121 and
+               // 179, then apply specified opacity for Group 2
+               // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+               // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               else if (testTwoPrimaryColors(tbBg, 120, 180))
+                       tbBg = blend(tbBg, bg, 20);
+
+               // Group 3
+               // Rule: Everything else
+               // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+               // Aqua, Wn High Con White, Wn High Con #1
+               // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               else {
+                       tbBg = blend(tbBg, bg, 10);
+               }
+
+               createColor(IFormColors.TB_BG, tbBg);
+               
+               // for backward compatibility
+               createColor(TB_GBG, tbBg);
+       }
+
+       private void createTitleBarOutlineColors() {
+               // title bar outline - border color
+               RGB tbBorder = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+               RGB bg = getImpliedBackground().getRGB();
+               // Group 1
+               // Rule: If at least 2 of the RGB values are equal to or between 180 and
+               // 255, then apply specified opacity for Group 1
+               // Examples: Vista, XP Silver, Wn High Con #2
+               // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+               if (testTwoPrimaryColors(tbBorder, 179, 256))
+                       tbBorder = blend(tbBorder, bg, 70);
+
+               // Group 2
+               // Rule: If at least 2 of the RGB values are equal to or between 121 and
+               // 179, then apply specified opacity for Group 2
+               // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+
+               // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+               else if (testTwoPrimaryColors(tbBorder, 120, 180))
+                       tbBorder = blend(tbBorder, bg, 50);
+
+               // Group 3
+               // Rule: Everything else
+               // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+               // Aqua, Wn High Con White, Wn High Con #1
+
+               // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+               else {
+                       tbBorder = blend(tbBorder, bg, 30);
+               }
+               createColor(FormColors.TB_BORDER, tbBorder);
+       }
+
+       private void updateFormHeaderColors() {
+               if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) {
+                       disposeIfFound(IFormColors.H_GRADIENT_END);
+                       disposeIfFound(IFormColors.H_GRADIENT_START);
+                       disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1);
+                       disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2);
+                       disposeIfFound(IFormColors.H_HOVER_LIGHT);
+                       disposeIfFound(IFormColors.H_HOVER_FULL);
+                       initializeFormHeaderColors();
+               }
+       }
+
+       private void disposeIfFound(String key) {
+               Color color = getColor(key);
+               if (color != null) {
+                       colorRegistry.remove(key);
+               // RAP [rh] changes due to missing Color#dispose()                      
+//                     color.dispose();
+               }
+       }
+
+       private void createFormHeaderColors() {
+               createFormHeaderGradientColors();
+               createFormHeaderKeylineColors();
+               createFormHeaderDNDColors();
+       }
+
+       private void createFormHeaderGradientColors() {
+               RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+               Color bgColor = getImpliedBackground();
+               RGB bg = bgColor.getRGB();
+               RGB bottom, top;
+               // Group 1
+               // Rule: If at least 2 of the RGB values are equal to or between 180 and
+               // 255, then apply specified opacity for Group 1
+               // Examples: Vista, XP Silver, Wn High Con #2
+               // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               if (testTwoPrimaryColors(titleBg, 179, 256)) {
+                       bottom = blend(titleBg, bg, 30);
+                       top = bg;
+               }
+
+               // Group 2
+               // Rule: If at least 2 of the RGB values are equal to or between 121 and
+               // 179, then apply specified opacity for Group 2
+               // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+               // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               else if (testTwoPrimaryColors(titleBg, 120, 180)) {
+                       bottom = blend(titleBg, bg, 20);
+                       top = bg;
+               }
+
+               // Group 3
+               // Rule: If at least 2 of the RGB values are equal to or between 0 and
+               // 120, then apply specified opacity for Group 3
+               // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+               // Aqua, Wn High Con White, Wn High Con #1
+               // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND
+               // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND
+               else {
+                       bottom = blend(titleBg, bg, 10);
+                       top = bg;
+               }
+               createColor(IFormColors.H_GRADIENT_END, top);
+               createColor(IFormColors.H_GRADIENT_START, bottom);
+       }
+
+       private void createFormHeaderKeylineColors() {
+               RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND);
+               Color bgColor = getImpliedBackground();
+               RGB bg = bgColor.getRGB();
+               RGB keyline2;
+               // H_BOTTOM_KEYLINE1
+               createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255));
+
+               // H_BOTTOM_KEYLINE2
+               // Group 1
+               // Rule: If at least 2 of the RGB values are equal to or between 180 and
+               // 255, then apply specified opacity for Group 1
+               // Examples: Vista, XP Silver, Wn High Con #2
+               // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND
+               if (testTwoPrimaryColors(titleBg, 179, 256))
+                       keyline2 = blend(titleBg, bg, 70);
+
+               // Group 2
+               // Rule: If at least 2 of the RGB values are equal to or between 121 and
+               // 179, then apply specified opacity for Group 2
+               // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black
+               // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND
+               else if (testTwoPrimaryColors(titleBg, 120, 180))
+                       keyline2 = blend(titleBg, bg, 50);
+
+               // Group 3
+               // Rule: If at least 2 of the RGB values are equal to or between 0 and
+               // 120, then apply specified opacity for Group 3
+               // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX
+               // Aqua, Wn High Con White, Wn High Con #1
+
+               // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND
+               else
+                       keyline2 = blend(titleBg, bg, 30);
+               // H_BOTTOM_KEYLINE2
+               createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2);
+       }
+
+       private void createFormHeaderDNDColors() {
+               RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT);
+               Color bgColor = getImpliedBackground();
+               RGB bg = bgColor.getRGB();
+               RGB light, full;
+               // ALL Themes
+               //
+               // Light Highlight
+               // When *near* the 'hot' area
+               // Rule: If near the title in the 'hot' area, show background highlight
+               // TITLE_BACKGROUND_GRADIENT @ 40%
+               light = blend(titleBg, bg, 40);
+               // Full Highlight
+               // When *on* the title area (regions 1 and 2)
+               // Rule: If near the title in the 'hot' area, show background highlight
+               // TITLE_BACKGROUND_GRADIENT @ 60%
+               full = blend(titleBg, bg, 60);
+               // H_DND_LIGHT
+               // H_DND_FULL
+               createColor(IFormColors.H_HOVER_LIGHT, light);
+               createColor(IFormColors.H_HOVER_FULL, full);
+       }
+       
+       private LocalResourceManager getResourceManager() {
+               if (resources == null)
+                       resources = new LocalResourceManager(JFaceResources.getResources());
+               return resources;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..9e931ba
--- /dev/null
@@ -0,0 +1,122 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.HashMap;
+
+import org.eclipse.jface.resource.DeviceResourceException;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+public class FormFonts {
+       private static FormFonts instance;
+
+       public static FormFonts getInstance() {
+               if (instance == null)
+                       instance = new FormFonts();
+               return instance;
+       }
+
+       private LocalResourceManager resources;
+       private HashMap descriptors;
+
+       private FormFonts() {
+       }
+
+       private class BoldFontDescriptor extends FontDescriptor {
+               private FontData[] fFontData;
+
+               BoldFontDescriptor(Font font) {
+                       // RAP [if] Changes due to different way of creating fonts
+                       // fFontData = font.getFontData();
+                       // for (int i = 0; i < fFontData.length; i++) {
+                       // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD);
+                       // }
+                       FontData fontData = font.getFontData()[0];
+                       // Font boldFont = Graphics.getFont( fontData.getName(),
+                       // fontData.getHeight(),
+                       // fontData.getStyle() | SWT.BOLD );
+                       Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(),
+                                       fontData.getStyle() | SWT.BOLD);
+                       fFontData = boldFont.getFontData();
+               }
+
+               public boolean equals(Object obj) {
+                       if (obj instanceof BoldFontDescriptor) {
+                               BoldFontDescriptor desc = (BoldFontDescriptor) obj;
+                               if (desc.fFontData.length != fFontData.length)
+                                       return false;
+                               for (int i = 0; i < fFontData.length; i++)
+                                       if (!fFontData[i].equals(desc.fFontData[i]))
+                                               return false;
+                               return true;
+                       }
+                       return false;
+               }
+
+               public int hashCode() {
+                       int hash = 0;
+                       for (int i = 0; i < fFontData.length; i++)
+                               hash = hash * 7 + fFontData[i].hashCode();
+                       return hash;
+               }
+
+               public Font createFont(Device device) throws DeviceResourceException {
+                       // RAP [if] Changes due to different way of creating fonts
+                       return new Font(device, fFontData[0]);
+                       // return Graphics.getFont( fFontData[ 0 ] );
+               }
+
+               public void destroyFont(Font previouslyCreatedFont) {
+                       // RAP [if] unnecessary
+                       // previouslyCreatedFont.dispose();
+               }
+       }
+
+       public Font getBoldFont(Display display, Font font) {
+               checkHashMaps();
+               BoldFontDescriptor desc = new BoldFontDescriptor(font);
+               Font result = getResourceManager().createFont(desc);
+               descriptors.put(result, desc);
+               return result;
+       }
+
+       public boolean markFinished(Font boldFont) {
+               checkHashMaps();
+               BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont);
+               if (desc != null) {
+                       getResourceManager().destroyFont(desc);
+                       if (getResourceManager().find(desc) == null) {
+                               descriptors.remove(boldFont);
+                               validateHashMaps();
+                       }
+                       return true;
+
+               }
+               // if the image was not found, dispose of it for the caller
+               // RAP [if] unnecessary
+               // boldFont.dispose();
+               return false;
+       }
+
+       private LocalResourceManager getResourceManager() {
+               if (resources == null)
+                       resources = new LocalResourceManager(JFaceResources.getResources());
+               return resources;
+       }
+
+       private void checkHashMaps() {
+               if (descriptors == null)
+                       descriptors = new HashMap();
+       }
+
+       private void validateHashMaps() {
+               if (descriptors.size() == 0)
+                       descriptors = null;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..9927104
--- /dev/null
@@ -0,0 +1,913 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+//import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+// RAP [rh] Paint events missing
+//import org.eclipse.swt.events.PaintEvent;
+//import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+//RAP [rh] GC missing
+//import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+//import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+//import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+//import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.Widget;
+//import org.eclipse.ui.forms.FormColors;
+//import org.eclipse.ui.forms.HyperlinkGroup;
+//import org.eclipse.ui.forms.IFormColors;
+//import org.eclipse.ui.internal.forms.widgets.FormFonts;
+//import org.eclipse.ui.internal.forms.widgets.FormUtil;
+
+/**
+ * The toolkit is responsible for creating SWT controls adapted to work in
+ * Eclipse forms. In addition to changing their presentation properties (fonts,
+ * colors etc.), various listeners are attached to make them behave correctly in
+ * the form context.
+ * <p>
+ * In addition to being the control factory, the toolkit is also responsible for
+ * painting flat borders for select controls, managing hyperlink groups and
+ * control colors.
+ * <p>
+ * The toolkit creates some of the most common controls used to populate Eclipse
+ * forms. Controls that must be created using their constructors,
+ * <code>adapt()</code> method is available to change its properties in the
+ * same way as with the supported toolkit controls.
+ * <p>
+ * Typically, one toolkit object is created per workbench part (for example, an
+ * editor or a form wizard). The toolkit is disposed when the part is disposed.
+ * To conserve resources, it is possible to create one color object for the
+ * entire plug-in and share it between several toolkits. The plug-in is
+ * responsible for disposing the colors (disposing the toolkit that uses shared
+ * color object will not dispose the colors).
+ * <p>
+ * FormToolkit is normally instantiated, but can also be subclassed if some of
+ * the methods needs to be modified. In those cases, <code>super</code> must
+ * be called to preserve normal behaviour.
+ *
+ * @since 1.0
+ */
+public class FormToolkit {
+       public static final String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$
+
+       public static final String TREE_BORDER = "treeBorder"; //$NON-NLS-1$
+
+       public static final String TEXT_BORDER = "textBorder"; //$NON-NLS-1$
+
+       private int borderStyle = SWT.NULL;
+
+       private FormColors colors;
+
+       private int orientation = Window.getDefaultOrientation();
+
+       // private KeyListener deleteListener;
+       // RAP [rh] Paint events missing
+//     private BorderPainter borderPainter;
+
+       private BoldFontHolder boldFontHolder;
+
+//     private HyperlinkGroup hyperlinkGroup;
+       
+       private boolean isDisposed = false;
+
+       /* default */
+       VisibilityHandler visibilityHandler;
+
+       /* default */
+       KeyboardHandler keyboardHandler;
+
+       // RAP [rh] Paint events missing
+//     private class BorderPainter implements PaintListener {
+//             public void paintControl(PaintEvent event) {
+//                     Composite composite = (Composite) event.widget;
+//                     Control[] children = composite.getChildren();
+//                     for (int i = 0; i < children.length; i++) {
+//                             Control c = children[i];
+//                             boolean inactiveBorder = false;
+//                             boolean textBorder = false;
+//                             if (!c.isVisible())
+//                                     continue;
+//                             /*
+//                              * if (c.getEnabled() == false && !(c instanceof CCombo))
+//                              * continue;
+//                              */
+//                             if (c instanceof Hyperlink)
+//                                     continue;
+//                             Object flag = c.getData(KEY_DRAW_BORDER);
+//                             if (flag != null) {
+//                                     if (flag.equals(Boolean.FALSE))
+//                                             continue;
+//                                     if (flag.equals(TREE_BORDER))
+//                                             inactiveBorder = true;
+//                                     else if (flag.equals(TEXT_BORDER))
+//                                             textBorder = true;
+//                             }
+//                             if (getBorderStyle() == SWT.BORDER) {
+//                                     if (!inactiveBorder && !textBorder) {
+//                                             continue;
+//                                     }
+//                                     if (c instanceof Text || c instanceof Table
+//                                                     || c instanceof Tree)
+//                                             continue;
+//                             }
+//                             if (!inactiveBorder
+//                                             && (c instanceof Text || c instanceof CCombo || textBorder)) {
+//                                     Rectangle b = c.getBounds();
+//                                     GC gc = event.gc;
+//                                     gc.setForeground(c.getBackground());
+//                                     gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+//                                                     b.height + 1);
+//                                     // gc.setForeground(getBorderStyle() == SWT.BORDER ? colors
+//                                     // .getBorderColor() : colors.getForeground());
+//                                     gc.setForeground(colors.getBorderColor());
+//                                     if (c instanceof CCombo)
+//                                             gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+//                                                             b.height + 1);
+//                                     else
+//                                             gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1,
+//                                                             b.height + 3);
+//                             } else if (inactiveBorder || c instanceof Table
+//                                             || c instanceof Tree) {
+//                                     Rectangle b = c.getBounds();
+//                                     GC gc = event.gc;
+//                                     gc.setForeground(colors.getBorderColor());
+//                                     gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1,
+//                                                     b.height + 1);
+//                             }
+//                     }
+//             }
+//     }
+
+       private static class VisibilityHandler extends FocusAdapter {
+               public void focusGained(FocusEvent e) {
+                       Widget w = e.widget;
+                       if (w instanceof Control) {
+                               FormUtil.ensureVisible((Control) w);
+                       }
+               }
+       }
+
+       private static class KeyboardHandler extends KeyAdapter {
+               public void keyPressed(KeyEvent e) {
+                       Widget w = e.widget;
+                       if (w instanceof Control) {
+                               if (e.doit)
+                                       FormUtil.processKey(e.keyCode, (Control) w);
+                       }
+               }
+       }
+
+       private class BoldFontHolder {
+               private Font normalFont;
+
+               private Font boldFont;
+
+               public BoldFontHolder() {
+               }
+
+               public Font getBoldFont(Font font) {
+                       createBoldFont(font);
+                       return boldFont;
+               }
+
+               private void createBoldFont(Font font) {
+                       if (normalFont == null || !normalFont.equals(font)) {
+                               normalFont = font;
+                               dispose();
+                       }
+                       if (boldFont == null) {
+                               boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(),
+                                               normalFont);
+                       }
+               }
+
+               public void dispose() {
+                       if (boldFont != null) {
+                               FormFonts.getInstance().markFinished(boldFont);
+                               boldFont = null;
+                       }
+               }
+       }
+
+       /**
+        * Creates a toolkit that is self-sufficient (will manage its own colors).
+        * <p>
+        * Clients that call this method must call {@link #dispose()} when they
+        * are finished using the toolkit.
+        *
+        */
+       public FormToolkit(Display display) {
+               this(new FormColors(display));
+       }
+
+       /**
+        * Creates a toolkit that will use the provided (shared) colors. The toolkit
+        * will dispose the colors if and only if they are <b>not</b> marked as
+        * shared via the <code>markShared()</code> method.
+        * <p>
+        * Clients that call this method must call {@link #dispose()} when they
+        * are finished using the toolkit.
+        *
+        * @param colors
+        *            the shared colors
+        */
+       public FormToolkit(FormColors colors) {
+               this.colors = colors;
+               initialize();
+       }
+
+       /**
+        * Creates a button as a part of the form.
+        *
+        * @param parent
+        *            the button parent
+        * @param text
+        *            an optional text for the button (can be <code>null</code>)
+        * @param style
+        *            the button style (for example, <code>SWT.PUSH</code>)
+        * @return the button widget
+        */
+       public Button createButton(Composite parent, String text, int style) {
+               Button button = new Button(parent, style | SWT.FLAT | orientation);
+               if (text != null)
+                       button.setText(text);
+               adapt(button, true, true);
+               return button;
+       }
+
+       /**
+        * Creates the composite as a part of the form.
+        *
+        * @param parent
+        *            the composite parent
+        * @return the composite widget
+        */
+       public Composite createComposite(Composite parent) {
+               return createComposite(parent, SWT.NULL);
+       }
+
+       /**
+        * Creates the composite as part of the form using the provided style.
+        *
+        * @param parent
+        *            the composite parent
+        * @param style
+        *            the composite style
+        * @return the composite widget
+        */
+       public Composite createComposite(Composite parent, int style) {
+//             Composite composite = new LayoutComposite(parent, style | orientation);
+               Composite composite = new Composite(parent, style | orientation);
+               adapt(composite);
+               return composite;
+       }
+
+       /**
+        * Creats the composite that can server as a separator between various parts
+        * of a form. Separator height should be controlled by setting the height
+        * hint on the layout data for the composite.
+        *
+        * @param parent
+        *            the separator parent
+        * @return the separator widget
+        */
+// RAP [rh] createCompositeSeparator: currently no useful implementation possible, delete?
+       public Composite createCompositeSeparator(Composite parent) {
+               final Composite composite = new Composite(parent, orientation);
+// RAP [rh] GC and paint events missing
+//             composite.addListener(SWT.Paint, new Listener() {
+//                     public void handleEvent(Event e) {
+//                             if (composite.isDisposed())
+//                                     return;
+//                             Rectangle bounds = composite.getBounds();
+//                             GC gc = e.gc;
+//                             gc.setForeground(colors.getColor(IFormColors.SEPARATOR));
+//                             if (colors.getBackground() != null)
+//                                     gc.setBackground(colors.getBackground());
+//                             gc.fillGradientRectangle(0, 0, bounds.width, bounds.height,
+//                                             false);
+//                     }
+//             });
+//             if (parent instanceof Section)
+//                     ((Section) parent).setSeparatorControl(composite);
+               return composite;
+       }
+
+       /**
+        * Creates a label as a part of the form.
+        *
+        * @param parent
+        *            the label parent
+        * @param text
+        *            the label text
+        * @return the label widget
+        */
+       public Label createLabel(Composite parent, String text) {
+               return createLabel(parent, text, SWT.NONE);
+       }
+
+       /**
+        * Creates a label as a part of the form.
+        *
+        * @param parent
+        *            the label parent
+        * @param text
+        *            the label text
+        * @param style
+        *            the label style
+        * @return the label widget
+        */
+       public Label createLabel(Composite parent, String text, int style) {
+               Label label = new Label(parent, style | orientation);
+               if (text != null)
+                       label.setText(text);
+               adapt(label, false, false);
+               return label;
+       }
+
+       /**
+        * Creates a hyperlink as a part of the form. The hyperlink will be added to
+        * the hyperlink group that belongs to this toolkit.
+        *
+        * @param parent
+        *            the hyperlink parent
+        * @param text
+        *            the text of the hyperlink
+        * @param style
+        *            the hyperlink style
+        * @return the hyperlink widget
+        */
+//     public Hyperlink createHyperlink(Composite parent, String text, int style) {
+//             Hyperlink hyperlink = new Hyperlink(parent, style | orientation);
+//             if (text != null)
+//                     hyperlink.setText(text);
+//             hyperlink.addFocusListener(visibilityHandler);
+//             hyperlink.addKeyListener(keyboardHandler);
+//             hyperlinkGroup.add(hyperlink);
+//             return hyperlink;
+//     }
+
+       /**
+        * Creates an image hyperlink as a part of the form. The hyperlink will be
+        * added to the hyperlink group that belongs to this toolkit.
+        *
+        * @param parent
+        *            the hyperlink parent
+        * @param style
+        *            the hyperlink style
+        * @return the image hyperlink widget
+        */
+//     public ImageHyperlink createImageHyperlink(Composite parent, int style) {
+//             ImageHyperlink hyperlink = new ImageHyperlink(parent, style
+//                             | orientation);
+//             hyperlink.addFocusListener(visibilityHandler);
+//             hyperlink.addKeyListener(keyboardHandler);
+//             hyperlinkGroup.add(hyperlink);
+//             return hyperlink;
+//     }
+
+       /**
+        * Creates a rich text as a part of the form.
+        *
+        * @param parent
+        *            the rich text parent
+        * @param trackFocus
+        *            if <code>true</code>, the toolkit will monitor focus
+        *            transfers to ensure that the hyperlink in focus is visible in
+        *            the form.
+        * @return the rich text widget
+        * @since 1.2
+        */
+//     public FormText createFormText(Composite parent, boolean trackFocus) {
+//             FormText engine = new FormText(parent, SWT.WRAP | orientation);
+//             engine.marginWidth = 1;
+//             engine.marginHeight = 0;
+//             engine.setHyperlinkSettings(getHyperlinkGroup());
+//             adapt(engine, trackFocus, true);
+//             engine.setMenu(parent.getMenu());
+//             return engine;
+//     }
+
+       /**
+        * Adapts a control to be used in a form that is associated with this
+        * toolkit. This involves adjusting colors and optionally adding handlers to
+        * ensure focus tracking and keyboard management.
+        *
+        * @param control
+        *            a control to adapt
+        * @param trackFocus
+        *            if <code>true</code>, form will be scrolled horizontally
+        *            and/or vertically if needed to ensure that the control is
+        *            visible when it gains focus. Set it to <code>false</code> if
+        *            the control is not capable of gaining focus.
+        * @param trackKeyboard
+        *            if <code>true</code>, the control that is capable of
+        *            gaining focus will be tracked for certain keys that are
+        *            important to the underlying form (for example, PageUp,
+        *            PageDown, ScrollUp, ScrollDown etc.). Set it to
+        *            <code>false</code> if the control is not capable of gaining
+        *            focus or these particular key event are already used by the
+        *            control.
+        */
+       public void adapt(Control control, boolean trackFocus, boolean trackKeyboard) {
+               control.setBackground(colors.getBackground());
+               control.setForeground(colors.getForeground());
+//             if (control instanceof ExpandableComposite) {
+//                     ExpandableComposite ec = (ExpandableComposite) control;
+//                     if (ec.toggle != null) {
+//                             if (trackFocus)
+//                                     ec.toggle.addFocusListener(visibilityHandler);
+//                             if (trackKeyboard)
+//                                     ec.toggle.addKeyListener(keyboardHandler);
+//                     }
+//                     if (ec.textLabel != null) {
+//                             if (trackFocus)
+//                                     ec.textLabel.addFocusListener(visibilityHandler);
+//                             if (trackKeyboard)
+//                                     ec.textLabel.addKeyListener(keyboardHandler);
+//                     }
+//                     return;
+//             }
+               if (trackFocus)
+                       control.addFocusListener(visibilityHandler);
+               if (trackKeyboard)
+                       control.addKeyListener(keyboardHandler);
+       }
+
+       /**
+        * Adapts a composite to be used in a form associated with this toolkit.
+        *
+        * @param composite
+        *            the composite to adapt
+        */
+       public void adapt(Composite composite) {
+               composite.setBackground(colors.getBackground());
+               composite.addMouseListener(new MouseAdapter() {
+                       public void mouseDown(MouseEvent e) {
+                               ((Control) e.widget).setFocus();
+                       }
+               });
+               if (composite.getParent() != null)
+                       composite.setMenu(composite.getParent().getMenu());
+       }
+
+       /**
+        * A helper method that ensures the provided control is visible when
+        * ScrolledComposite is somewhere in the parent chain. If scroll bars are
+        * visible and the control is clipped, the client of the scrolled composite
+        * will be scrolled to reveal the control.
+        *
+        * @param c
+        *            the control to reveal
+        */
+       public static void ensureVisible(Control c) {
+               FormUtil.ensureVisible(c);
+       }
+
+       /**
+        * Creates a section as a part of the form.
+        *
+        * @param parent
+        *            the section parent
+        * @param sectionStyle
+        *            the section style
+        * @return the section widget
+        */
+//     public Section createSection(Composite parent, int sectionStyle) {
+//             Section section = new Section(parent, orientation, sectionStyle);
+//             section.setMenu(parent.getMenu());
+//             adapt(section, true, true);
+//             if (section.toggle != null) {
+//                     section.toggle.setHoverDecorationColor(colors
+//                                     .getColor(IFormColors.TB_TOGGLE_HOVER));
+//                     section.toggle.setDecorationColor(colors
+//                                     .getColor(IFormColors.TB_TOGGLE));
+//             }
+//             section.setFont(boldFontHolder.getBoldFont(parent.getFont()));
+//             if ((sectionStyle & Section.TITLE_BAR) != 0
+//                             || (sectionStyle & Section.SHORT_TITLE_BAR) != 0) {
+//                     colors.initializeSectionToolBarColors();
+//                     section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG));
+//                     section.setTitleBarBorderColor(colors
+//                                     .getColor(IFormColors.TB_BORDER));
+//             }
+//             // call setTitleBarForeground regardless as it also sets the label color
+//             section.setTitleBarForeground(colors
+//                             .getColor(IFormColors.TB_TOGGLE));
+//             return section;
+//     }
+
+       /**
+        * Creates an expandable composite as a part of the form.
+        *
+        * @param parent
+        *            the expandable composite parent
+        * @param expansionStyle
+        *            the expandable composite style
+        * @return the expandable composite widget
+        */
+//     public ExpandableComposite createExpandableComposite(Composite parent,
+//                     int expansionStyle) {
+//             ExpandableComposite ec = new ExpandableComposite(parent, orientation,
+//                             expansionStyle);
+//             ec.setMenu(parent.getMenu());
+//             adapt(ec, true, true);
+//             ec.setFont(boldFontHolder.getBoldFont(ec.getFont()));
+//             return ec;
+//     }
+
+       /**
+        * Creates a separator label as a part of the form.
+        *
+        * @param parent
+        *            the separator parent
+        * @param style
+        *            the separator style
+        * @return the separator label
+        */
+       public Label createSeparator(Composite parent, int style) {
+               Label label = new Label(parent, SWT.SEPARATOR | style | orientation);
+               label.setBackground(colors.getBackground());
+               label.setForeground(colors.getBorderColor());
+               return label;
+       }
+
+       /**
+        * Creates a table as a part of the form.
+        *
+        * @param parent
+        *            the table parent
+        * @param style
+        *            the table style
+        * @return the table widget
+        */
+       public Table createTable(Composite parent, int style) {
+               Table table = new Table(parent, style | borderStyle | orientation);
+               adapt(table, false, false);
+               // hookDeleteListener(table);
+               return table;
+       }
+
+       /**
+        * Creates a text as a part of the form.
+        *
+        * @param parent
+        *            the text parent
+        * @param value
+        *            the text initial value
+        * @return the text widget
+        */
+       public Text createText(Composite parent, String value) {
+               return createText(parent, value, SWT.SINGLE);
+       }
+
+       /**
+        * Creates a text as a part of the form.
+        *
+        * @param parent
+        *            the text parent
+        * @param value
+        *            the text initial value
+        * @param style
+        *            the text style
+        * @return the text widget
+        */
+       public Text createText(Composite parent, String value, int style) {
+               Text text = new Text(parent, borderStyle | style | orientation);
+               if (value != null)
+                       text.setText(value);
+               text.setForeground(colors.getForeground());
+               text.setBackground(colors.getBackground());
+               text.addFocusListener(visibilityHandler);
+               return text;
+       }
+
+       /**
+        * Creates a tree widget as a part of the form.
+        *
+        * @param parent
+        *            the tree parent
+        * @param style
+        *            the tree style
+        * @return the tree widget
+        */
+       public Tree createTree(Composite parent, int style) {
+               Tree tree = new Tree(parent, borderStyle | style | orientation);
+               adapt(tree, false, false);
+               // hookDeleteListener(tree);
+               return tree;
+       }
+
+       /**
+        * Creates a scrolled form widget in the provided parent. If you do not
+        * require scrolling because there is already a scrolled composite up the
+        * parent chain, use 'createForm' instead.
+        *
+        * @param parent
+        *            the scrolled form parent
+        * @return the form that can scroll itself
+        * @see #createForm
+        */
+       public ScrolledComposite createScrolledForm(Composite parent) {
+               ScrolledComposite form = new ScrolledComposite(parent, SWT.V_SCROLL
+                               | SWT.H_SCROLL | orientation);
+               form.setExpandHorizontal(true);
+               form.setExpandVertical(true);
+               form.setBackground(colors.getBackground());
+               form.setForeground(colors.getColor(IFormColors.TITLE));
+               form.setFont(JFaceResources.getHeaderFont());
+               return form;
+       }
+
+       /**
+        * Creates a form widget in the provided parent. Note that this widget does
+        * not scroll its content, so make sure there is a scrolled composite up the
+        * parent chain. If you require scrolling, use 'createScrolledForm' instead.
+        *
+        * @param parent
+        *            the form parent
+        * @return the form that does not scroll
+        * @see #createScrolledForm
+        */
+//     public Form createForm(Composite parent) {
+//             Form formContent = new Form(parent, orientation);
+//             formContent.setBackground(colors.getBackground());
+//             formContent.setForeground(colors.getColor(IFormColors.TITLE));
+//             formContent.setFont(JFaceResources.getHeaderFont());
+//             return formContent;
+//     }
+
+       /**
+        * Takes advantage of the gradients and other capabilities to decorate the
+        * form heading using colors computed based on the current skin and
+        * operating system.
+        *
+        * @param form
+        *            the form to decorate
+        */
+
+//     public void decorateFormHeading(Form form) {
+//             Color top = colors.getColor(IFormColors.H_GRADIENT_END);
+//             Color bot = colors.getColor(IFormColors.H_GRADIENT_START);
+//             form.setTextBackground(new Color[] { top, bot }, new int[] { 100 },
+//                             true);
+//             form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors
+//                             .getColor(IFormColors.H_BOTTOM_KEYLINE1));
+//             form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors
+//                             .getColor(IFormColors.H_BOTTOM_KEYLINE2));
+//             form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors
+//                             .getColor(IFormColors.H_HOVER_LIGHT));
+//             form.setHeadColor(IFormColors.H_HOVER_FULL, colors
+//                             .getColor(IFormColors.H_HOVER_FULL));
+//             form.setHeadColor(IFormColors.TB_TOGGLE, colors
+//                             .getColor(IFormColors.TB_TOGGLE));
+//             form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors
+//                             .getColor(IFormColors.TB_TOGGLE_HOVER));
+//             form.setSeparatorVisible(true);
+//     }
+
+       /**
+        * Creates a scrolled page book widget as a part of the form.
+        *
+        * @param parent
+        *            the page book parent
+        * @param style
+        *            the text style
+        * @return the scrolled page book widget
+        */
+//     public ScrolledPageBook createPageBook(Composite parent, int style) {
+//             ScrolledPageBook book = new ScrolledPageBook(parent, style
+//                             | orientation);
+//             adapt(book, true, true);
+//             book.setMenu(parent.getMenu());
+//             return book;
+//     }
+
+       /**
+        * Disposes the toolkit.
+        */
+       public void dispose() {
+               if (isDisposed) {
+                       return;
+               }
+               isDisposed = true;
+               if (colors.isShared() == false) {
+                       colors.dispose();
+                       colors = null;
+               }
+               boldFontHolder.dispose();
+       }
+
+       /**
+        * Returns the hyperlink group that manages hyperlinks for this toolkit.
+        *
+        * @return the hyperlink group
+        */
+//     public HyperlinkGroup getHyperlinkGroup() {
+//             return hyperlinkGroup;
+//     }
+
+       /**
+        * Sets the background color for the entire toolkit. The method delegates
+        * the call to the FormColors object and also updates the hyperlink group so
+        * that hyperlinks and other objects are in sync.
+        *
+        * @param bg
+        *            the new background color
+        */
+       public void setBackground(Color bg) {
+//             hyperlinkGroup.setBackground(bg);
+               colors.setBackground(bg);
+       }
+
+       /**
+        * Refreshes the hyperlink colors by loading from JFace settings.
+        */
+//     public void refreshHyperlinkColors() {
+//             hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay());
+//     }
+
+// RAP [rh] paintBordersFor not useful as no GC to actually paint borders
+//     /**
+//      * Paints flat borders for widgets created by this toolkit within the
+//      * provided parent. Borders will not be painted if the global border style
+//      * is SWT.BORDER (i.e. if native borders are used). Call this method during
+//      * creation of a form composite to get the borders of its children painted.
+//      * Care should be taken when selection layout margins. At least one pixel
+//      * pargin width and height must be chosen to allow the toolkit to paint the
+//      * border on the parent around the widgets.
+//      * <p>
+//      * Borders are painted for some controls that are selected by the toolkit by
+//      * default. If a control needs a border but is not on its list, it is
+//      * possible to force border in the following way:
+//      *
+//      * <pre>
+//      *
+//      *
+//      *
+//      *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
+//      *
+//      *             or
+//      *
+//      *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
+//      *
+//      *
+//      *
+//      * </pre>
+//      *
+//      * @param parent
+//      *            the parent that owns the children for which the border needs
+//      *            to be painted.
+//      */
+//     public void paintBordersFor(Composite parent) {
+//             // if (borderStyle == SWT.BORDER)
+//             // return;
+//             if (borderPainter == null)
+//                     borderPainter = new BorderPainter();
+//             parent.addPaintListener(borderPainter);
+//     }
+
+       /**
+        * Returns the colors used by this toolkit.
+        *
+        * @return the color object
+        */
+       public FormColors getColors() {
+               return colors;
+       }
+
+       /**
+        * Returns the border style used for various widgets created by this
+        * toolkit. The intent of the toolkit is to create controls with styles that
+        * yield a 'flat' appearance. On systems where the native borders are
+        * already flat, we set the style to SWT.BORDER and don't paint the borders
+        * ourselves. Otherwise, the style is set to SWT.NULL, and borders are
+        * painted by the toolkit.
+        *
+        * @return the global border style
+        */
+       public int getBorderStyle() {
+               return borderStyle;
+       }
+
+       /**
+        * Returns the margin required around the children whose border is being
+        * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since
+        * the border is painted around the controls on the parent, a number of
+        * pixels needs to be reserved for this border. For windowing systems where
+        * the native border is used, this margin is 0.
+        *
+        * @return the margin in the parent when children have their border painted
+        */
+       public int getBorderMargin() {
+               return getBorderStyle() == SWT.BORDER ? 0 : 2;
+       }
+
+       /**
+        * Sets the border style to be used when creating widgets. The toolkit
+        * chooses the correct style based on the platform but this value can be
+        * changed using this method.
+        *
+        * @param style
+        *            <code>SWT.BORDER</code> or <code>SWT.NULL</code>
+        * @see #getBorderStyle
+        */
+       public void setBorderStyle(int style) {
+               this.borderStyle = style;
+       }
+
+       /**
+        * A utility method that ensures that the control is visible in the scrolled
+        * composite. The prerequisite for this method is that the control has a
+        * class that extends ScrolledComposite somewhere in the parent chain. If
+        * the control is partially or fully clipped, the composite is scrolled to
+        * set by setting the origin to the control origin.
+        *
+        * @param c
+        *            the control to make visible
+        * @param verticalOnly
+        *            if <code>true</code>, the scrolled composite will be
+        *            scrolled only vertically if needed. Otherwise, the scrolled
+        *            composite origin will be set to the control origin.
+        */
+       public static void setControlVisible(Control c, boolean verticalOnly) {
+               ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+               if (scomp == null)
+                       return;
+               Point location = FormUtil.getControlLocation(scomp, c);
+               scomp.setOrigin(location);
+       }
+
+       private void initialize() {
+               initializeBorderStyle();
+//             hyperlinkGroup = new HyperlinkGroup(colors.getDisplay());
+//             hyperlinkGroup.setBackground(colors.getBackground());
+               visibilityHandler = new VisibilityHandler();
+               keyboardHandler = new KeyboardHandler();
+               boldFontHolder = new BoldFontHolder();
+       }
+
+// RAP [rh] revise detection of border style: can't ask OS here
+       private void initializeBorderStyle() {
+//             String osname = System.getProperty("os.name"); //$NON-NLS-1$
+//             String osversion = System.getProperty("os.version"); //$NON-NLS-1$
+//             if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$
+//                     // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista
+//                     // (6.0))
+//                     // Check for Windows Classic. If not used, set the style to BORDER
+//                     RGB rgb = colors.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+//                     if (rgb.red != 212 || rgb.green != 208 || rgb.blue != 200)
+//                             borderStyle = SWT.BORDER;
+//             } else if (osname.startsWith("Mac")) //$NON-NLS-1$
+//                     borderStyle = SWT.BORDER;
+
+               borderStyle = SWT.BORDER;
+       }
+
+       /**
+        * Returns the orientation that all the widgets created by this toolkit will
+        * inherit, if set. Can be <code>SWT.NULL</code>,
+        * <code>SWT.LEFT_TO_RIGHT</code> and <code>SWT.RIGHT_TO_LEFT</code>.
+        *
+        * @return orientation style for this toolkit, or <code>SWT.NULL</code> if
+        *         not set. The default orientation is inherited from the Window
+        *         default orientation.
+        * @see org.eclipse.jface.window.Window#getDefaultOrientation()
+        */
+
+       public int getOrientation() {
+               return orientation;
+       }
+
+       /**
+        * Sets the orientation that all the widgets created by this toolkit will
+        * inherit. Can be <code>SWT.NULL</code>, <code>SWT.LEFT_TO_RIGHT</code>
+        * and <code>SWT.RIGHT_TO_LEFT</code>.
+        *
+        * @param orientation
+        *            style for this toolkit.
+        */
+
+       public void setOrientation(int orientation) {
+               this.orientation = orientation;
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..76e3f11
--- /dev/null
@@ -0,0 +1,522 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.MouseEvent;
+//import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+//import org.eclipse.swt.graphics.Image;
+//import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Layout;
+//import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Text;
+//import org.eclipse.ui.forms.widgets.ColumnLayout;
+//import org.eclipse.ui.forms.widgets.Form;
+//import org.eclipse.ui.forms.widgets.FormText;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ILayoutExtension;
+//
+//import com.ibm.icu.text.BreakIterator;
+
+public class FormUtil {
+       public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$
+
+       static final int H_SCROLL_INCREMENT = 5;
+
+       static final int V_SCROLL_INCREMENT = 64;
+
+       public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$
+
+       public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$
+       public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$
+
+       public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$
+
+       public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$
+       
+       public static final String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$
+
+       public static Text createText(Composite parent, String label,
+                       FormToolkit factory) {
+               return createText(parent, label, factory, 1);
+       }
+
+       public static Text createText(Composite parent, String label,
+                       FormToolkit factory, int span) {
+               factory.createLabel(parent, label);
+               Text text = factory.createText(parent, ""); //$NON-NLS-1$
+               int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+                               : GridData.HORIZONTAL_ALIGN_FILL;
+               GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+               gd.horizontalSpan = span;
+               text.setLayoutData(gd);
+               return text;
+       }
+
+       public static Text createText(Composite parent, String label,
+                       FormToolkit factory, int span, int style) {
+               Label l = factory.createLabel(parent, label);
+               if ((style & SWT.MULTI) != 0) {
+                       GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
+                       l.setLayoutData(gd);
+               }
+               Text text = factory.createText(parent, "", style); //$NON-NLS-1$
+               int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+                               : GridData.HORIZONTAL_ALIGN_FILL;
+               GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+               gd.horizontalSpan = span;
+               text.setLayoutData(gd);
+               return text;
+       }
+
+       public static Text createText(Composite parent, FormToolkit factory,
+                       int span) {
+               Text text = factory.createText(parent, ""); //$NON-NLS-1$
+               int hfill = span == 1 ? GridData.FILL_HORIZONTAL
+                               : GridData.HORIZONTAL_ALIGN_FILL;
+               GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER);
+               gd.horizontalSpan = span;
+               text.setLayoutData(gd);
+               return text;
+       }
+
+       public static int computeMinimumWidth(GC gc, String text) {
+//             BreakIterator wb = BreakIterator.getWordInstance();
+//             wb.setText(text);
+//             int last = 0;
+//
+//             int width = 0;
+//
+//             for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+//                     String word = text.substring(last, loc);
+//                     Point extent = gc.textExtent(word);
+//                     width = Math.max(width, extent.x);
+//                     last = loc;
+//             }
+//             String lastWord = text.substring(last);
+//             Point extent = gc.textExtent(lastWord);
+//             width = Math.max(width, extent.x);
+//             return width;
+               return 0;
+       }
+       
+       public static Point computeWrapSize(GC gc, String text, int wHint) {    
+//             BreakIterator wb = BreakIterator.getWordInstance();
+//             wb.setText(text);
+               FontMetrics fm = gc.getFontMetrics();
+               int lineHeight = fm.getHeight();
+               
+               int saved = 0;
+               int last = 0;
+               int height = lineHeight;
+               int maxWidth = 0;
+//             for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+//                     String word = text.substring(saved, loc);
+//                     Point extent = gc.textExtent(word);
+//                     if (extent.x > wHint) {
+//                             // overflow
+//                             saved = last;
+//                             height += extent.y;
+//                             // switch to current word so maxWidth will accommodate very long single words
+//                             word = text.substring(last, loc);
+//                             extent = gc.textExtent(word);
+//                     }
+//                     maxWidth = Math.max(maxWidth, extent.x);
+//                     last = loc;
+//             }
+               /*
+                * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth.
+                * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made
+                * to estimate the height, but the algorithm needs to be run again to be sure.
+                */
+               if (maxWidth > wHint)
+                       return computeWrapSize(gc, text, maxWidth);               
+               return new Point(maxWidth, height);
+       }
+
+// RAP [rh] paintWrapText unnecessary
+//     public static void paintWrapText(GC gc, String text, Rectangle bounds) {
+//             paintWrapText(gc, text, bounds, false);
+//     }
+       
+// RAP [rh] paintWrapText unnecessary
+//     public static void paintWrapText(GC gc, String text, Rectangle bounds,
+//                     boolean underline) {
+//             BreakIterator wb = BreakIterator.getWordInstance();
+//             wb.setText(text);
+//             FontMetrics fm = gc.getFontMetrics();
+//             int lineHeight = fm.getHeight();
+//             int descent = fm.getDescent();
+//
+//             int saved = 0;
+//             int last = 0;
+//             int y = bounds.y;
+//             int width = bounds.width;
+//
+//             for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
+//                     String line = text.substring(saved, loc);
+//                     Point extent = gc.textExtent(line);
+//
+//                     if (extent.x > width) {
+//                             // overflow
+//                             String prevLine = text.substring(saved, last);
+//                             gc.drawText(prevLine, bounds.x, y, true);
+//                             if (underline) {
+//                                     Point prevExtent = gc.textExtent(prevLine);
+//                                     int lineY = y + lineHeight - descent + 1;
+//                                     gc
+//                                                     .drawLine(bounds.x, lineY, bounds.x + prevExtent.x,
+//                                                                     lineY);
+//                             }
+//
+//                             saved = last;
+//                             y += lineHeight;
+//                     }
+//                     last = loc;
+//             }
+//             // paint the last line
+//             String lastLine = text.substring(saved, last);
+//             gc.drawText(lastLine, bounds.x, y, true);
+//             if (underline) {
+//                     int lineY = y + lineHeight - descent + 1;
+//                     Point lastExtent = gc.textExtent(lastLine);
+//                     gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY);
+//             }
+//     }
+
+       public static ScrolledComposite getScrolledComposite(Control c) {
+               Composite parent = c.getParent();
+
+               while (parent != null) {
+                       if (parent instanceof ScrolledComposite) {
+                               return (ScrolledComposite) parent;
+                       }
+                       parent = parent.getParent();
+               }
+               return null;
+       }
+
+       public static void ensureVisible(Control c) {
+               ScrolledComposite scomp = getScrolledComposite(c);
+               if (scomp != null) {
+                       Object data = scomp.getData(FOCUS_SCROLLING);
+                       if (data == null || !data.equals(Boolean.FALSE))
+                               FormUtil.ensureVisible(scomp, c);
+               }
+       }
+
+       public static void ensureVisible(ScrolledComposite scomp, Control control) {
+               // if the control is a FormText we do not need to scroll since it will
+               // ensure visibility of its segments as necessary
+//             if (control instanceof FormText)
+//                     return;
+               Point controlSize = control.getSize();
+               Point controlOrigin = getControlLocation(scomp, control);
+               ensureVisible(scomp, controlOrigin, controlSize);
+       }
+
+       public static void ensureVisible(ScrolledComposite scomp,
+                       Point controlOrigin, Point controlSize) {
+               Rectangle area = scomp.getClientArea();
+               Point scompOrigin = scomp.getOrigin();
+
+               int x = scompOrigin.x;
+               int y = scompOrigin.y;
+
+               // horizontal right, but only if the control is smaller
+               // than the client area
+               if (controlSize.x < area.width
+                               && (controlOrigin.x + controlSize.x > scompOrigin.x
+                                               + area.width)) {
+                       x = controlOrigin.x + controlSize.x - area.width;
+               }
+               // horizontal left - make sure the left edge of
+               // the control is showing
+               if (controlOrigin.x < x) {
+                       if (controlSize.x < area.width)
+                               x = controlOrigin.x + controlSize.x - area.width;
+                       else
+                               x = controlOrigin.x;
+               }
+               // vertical bottom
+               if (controlSize.y < area.height
+                               && (controlOrigin.y + controlSize.y > scompOrigin.y
+                                               + area.height)) {
+                       y = controlOrigin.y + controlSize.y - area.height;
+               }
+               // vertical top - make sure the top of
+               // the control is showing
+               if (controlOrigin.y < y) {
+                       if (controlSize.y < area.height)
+                               y = controlOrigin.y + controlSize.y - area.height;
+                       else
+                               y = controlOrigin.y;
+               }
+
+               if (scompOrigin.x != x || scompOrigin.y != y) {
+                       // scroll to reveal
+                       scomp.setOrigin(x, y);
+               }
+       }
+
+       public static void ensureVisible(ScrolledComposite scomp, Control control,
+                       MouseEvent e) {
+               Point controlOrigin = getControlLocation(scomp, control);
+               int rX = controlOrigin.x + e.x;
+               int rY = controlOrigin.y + e.y;
+               Rectangle area = scomp.getClientArea();
+               Point scompOrigin = scomp.getOrigin();
+
+               int x = scompOrigin.x;
+               int y = scompOrigin.y;
+               // System.out.println("Ensure: area="+area+", origin="+scompOrigin+",
+               // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y);
+
+               // horizontal right
+               if (rX > scompOrigin.x + area.width) {
+                       x = rX - area.width;
+               }
+               // horizontal left
+               else if (rX < x) {
+                       x = rX;
+               }
+               // vertical bottom
+               if (rY > scompOrigin.y + area.height) {
+                       y = rY - area.height;
+               }
+               // vertical top
+               else if (rY < y) {
+                       y = rY;
+               }
+
+               if (scompOrigin.x != x || scompOrigin.y != y) {
+                       // scroll to reveal
+                       scomp.setOrigin(x, y);
+               }
+       }
+
+       public static Point getControlLocation(ScrolledComposite scomp,
+                       Control control) {
+               int x = 0;
+               int y = 0;
+               Control content = scomp.getContent();
+               Control currentControl = control;
+               for (;;) {
+                       if (currentControl == content)
+                               break;
+                       Point location = currentControl.getLocation();
+                       // if (location.x > 0)
+                       // x += location.x;
+                       // if (location.y > 0)
+                       // y += location.y;
+                       x += location.x;
+                       y += location.y;
+                       currentControl = currentControl.getParent();
+               }
+               return new Point(x, y);
+       }
+
+       static void scrollVertical(ScrolledComposite scomp, boolean up) {
+               scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT);
+       }
+
+       static void scrollHorizontal(ScrolledComposite scomp, boolean left) {
+               scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0);
+       }
+
+       static void scrollPage(ScrolledComposite scomp, boolean up) {
+               Rectangle clientArea = scomp.getClientArea();
+               int increment = up ? -clientArea.height : clientArea.height;
+               scroll(scomp, 0, increment);
+       }
+
+       static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) {
+               Point origin = scomp.getOrigin();
+               Point contentSize = scomp.getContent().getSize();
+               int xorigin = origin.x + xoffset;
+               int yorigin = origin.y + yoffset;
+               xorigin = Math.max(xorigin, 0);
+               xorigin = Math.min(xorigin, contentSize.x - 1);
+               yorigin = Math.max(yorigin, 0);
+               yorigin = Math.min(yorigin, contentSize.y - 1);
+               scomp.setOrigin(xorigin, yorigin);
+       }
+
+// RAP [rh] FormUtil#updatePageIncrement: empty implementation
+       public static void updatePageIncrement(ScrolledComposite scomp) {
+//             ScrollBar vbar = scomp.getVerticalBar();
+//             if (vbar != null) {
+//                     Rectangle clientArea = scomp.getClientArea();
+//                     int increment = clientArea.height - 5;
+//                     vbar.setPageIncrement(increment);
+//             }
+//             ScrollBar hbar = scomp.getHorizontalBar();
+//             if (hbar != null) {
+//                     Rectangle clientArea = scomp.getClientArea();
+//                     int increment = clientArea.width - 5;
+//                     hbar.setPageIncrement(increment);
+//             }
+       }
+
+       public static void processKey(int keyCode, Control c) {
+               if (c.isDisposed()) {
+                       return;
+               }
+               ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+               if (scomp != null) {
+                       if (c instanceof Combo)
+                               return;
+                       switch (keyCode) {
+                       case SWT.ARROW_DOWN:
+                               if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+                                       FormUtil.scrollVertical(scomp, false);
+                               break;
+                       case SWT.ARROW_UP:
+                               if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+                                       FormUtil.scrollVertical(scomp, true);
+                               break;
+                       case SWT.ARROW_LEFT:
+                               FormUtil.scrollHorizontal(scomp, true);
+                               break;
+                       case SWT.ARROW_RIGHT:
+                               FormUtil.scrollHorizontal(scomp, false);
+                               break;
+                       case SWT.PAGE_UP:
+                               FormUtil.scrollPage(scomp, true);
+                               break;
+                       case SWT.PAGE_DOWN:
+                               FormUtil.scrollPage(scomp, false);
+                               break;
+                       }
+               }
+       }
+
+       public static boolean isWrapControl(Control c) {
+               if ((c.getStyle() & SWT.WRAP) != 0)
+                       return true;
+               if (c instanceof Composite) {
+                       return false;
+//                     return ((Composite) c).getLayout() instanceof ILayoutExtension;
+               }
+               return false;
+       }
+
+       public static int getWidthHint(int wHint, Control c) {
+               boolean wrap = isWrapControl(c);
+               return wrap ? wHint : SWT.DEFAULT;
+       }
+
+       public static int getHeightHint(int hHint, Control c) {
+               if (c instanceof Composite) {
+                       Layout layout = ((Composite) c).getLayout();
+//                     if (layout instanceof ColumnLayout)
+//                             return hHint;
+               }
+               return SWT.DEFAULT;
+       }
+
+       public static int computeMinimumWidth(Control c, boolean changed) {
+               if (c instanceof Composite) {
+                       Layout layout = ((Composite) c).getLayout();
+//                     if (layout instanceof ILayoutExtension)
+//                             return ((ILayoutExtension) layout).computeMinimumWidth(
+//                                             (Composite) c, changed);
+               }
+               return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x;
+       }
+
+       public static int computeMaximumWidth(Control c, boolean changed) {
+               if (c instanceof Composite) {
+                       Layout layout = ((Composite) c).getLayout();
+//                     if (layout instanceof ILayoutExtension)
+//                             return ((ILayoutExtension) layout).computeMaximumWidth(
+//                                             (Composite) c, changed);
+               }
+               return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x;
+       }
+
+//     public static Form getForm(Control c) {
+//             Composite parent = c.getParent();
+//             while (parent != null) {
+//                     if (parent instanceof Form) {
+//                             return (Form) parent;
+//                     }
+//                     parent = parent.getParent();
+//             }
+//             return null;
+//     }
+
+// RAP [rh] FormUtil#createAlphaMashImage unnecessary  
+//     public static Image createAlphaMashImage(Device device, Image srcImage) {
+//             Rectangle bounds = srcImage.getBounds();
+//             int alpha = 0;
+//             int calpha = 0;
+//             ImageData data = srcImage.getImageData();
+//             // Create a new image with alpha values alternating
+//             // between fully transparent (0) and fully opaque (255).
+//             // This image will show the background through the
+//             // transparent pixels.
+//             for (int i = 0; i < bounds.height; i++) {
+//                     // scan line
+//                     alpha = calpha;
+//                     for (int j = 0; j < bounds.width; j++) {
+//                             // column
+//                             data.setAlpha(j, i, alpha);
+//                             alpha = alpha == 255 ? 0 : 255;
+//                     }
+//                     calpha = calpha == 255 ? 0 : 255;
+//             }
+//             return new Image(device, data);
+//     }
+
+       public static boolean mnemonicMatch(String text, char key) {
+               char mnemonic = findMnemonic(text);
+               if (mnemonic == '\0')
+                       return false;
+               return Character.toUpperCase(key) == Character.toUpperCase(mnemonic);
+       }
+
+       private static char findMnemonic(String string) {
+               int index = 0;
+               int length = string.length();
+               do {
+                       while (index < length && string.charAt(index) != '&')
+                               index++;
+                       if (++index >= length)
+                               return '\0';
+                       if (string.charAt(index) != '&')
+                               return string.charAt(index);
+                       index++;
+               } while (index < length);
+               return '\0';
+       }
+       
+       public static void setFocusScrollingEnabled(Control c, boolean enabled) {
+               ScrolledComposite scomp = null;
+               
+               if (c instanceof ScrolledComposite)
+                       scomp = (ScrolledComposite)c;
+               else
+                       scomp = getScrolledComposite(c);
+               if (scomp!=null)
+                       scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE);
+       }
+       
+       // RAP [rh] FormUtil#setAntialias unnecessary
+//     public static void setAntialias(GC gc, int style) {
+//             if (!gc.getAdvanced()) {
+//                     gc.setAdvanced(true);
+//                     if (!gc.getAdvanced())
+//                             return;
+//             }
+//             gc.setAntialias(style);
+//     }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..cf0e5d3
--- /dev/null
@@ -0,0 +1,102 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+/**
+ * A place to hold all the color constants used in the forms package.
+ * 
+ * @since 1.0
+ */
+
+public interface IFormColors {
+       /**
+        * A prefix for all the keys.
+        */
+       String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$
+       /**
+        * Key for the form title foreground color.
+        */
+       String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$
+
+       /**
+        * A prefix for the header color constants.
+        */
+       String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$
+       /*
+        * A prefix for the section title bar color constants.
+        */
+       String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$        
+       /**
+        * Key for the form header background gradient ending color.
+        */
+       String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$
+
+       /**
+        * Key for the form header background gradient starting color.
+        * 
+        */
+       String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$
+       /**
+        * Key for the form header bottom keyline 1 color.
+        * 
+        */
+       String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$
+       /**
+        * Key for the form header bottom keyline 2 color.
+        * 
+        */
+       String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$
+       /**
+        * Key for the form header light hover color.
+        * 
+        */
+       String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$
+       /**
+        * Key for the form header full hover color.
+        * 
+        */
+       String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$
+
+       /**
+        * Key for the tree/table border color.
+        */
+       String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$
+
+       /**
+        * Key for the section separator color.
+        */
+       String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$
+
+       /**
+        * Key for the section title bar background.
+        */
+       String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$
+
+       /**
+        * Key for the section title bar foreground.
+        */
+       String TB_FG = TB_PREFIX + "FG"; //$NON-NLS-1$
+
+       /**
+        * Key for the section title bar gradient.
+        * @deprecated Since 3.3, this color is not used any more. The 
+        * tool bar gradient is created starting from {@link #TB_BG} to
+        * the section background color.
+        */
+       String TB_GBG = TB_BG;
+
+       /**
+        * Key for the section title bar border.
+        */
+       String TB_BORDER = TB_PREFIX + "BORDER"; //$NON-NLS-1$
+
+       /**
+        * Key for the section toggle color. Since 3.1, this color is used for all
+        * section styles.
+        */
+       String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$
+
+       /**
+        * Key for the section toggle hover color.
+        * 
+        */
+       String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$              
+}
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..954cc03
--- /dev/null
@@ -0,0 +1,108 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+/**
+ * Classes that implement this interface can be added to the managed form and
+ * take part in the form life cycle. The part is initialized with the form and
+ * will be asked to accept focus. The part can receive form input and can elect
+ * to do something according to it (for example, select an object that matches
+ * the input).
+ * <p>
+ * The form part has two 'out of sync' states in respect to the model(s) that
+ * feed the form: <b>dirty</b> and <b>stale</b>. When a part is dirty, it
+ * means that the user interacted with it and now its widgets contain state that
+ * is newer than the model. In order to sync up with the model, 'commit' needs
+ * to be called. In contrast, the model can change 'under' the form (as a result
+ * of some actions outside the form), resulting in data in the model being
+ * 'newer' than the content presented in the form. A 'stale' form part is
+ * brought in sync with the model by calling 'refresh'. The part is responsible
+ * for notifying the form when one of these states change in the part. The form
+ * reserves the right to handle this notification in the most appropriate way
+ * for the situation (for example, if the form is in a page of the multi-page
+ * editor, it may do nothing for stale parts if the page is currently not
+ * showing).
+ * <p>
+ * When the form is disposed, each registered part is disposed as well. Parts
+ * are responsible for releasing any system resources they created and for
+ * removing themselves as listeners from all event providers.
+ * 
+ * @see IManagedForm
+ * @since 1.0
+ * 
+ */
+public interface IFormPart {
+       /**
+        * Initializes the part.
+        * 
+        * @param form
+        *            the managed form that manages the part
+        */
+       void initialize(IManagedForm form);
+
+       /**
+        * Disposes the part allowing it to release allocated resources.
+        */
+       void dispose();
+
+       /**
+        * Returns true if the part has been modified with respect to the data
+        * loaded from the model.
+        * 
+        * @return true if the part has been modified with respect to the data
+        *         loaded from the model
+        */
+       boolean isDirty();
+
+       /**
+        * If part is displaying information loaded from a model, this method
+        * instructs it to commit the new (modified) data back into the model.
+        * 
+        * @param onSave
+        *            indicates if commit is called during 'save' operation or for
+        *            some other reason (for example, if form is contained in a
+        *            wizard or a multi-page editor and the user is about to leave
+        *            the page).
+        */
+       void commit(boolean onSave);
+
+       /**
+        * Notifies the part that an object has been set as overall form's input.
+        * The part can elect to react by revealing or selecting the object, or do
+        * nothing if not applicable.
+        * 
+        * @return <code>true</code> if the part has selected and revealed the
+        *         input object, <code>false</code> otherwise.
+        */
+       boolean setFormInput(Object input);
+
+       /**
+        * Instructs form part to transfer focus to the widget that should has focus
+        * in that part. The method can do nothing (if it has no widgets capable of
+        * accepting focus).
+        */
+       void setFocus();
+
+       /**
+        * Tests whether the form part is stale and needs refreshing. Parts can
+        * receive notification from models that will make their content stale, but
+        * may need to delay refreshing to improve performance (for example, there
+        * is no need to immediately refresh a part on a form that is current on a
+        * hidden page).
+        * <p>
+        * It is important to differentiate 'stale' and 'dirty' states. Part is
+        * 'dirty' if user interacted with its editable widgets and changed the
+        * values. In contrast, part is 'stale' when the data it presents in the
+        * widgets has been changed in the model without direct user interaction.
+        * 
+        * @return <code>true</code> if the part needs refreshing,
+        *         <code>false</code> otherwise.
+        */
+       boolean isStale();
+
+       /**
+        * Refreshes the part completely from the information freshly obtained from
+        * the model. The method will not be called if the part is not stale.
+        * Otherwise, the part is responsible for clearing the 'stale' flag after
+        * refreshing itself.
+        */
+       void refresh();
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..490d3a3
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.ScrolledComposite;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is no 1/1 mapping between widgets and form parts. A widget like Section
+ * can be a part by itself, but a number of widgets can gather around one form
+ * part.
+ * <p>
+ * This interface should not be extended or implemented. New form instances
+ * should be created using ManagedForm.
+ * 
+ * @see ManagedForm
+ * @since 1.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface IManagedForm {
+       /**
+        * Initializes the form by looping through the managed parts and
+        * initializing them. Has no effect if already called once.
+        */
+       public void initialize();
+
+       /**
+        * Returns the toolkit used by this form.
+        * 
+        * @return the toolkit
+        */
+       public FormToolkit getToolkit();
+
+       /**
+        * Returns the form widget managed by this form.
+        * 
+        * @return the form widget
+        */
+       public ScrolledComposite getForm();
+
+       /**
+        * Reflows the form as a result of the layout change.
+        * 
+        * @param changed
+        *            if <code>true</code>, discard cached layout information
+        */
+       public void reflow(boolean changed);
+
+       /**
+        * A part can use this method to notify other parts that implement
+        * IPartSelectionListener about selection changes.
+        * 
+        * @param part
+        *            the part that broadcasts the selection
+        * @param selection
+        *            the selection in the part
+        */
+       public void fireSelectionChanged(IFormPart part, ISelection selection);
+
+       /**
+        * Returns all the parts currently managed by this form.
+        * 
+        * @return the managed parts
+        */
+       IFormPart[] getParts();
+
+       /**
+        * Adds the new part to the form.
+        * 
+        * @param part
+        *            the part to add
+        */
+       void addPart(IFormPart part);
+
+       /**
+        * Removes the part from the form.
+        * 
+        * @param part
+        *            the part to remove
+        */
+       void removePart(IFormPart part);
+
+       /**
+        * Sets the input of this page to the provided object.
+        * 
+        * @param input
+        *            the new page input
+        * @return <code>true</code> if the form contains this object,
+        *         <code>false</code> otherwise.
+        */
+       boolean setInput(Object input);
+
+       /**
+        * Returns the current page input.
+        * 
+        * @return page input object or <code>null</code> if not applicable.
+        */
+       Object getInput();
+
+       /**
+        * Tests if form is dirty. A managed form is dirty if at least one managed
+        * part is dirty.
+        * 
+        * @return <code>true</code> if at least one managed part is dirty,
+        *         <code>false</code> otherwise.
+        */
+       boolean isDirty();
+
+       /**
+        * Notifies the form that the dirty state of one of its parts has changed.
+        * The global dirty state of the form can be obtained by calling 'isDirty'.
+        * 
+        * @see #isDirty
+        */
+       void dirtyStateChanged();
+
+       /**
+        * Commits the dirty form. All pending changes in the widgets are flushed
+        * into the model.
+        * 
+        * @param onSave
+        */
+       void commit(boolean onSave);
+
+       /**
+        * Tests if form is stale. A managed form is stale if at least one managed
+        * part is stale. This can happen when the underlying model changes,
+        * resulting in the presentation of the part being out of sync with the
+        * model and needing refreshing.
+        * 
+        * @return <code>true</code> if the form is stale, <code>false</code>
+        *         otherwise.
+        */
+       boolean isStale();
+
+       /**
+        * Notifies the form that the stale state of one of its parts has changed.
+        * The global stale state of the form can be obtained by calling 'isStale'.
+        */
+       void staleStateChanged();
+
+       /**
+        * Refreshes the form by refreshing every part that is stale.
+        */
+       void refresh();
+
+       /**
+        * Sets the container that owns this form. Depending on the context, the
+        * container may be wizard, editor page, editor etc.
+        * 
+        * @param container
+        *            the container of this form
+        */
+       void setContainer(Object container);
+
+       /**
+        * Returns the container of this form.
+        * 
+        * @return the form container
+        */
+       Object getContainer();
+
+       /**
+        * Returns the message manager that will keep track of messages in this
+        * form.
+        * 
+        * @return the message manager instance
+        */
+//     IMessageManager getMessageManager();
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..0f557d4
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import org.eclipse.jface.viewers.ISelection;
+
+/**
+ * Form parts can implement this interface if they want to be 
+ * notified when another part on the same form changes selection 
+ * state.
+ * 
+ * @see IFormPart
+ * @since 1.0
+ */
+public interface IPartSelectionListener {
+       /**
+        * Called when the provided part has changed selection state.
+        * 
+        * @param part
+        *            the selection source
+        * @param selection
+        *            the new selection
+        */
+       public void selectionChanged(IFormPart part, ISelection selection);
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..4140465
--- /dev/null
@@ -0,0 +1,323 @@
+package org.argeo.cms.ui.eclipse.forms;
+
+import java.util.Vector;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.widgets.Composite;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * Managed form wraps a form widget and adds life cycle methods for form parts.
+ * A form part is a portion of the form that participates in form life cycle
+ * events.
+ * <p>
+ * There is requirement for 1/1 mapping between widgets and form parts. A widget
+ * like Section can be a part by itself, but a number of widgets can join around
+ * one form part.
+ * <p>
+ * Note to developers: this class is left public to allow its use beyond the
+ * original intention (inside a multi-page editor's page). You should limit the
+ * use of this class to make new instances inside a form container (wizard page,
+ * dialog etc.). Clients that need access to the class should not do it
+ * directly. Instead, they should do it through IManagedForm interface as much
+ * as possible.
+ * 
+ * @since 1.0
+ */
+public class ManagedForm implements IManagedForm {
+       private Object input;
+
+       private ScrolledComposite form;
+
+       private FormToolkit toolkit;
+
+       private Object container;
+
+       private boolean ownsToolkit;
+
+       private boolean initialized;
+
+       private Vector parts = new Vector();
+
+       /**
+        * Creates a managed form in the provided parent. Form toolkit and widget
+        * will be created and owned by this object.
+        * 
+        * @param parent
+        *            the parent widget
+        */
+       public ManagedForm(Composite parent) {
+               toolkit = new FormToolkit(parent.getDisplay());
+               ownsToolkit = true;
+               form = toolkit.createScrolledForm(parent);
+               
+       }
+
+       /**
+        * Creates a managed form that will use the provided toolkit and
+        * 
+        * @param toolkit
+        * @param form
+        */
+       public ManagedForm(FormToolkit toolkit, ScrolledComposite form) {
+               this.form = form;
+               this.toolkit = toolkit;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#addPart(org.eclipse.ui.forms.IFormPart)
+        */
+       public void addPart(IFormPart part) {
+               parts.add(part);
+               part.initialize(this);
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#removePart(org.eclipse.ui.forms.IFormPart)
+        */
+       public void removePart(IFormPart part) {
+               parts.remove(part);
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#getParts()
+        */
+       public IFormPart[] getParts() {
+               return (IFormPart[]) parts.toArray(new IFormPart[parts.size()]);
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#getToolkit()
+        */
+       public FormToolkit getToolkit() {
+               return toolkit;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#getForm()
+        */
+       public ScrolledComposite getForm() {
+               return form;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#reflow(boolean)
+        */
+       public void reflow(boolean changed) {
+//             form.reflow(changed);
+       }
+
+       /**
+        * A part can use this method to notify other parts that implement
+        * IPartSelectionListener about selection changes.
+        * 
+        * @param part
+        *            the part that broadcasts the selection
+        * @param selection
+        *            the selection in the part
+        * @see IPartSelectionListener
+        */
+       public void fireSelectionChanged(IFormPart part, ISelection selection) {
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart cpart = (IFormPart) parts.get(i);
+                       if (part.equals(cpart))
+                               continue;
+//                     if (cpart instanceof IPartSelectionListener) {
+//                             ((IPartSelectionListener) cpart).selectionChanged(part,
+//                                             selection);
+//                     }
+               }
+       }
+
+       /**
+        * Initializes the form by looping through the managed parts and
+        * initializing them. Has no effect if already called once.
+        */
+       public void initialize() {
+               if (initialized)
+                       return;
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       part.initialize(this);
+               }
+               initialized = true;
+       }
+
+       /**
+        * Disposes all the parts in this form.
+        */
+       public void dispose() {
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       part.dispose();
+               }
+               if (ownsToolkit) {
+                       toolkit.dispose();
+               }
+       }
+
+       /**
+        * Refreshes the form by refreshes all the stale parts. Since 3.1, this
+        * method is performed on a UI thread when called from another thread so it
+        * is not needed to wrap the call in <code>Display.syncExec</code> or
+        * <code>asyncExec</code>.
+        */
+       public void refresh() {
+               Thread t = Thread.currentThread();
+               Thread dt = toolkit.getColors().getDisplay().getThread();
+               if (t.equals(dt))
+                       doRefresh();
+               else {
+                       toolkit.getColors().getDisplay().asyncExec(new Runnable() {
+                               public void run() {
+                                       doRefresh();
+                               }
+                       });
+               }
+       }
+
+       private void doRefresh() {
+               int nrefreshed = 0;
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       if (part.isStale()) {
+                               part.refresh();
+                               nrefreshed++;
+                       }
+               }
+//             if (nrefreshed > 0)
+//                     form.reflow(true);
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#commit(boolean)
+        */
+       public void commit(boolean onSave) {
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       if (part.isDirty())
+                               part.commit(onSave);
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object)
+        */
+       public boolean setInput(Object input) {
+               boolean pageResult = false;
+
+               this.input = input;
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       boolean result = part.setFormInput(input);
+                       if (result)
+                               pageResult = true;
+               }
+               return pageResult;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#getInput()
+        */
+       public Object getInput() {
+               return input;
+       }
+
+       /**
+        * Transfers the focus to the first form part.
+        */
+       public void setFocus() {
+               if (parts.size() > 0) {
+                       IFormPart part = (IFormPart) parts.get(0);
+                       part.setFocus();
+               }
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#isDirty()
+        */
+       public boolean isDirty() {
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       if (part.isDirty())
+                               return true;
+               }
+               return false;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#isStale()
+        */
+       public boolean isStale() {
+               for (int i = 0; i < parts.size(); i++) {
+                       IFormPart part = (IFormPart) parts.get(i);
+                       if (part.isStale())
+                               return true;
+               }
+               return false;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged()
+        */
+       public void dirtyStateChanged() {
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged()
+        */
+       public void staleStateChanged() {
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#getContainer()
+        */
+       public Object getContainer() {
+               return container;
+       }
+
+       /*
+        * (non-Javadoc)
+        * 
+        * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object)
+        */
+       public void setContainer(Object container) {
+               this.container = container;
+       }
+
+       /* (non-Javadoc)
+        * @see org.eclipse.ui.forms.IManagedForm#getMessageManager()
+        */
+//     public IMessageManager getMessageManager() {
+//             return form.getMessageManager();
+//     }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..7fa00d9
--- /dev/null
@@ -0,0 +1,89 @@
+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
new file mode 100644 (file)
index 0000000..1511cf3
--- /dev/null
@@ -0,0 +1,277 @@
+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
new file mode 100644 (file)
index 0000000..eb08cb5
--- /dev/null
@@ -0,0 +1,119 @@
+package org.argeo.cms.ui.eclipse.forms.editor;
+import org.argeo.cms.ui.eclipse.forms.IManagedForm;
+import org.eclipse.swt.widgets.Control;
+/**
+ * Interface that all GUI pages need to implement in order
+ * to be added to FormEditor part. The interface makes 
+ * several assumptions:
+ * <ul>
+ * <li>The form page has a managed form</li>
+ * <li>The form page has a unique id</li>
+ * <li>The form page can be GUI but can also wrap a complete
+ * editor class (in that case, it should return <code>true</code>
+ * from <code>isEditor()</code> method).</li>
+ * <li>The form page is lazy i.e. understands that 
+ * its part control will be created at the last possible
+ * moment.</li>.
+ * </ul>
+ * <p>Existing editors can be wrapped by implementing
+ * this interface. In this case, 'isEditor' should return <code>true</code>.
+ * A common editor to wrap in <code>TextEditor</code> that is
+ * often added to show the raw source code of the file open into
+ * the multi-page editor.
+ * 
+ * @since 1.0
+ */
+public interface IFormPage {
+       /**
+        * @param editor
+        *            the form editor that this page belongs to
+        */
+       void initialize(FormEditor editor);
+       /**
+        * Returns the editor this page belongs to.
+        * 
+        * @return the form editor
+        */
+       FormEditor getEditor();
+       /**
+        * Returns the managed form of this page, unless this is a source page.
+        * 
+        * @return the managed form or <samp>null </samp> if this is a source page.
+        */
+       IManagedForm getManagedForm();
+       /**
+        * Indicates whether the page has become the active in the editor. Classes
+        * that implement this interface may use this method to commit the page (on
+        * <code>false</code>) or lazily create and/or populate the content on
+        * <code>true</code>.
+        * 
+        * @param active
+        *            <code>true</code> if page should be visible, <code>false</code>
+        *            otherwise.
+        */
+       void setActive(boolean active);
+       /**
+        * Returns <samp>true </samp> if page is currently active, false if not.
+        * 
+        * @return <samp>true </samp> for active page.
+        */
+       boolean isActive();
+       /**
+        * Tests if the content of the page is in a state that allows the
+        * editor to flip to another page. Typically, pages that contain
+        * raw source with syntax errors should not allow editors to 
+        * leave them until errors are corrected.
+        * @return <code>true</code> if the editor can flip to another page,
+        * <code>false</code> otherwise.
+        */
+       boolean canLeaveThePage();
+       /**
+        * Returns the control associated with this page.
+        * 
+        * @return the control of this page if created or <samp>null </samp> if the
+        *         page has not been shown yet.
+        */
+       Control getPartControl();
+       /**
+        * Page must have a unique id that can be used to show it without knowing
+        * its relative position in the editor.
+        * 
+        * @return the unique page identifier
+        */
+       String getId();
+       /**
+        * Returns the position of the page in the editor.
+        * 
+        * @return the zero-based index of the page in the editor.
+        */
+       int getIndex();
+       /**
+        * Sets the position of the page in the editor.
+        * 
+        * @param index
+        *            the zero-based index of the page in the editor.
+        */
+       void setIndex(int index);
+       /**
+        * Tests whether this page wraps a complete editor that
+        * can be registered on its own, or represents a page
+        * that cannot exist outside the multi-page editor context.
+        * 
+        * @return <samp>true </samp> if the page wraps an editor,
+        *         <samp>false </samp> if this is a form page.
+        */
+       boolean isEditor();
+       /**
+        * A hint to bring the provided object into focus. If the object is in a
+        * tree or table control, select it. If it is shown on a scrollable page,
+        * ensure that it is visible. If the object is not presented in 
+        * the page, <code>false</code> should be returned to allow another
+        * page to try.
+        * 
+        * @param object
+        *            object to select and reveal
+        * @return <code>true</code> if the request was successful, <code>false</code>
+        *         otherwise.
+        */
+       boolean selectReveal(Object object);
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..e74de5e
--- /dev/null
@@ -0,0 +1,75 @@
+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
new file mode 100644 (file)
index 0000000..fd3f48e
--- /dev/null
@@ -0,0 +1,261 @@
+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
new file mode 100644 (file)
index 0000000..8591a92
--- /dev/null
@@ -0,0 +1,298 @@
+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
new file mode 100644 (file)
index 0000000..0920093
--- /dev/null
@@ -0,0 +1,80 @@
+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
new file mode 100644 (file)
index 0000000..fe9f7e7
--- /dev/null
@@ -0,0 +1,7 @@
+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
new file mode 100644 (file)
index 0000000..f3a56f7
--- /dev/null
@@ -0,0 +1,114 @@
+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
new file mode 100644 (file)
index 0000000..cc732d4
--- /dev/null
@@ -0,0 +1,608 @@
+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
new file mode 100644 (file)
index 0000000..24067ea
--- /dev/null
@@ -0,0 +1,29 @@
+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
new file mode 100644 (file)
index 0000000..1a445bd
--- /dev/null
@@ -0,0 +1,196 @@
+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
new file mode 100644 (file)
index 0000000..3f588d1
--- /dev/null
@@ -0,0 +1,169 @@
+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
new file mode 100644 (file)
index 0000000..5f954c1
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..5a5ecdb
--- /dev/null
@@ -0,0 +1,524 @@
+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
new file mode 100644 (file)
index 0000000..e875b5a
--- /dev/null
@@ -0,0 +1,37 @@
+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
new file mode 100644 (file)
index 0000000..c548e2a
--- /dev/null
@@ -0,0 +1,383 @@
+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
new file mode 100644 (file)
index 0000000..9ae3192
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..6a6c272
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..e10da3a
--- /dev/null
@@ -0,0 +1,37 @@
+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
new file mode 100644 (file)
index 0000000..44885b1
--- /dev/null
@@ -0,0 +1,81 @@
+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
new file mode 100644 (file)
index 0000000..c8582f0
--- /dev/null
@@ -0,0 +1,74 @@
+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
new file mode 100644 (file)
index 0000000..c5c1a01
--- /dev/null
@@ -0,0 +1,72 @@
+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
new file mode 100644 (file)
index 0000000..3806341
--- /dev/null
@@ -0,0 +1,75 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Observable;
+import java.util.TreeMap;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+
+public class DefaultRepositoryRegister extends Observable implements RepositoryRegister {
+       /** Key for a JCR repository alias */
+       private final static String CN = CmsConstants.CN;
+       /** Key for a JCR repository URI */
+       // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
+       private final static CmsLog log = CmsLog.getLog(DefaultRepositoryRegister.class);
+
+       /** Read only map which will be directly exposed. */
+       private Map<String, Repository> repositories = Collections.unmodifiableMap(new TreeMap<String, Repository>());
+
+       @SuppressWarnings("rawtypes")
+       public synchronized Repository getRepository(Map parameters) throws RepositoryException {
+               if (!parameters.containsKey(CN))
+                       throw new RepositoryException("Parameter " + CN + " has to be defined.");
+               String alias = parameters.get(CN).toString();
+               if (!repositories.containsKey(alias))
+                       throw new RepositoryException("No repository registered with alias " + alias);
+
+               return repositories.get(alias);
+       }
+
+       /** Access to the read-only map */
+       public synchronized Map<String, Repository> getRepositories() {
+               return repositories;
+       }
+
+       /** Registers a service, typically called when OSGi services are bound. */
+       @SuppressWarnings("rawtypes")
+       public synchronized void register(Repository repository, Map properties) {
+               String alias;
+               if (properties == null || !properties.containsKey(CN)) {
+                       log.warn("Cannot register a repository if no " + CN + " property is specified.");
+                       return;
+               }
+               alias = properties.get(CN).toString();
+               Map<String, Repository> map = new TreeMap<String, Repository>(repositories);
+               map.put(alias, repository);
+               repositories = Collections.unmodifiableMap(map);
+               setChanged();
+               notifyObservers(alias);
+       }
+
+       /** Unregisters a service, typically called when OSGi services are unbound. */
+       @SuppressWarnings("rawtypes")
+       public synchronized void unregister(Repository repository, Map properties) {
+               // TODO: also check bean name?
+               if (properties == null || !properties.containsKey(CN)) {
+                       log.warn("Cannot unregister a repository without property " + CN);
+                       return;
+               }
+
+               String alias = properties.get(CN).toString();
+               Map<String, Repository> map = new TreeMap<String, Repository>(repositories);
+               if (map.remove(alias) == null) {
+                       log.warn("No repository was registered with alias " + alias);
+                       return;
+               }
+               repositories = Collections.unmodifiableMap(map);
+               setChanged();
+               notifyObservers(alias);
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..0f7ee77
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Display some version information of a JCR full versionable node in a tree
+ * like structure
+ */
+public class FullVersioningTreeContentProvider implements ITreeContentProvider {
+       private static final long serialVersionUID = 8691772509491211112L;
+
+       /**
+        * Sends back the first level of the Tree. input element must be a single
+        * node object
+        */
+       public Object[] getElements(Object inputElement) {
+               try {
+                       Node rootNode = (Node) inputElement;
+                       String curPath = rootNode.getPath();
+                       VersionManager vm = rootNode.getSession().getWorkspace()
+                                       .getVersionManager();
+
+                       VersionHistory vh = vm.getVersionHistory(curPath);
+                       List<Version> result = new ArrayList<Version>();
+                       VersionIterator vi = vh.getAllLinearVersions();
+
+                       while (vi.hasNext()) {
+                               result.add(vi.nextVersion());
+                       }
+                       return result.toArray();
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException(
+                                       "Unexpected error while getting version elements", re);
+               }
+       }
+
+       public Object[] getChildren(Object parentElement) {
+               try {
+                       if (parentElement instanceof Version) {
+                               List<Node> tmp = new ArrayList<Node>();
+                               tmp.add(((Version) parentElement).getFrozenNode());
+                               return tmp.toArray();
+                       }
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException("Unexpected error while getting child "
+                                       + "node for version element", re);
+               }
+               return null;
+       }
+
+       public Object getParent(Object element) {
+               try {
+                       // this will not work in a simpleVersionning environment, parent is
+                       // not a node.
+                       if (element instanceof Node
+                                       && ((Node) element).isNodeType(NodeType.NT_FROZEN_NODE)) {
+                               Node node = (Node) element;
+                               return node.getParent();
+                       } else
+                               return null;
+               } catch (RepositoryException e) {
+                       return null;
+               }
+       }
+
+       public boolean hasChildren(Object element) {
+               try {
+                       if (element instanceof Version)
+                               return true;
+                       else if (element instanceof Node)
+                               return ((Node) element).hasNodes();
+                       else
+                               return false;
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot check children of " + element, e);
+               }
+       }
+
+       public void dispose() {
+       }
+
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+       }
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..e4c5873
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..1707681
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+
+/** Centralizes the management of double click on a NodeTreeViewer */
+public class JcrDClickListener implements IDoubleClickListener {
+       // private final static Log log = LogFactory
+       // .getLog(GenericNodeDoubleClickListener.class);
+
+       private TreeViewer nodeViewer;
+
+       // private JcrFileProvider jfp;
+       // private FileHandler fileHandler;
+
+       public JcrDClickListener(TreeViewer nodeViewer) {
+               this.nodeViewer = nodeViewer;
+               // jfp = new JcrFileProvider();
+               // Commented out. see https://www.argeo.org/bugzilla/show_bug.cgi?id=188
+               // fileHandler = null;
+               // fileHandler = new FileHandler(jfp);
+       }
+
+       public void doubleClick(DoubleClickEvent event) {
+               if (event.getSelection() == null || event.getSelection().isEmpty())
+                       return;
+               Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement();
+               if (obj instanceof RepositoryElem) {
+                       RepositoryElem rpNode = (RepositoryElem) obj;
+                       if (rpNode.isConnected()) {
+                               rpNode.logout();
+                       } else {
+                               rpNode.login();
+                       }
+                       nodeViewer.refresh(obj);
+               } else if (obj instanceof WorkspaceElem) {
+                       WorkspaceElem wn = (WorkspaceElem) obj;
+                       if (wn.isConnected())
+                               wn.logout();
+                       else
+                               wn.login();
+                       nodeViewer.refresh(obj);
+               } else if (obj instanceof SingleJcrNodeElem) {
+                       SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj;
+                       Node node = sjn.getNode();
+                       openNode(node);
+               }
+       }
+
+       protected void openNode(Node node) {
+               // TODO implement generic behaviour
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..d1d1e31
--- /dev/null
@@ -0,0 +1,24 @@
+package org.argeo.cms.ui.jcr;
+
+import org.argeo.cms.ui.theme.CmsImages;
+import org.eclipse.swt.graphics.Image;
+
+/** Shared icons. */
+public class JcrImages {
+       public final static Image NODE = CmsImages.createIcon("node.gif");
+       public final static Image FOLDER = CmsImages.createIcon("folder.gif");
+       public final static Image FILE = CmsImages.createIcon("file.gif");
+       public final static Image BINARY = CmsImages.createIcon("binary.png");
+       public final static Image HOME = CmsImages.createIcon("person-logged-in.png");
+       public final static Image SORT = CmsImages.createIcon("sort.gif");
+       public final static Image REMOVE = CmsImages.createIcon("remove.gif");
+
+       public final static Image REPOSITORIES = CmsImages.createIcon("repositories.gif");
+       public final static Image REPOSITORY_DISCONNECTED = CmsImages.createIcon("repository_disconnected.gif");
+       public final static Image REPOSITORY_CONNECTED = CmsImages.createIcon("repository_connected.gif");
+       public final static Image REMOTE_DISCONNECTED = CmsImages.createIcon("remote_disconnected.gif");
+       public final static Image REMOTE_CONNECTED = CmsImages.createIcon("remote_connected.gif");
+       public final static Image WORKSPACE_DISCONNECTED = CmsImages.createIcon("workspace_disconnected.png");
+       public final static Image WORKSPACE_CONNECTED = CmsImages.createIcon("workspace_connected.png");
+
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..cc8479f
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/**
+ * Implementation of the {@code ITreeContentProvider} in order to display a
+ * single JCR node and its children in a tree like structure
+ */
+public class JcrTreeContentProvider implements ITreeContentProvider {
+       private static final long serialVersionUID = -2128326504754297297L;
+       // private Node rootNode;
+       private JcrItemsComparator itemComparator = new JcrItemsComparator();
+
+       /**
+        * Sends back the first level of the Tree. input element must be a single node
+        * object
+        */
+       public Object[] getElements(Object inputElement) {
+               Node rootNode = (Node) inputElement;
+               return childrenNodes(rootNode);
+       }
+
+       public Object[] getChildren(Object parentElement) {
+               return childrenNodes((Node) parentElement);
+       }
+
+       public Object getParent(Object element) {
+               try {
+                       Node node = (Node) element;
+                       if (!node.getPath().equals("/"))
+                               return node.getParent();
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       return null;
+               }
+       }
+
+       public boolean hasChildren(Object element) {
+               try {
+                       return ((Node) element).hasNodes();
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot check children existence on " + element, e);
+               }
+       }
+
+       protected Object[] childrenNodes(Node parentNode) {
+               try {
+                       List<Node> children = new ArrayList<Node>();
+                       NodeIterator nit = parentNode.getNodes();
+                       while (nit.hasNext()) {
+                               Node node = nit.nextNode();
+//                             if (node.getName().startsWith("rep:") || node.getName().startsWith("jcr:")
+//                                             || node.getName().startsWith("nt:"))
+//                                     continue nodes;
+                               children.add(node);
+                       }
+                       Node[] arr = children.toArray(new Node[0]);
+                       Arrays.sort(arr, itemComparator);
+                       return arr;
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot list children of " + parentNode, e);
+               }
+       }
+
+       public void dispose() {
+       }
+
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..00449df
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..a5751c0
--- /dev/null
@@ -0,0 +1,113 @@
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ui.jcr.model.RemoteRepositoryElem;
+import org.argeo.cms.ui.jcr.model.RepositoriesElem;
+import org.argeo.cms.ui.jcr.model.RepositoryElem;
+import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
+import org.argeo.cms.ui.jcr.model.WorkspaceElem;
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Image;
+
+/** Provides reasonable defaults for know JCR types. */
+public class NodeLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = -3662051696443321843L;
+
+       private final static CmsLog log = CmsLog.getLog(NodeLabelProvider.class);
+
+       public String getText(Object element) {
+               try {
+                       if (element instanceof SingleJcrNodeElem) {
+                               SingleJcrNodeElem sjn = (SingleJcrNodeElem) element;
+                               return getText(sjn.getNode());
+                       } else if (element instanceof Node) {
+                               return getText((Node) element);
+                       } else
+                               return super.getText(element);
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Unexpected JCR error while getting node name.");
+               }
+       }
+
+       protected String getText(Node node) throws RepositoryException {
+               String label = node.getName();
+               StringBuffer mixins = new StringBuffer("");
+               for (NodeType type : node.getMixinNodeTypes())
+                       mixins.append(' ').append(type.getName());
+
+               return label + " [" + node.getPrimaryNodeType().getName() + mixins + "]";
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               if (element instanceof RemoteRepositoryElem) {
+                       if (((RemoteRepositoryElem) element).isConnected())
+                               return JcrImages.REMOTE_CONNECTED;
+                       else
+                               return JcrImages.REMOTE_DISCONNECTED;
+               } else if (element instanceof RepositoryElem) {
+                       if (((RepositoryElem) element).isConnected())
+                               return JcrImages.REPOSITORY_CONNECTED;
+                       else
+                               return JcrImages.REPOSITORY_DISCONNECTED;
+               } else if (element instanceof WorkspaceElem) {
+                       if (((WorkspaceElem) element).isConnected())
+                               return JcrImages.WORKSPACE_CONNECTED;
+                       else
+                               return JcrImages.WORKSPACE_DISCONNECTED;
+               } else if (element instanceof RepositoriesElem) {
+                       return JcrImages.REPOSITORIES;
+               } else if (element instanceof SingleJcrNodeElem) {
+                       Node nodeElem = ((SingleJcrNodeElem) element).getNode();
+                       return getImage(nodeElem);
+
+                       // if (element instanceof Node) {
+                       // return getImage((Node) element);
+                       // } else if (element instanceof WrappedNode) {
+                       // return getImage(((WrappedNode) element).getNode());
+                       // } else if (element instanceof NodesWrapper) {
+                       // return getImage(((NodesWrapper) element).getNode());
+                       // }
+               }
+               // try {
+               // return super.getImage();
+               // } catch (RepositoryException e) {
+               // return null;
+               // }
+               return super.getImage(element);
+       }
+
+       protected Image getImage(Node node) {
+               try {
+                       if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE))
+                               return JcrImages.FILE;
+                       else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER))
+                               return JcrImages.FOLDER;
+                       else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE))
+                               return JcrImages.BINARY;
+                       try {
+                               // TODO check workspace type?
+                               if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID))
+                                       return JcrImages.HOME;
+
+                               // optimizes
+//                             if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME))
+//                                     return JcrImages.HOME;
+                       } catch (NamespaceException e) {
+                               // node namespace is not registered in this repo
+                       }
+                       return JcrImages.NODE;
+               } catch (RepositoryException e) {
+                       log.warn("Error while retrieving type for " + node + " in order to display corresponding image");
+                       e.printStackTrace();
+                       return null;
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..444350a
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class OsgiRepositoryRegister extends DefaultRepositoryRegister {
+       private final static BundleContext bc = FrameworkUtil.getBundle(OsgiRepositoryRegister.class).getBundleContext();
+       private final ServiceTracker<Repository, Repository> repositoryTracker;
+
+       public OsgiRepositoryRegister() {
+               repositoryTracker = new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
+
+                       @Override
+                       public Repository addingService(ServiceReference<Repository> reference) {
+
+                               Repository repository = super.addingService(reference);
+                               Map<String, Object> props = new HashMap<>();
+                               for (String key : reference.getPropertyKeys()) {
+                                       props.put(key, reference.getProperty(key));
+                               }
+                               register(repository, props);
+                               return repository;
+                       }
+
+                       @Override
+                       public void removedService(ServiceReference<Repository> reference, Repository service) {
+                               Map<String, Object> props = new HashMap<>();
+                               for (String key : reference.getPropertyKeys()) {
+                                       props.put(key, reference.getProperty(key));
+                               }
+                               unregister(service, props);
+                               super.removedService(reference, service);
+                       }
+
+               };
+       }
+
+       public void init() {
+               repositoryTracker.open();
+       }
+
+       public void destroy() {
+               repositoryTracker.close();
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..fd544bb
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.Viewer;
+
+/** Simple content provider that displays all properties of a given Node */
+public class PropertiesContentProvider implements IStructuredContentProvider {
+       private static final long serialVersionUID = 5227554668841613078L;
+       private JcrItemsComparator itemComparator = new JcrItemsComparator();
+
+       public void dispose() {
+       }
+
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+       }
+
+       public Object[] getElements(Object inputElement) {
+               try {
+                       if (inputElement instanceof Node) {
+                               Set<Property> props = new TreeSet<Property>(itemComparator);
+                               PropertyIterator pit = ((Node) inputElement).getProperties();
+                               while (pit.hasNext())
+                                       props.add(pit.nextProperty());
+                               return props.toArray();
+                       }
+                       return new Object[] {};
+               } catch (RepositoryException e) {
+                       throw new EclipseUiException("Cannot get element for "
+                                       + inputElement, e);
+               }
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..37b90f7
--- /dev/null
@@ -0,0 +1,101 @@
+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
new file mode 100644 (file)
index 0000000..802c756
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.cms.ui.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+
+/** Allows to register repositories by name. */
+public interface RepositoryRegister extends RepositoryFactory {
+       /**
+        * The registered {@link Repository} as a read-only map. Note that this
+        * method should be called for each access in order to be sure to be up to
+        * date in case repositories have registered/unregistered
+        */
+       public Map<String, Repository> getRepositories();
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..37dfe2b
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.cms.ui.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+
+import org.argeo.eclipse.ui.EclipseUiException;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+
+/**
+ * Simple wrapping of the ColumnLabelProvider class to provide text display in
+ * order to build a tree for version. The getText() method does not assume that
+ * {@link Version} extends {@link Node} class to respect JCR 2.0 specification
+ * 
+ */
+public class VersionLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = 5270739851193688238L;
+
+       public String getText(Object element) {
+               try {
+                       if (element instanceof Version) {
+                               Version version = (Version) element;
+                               return version.getName();
+                       } else if (element instanceof Node) {
+                               return ((Node) element).getName();
+                       }
+               } catch (RepositoryException re) {
+                       throw new EclipseUiException(
+                                       "Unexpected error while getting element name", re);
+               }
+               return super.getText(element);
+       }
+}
diff --git a/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
new file mode 100644 (file)
index 0000000..61654b6
--- /dev/null
@@ -0,0 +1,21 @@
+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
new file mode 100644 (file)
index 0000000..428e7f1
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.ui.jcr.model;
+
+import java.util.Arrays;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..8586332
--- /dev/null
@@ -0,0 +1,112 @@
+package org.argeo.cms.ui.jcr.model;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..afff3ef
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.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
new file mode 100644 (file)
index 0000000..859deee
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..24fc575
--- /dev/null
@@ -0,0 +1,117 @@
+package org.argeo.cms.ui.jcr.model;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+// import javax.jcr.Workspace;
+import javax.jcr.Workspace;
+
+import org.argeo.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
new file mode 100644 (file)
index 0000000..8f54744
--- /dev/null
@@ -0,0 +1,2 @@
+/** Model for SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr.model;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..26ae330
--- /dev/null
@@ -0,0 +1,2 @@
+/** SWT/JFace JCR components. */
+package org.argeo.cms.ui.jcr;
\ No newline at end of file
diff --git a/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
new file mode 100644 (file)
index 0000000..82fdee7
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..3821e60
--- /dev/null
@@ -0,0 +1,282 @@
+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
new file mode 100644 (file)
index 0000000..fc0c821
--- /dev/null
@@ -0,0 +1,49 @@
+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
new file mode 100644 (file)
index 0000000..8b38479
--- /dev/null
@@ -0,0 +1,220 @@
+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
new file mode 100644 (file)
index 0000000..1fc9bd1
--- /dev/null
@@ -0,0 +1,246 @@
+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
new file mode 100644 (file)
index 0000000..284d2bd
--- /dev/null
@@ -0,0 +1,22 @@
+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
new file mode 100644 (file)
index 0000000..e8bf662
--- /dev/null
@@ -0,0 +1,97 @@
+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
new file mode 100644 (file)
index 0000000..8e0e7c1
--- /dev/null
@@ -0,0 +1,118 @@
+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
new file mode 100644 (file)
index 0000000..ac09b2a
--- /dev/null
@@ -0,0 +1,5 @@
+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
new file mode 100644 (file)
index 0000000..63e504b
--- /dev/null
@@ -0,0 +1,32 @@
+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
new file mode 100644 (file)
index 0000000..8ed06a2
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..ad1523c
--- /dev/null
@@ -0,0 +1,71 @@
+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
new file mode 100644 (file)
index 0000000..156a608
--- /dev/null
@@ -0,0 +1,129 @@
+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
new file mode 100644 (file)
index 0000000..316cb51
--- /dev/null
@@ -0,0 +1,55 @@
+package org.argeo.cms.ui.util;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class UserMenu extends CmsLoginShell {
+       private final Control source;
+       private final Node context;
+
+       public UserMenu(Control source, Node context) {
+               super(CmsUiUtils.getCmsView());
+               this.context = context;
+               createUi();
+               if (source == null)
+                       throw new CmsException("Source control cannot be null.");
+               this.source = source;
+               open();
+       }
+
+       @Override
+       protected Shell createShell() {
+               return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+       }
+
+       @Override
+       public void open() {
+               Shell shell = getShell();
+               shell.pack();
+               shell.layout();
+               shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
+               shell.addShellListener(new ShellAdapter() {
+                       private static final long serialVersionUID = 5178980294808435833L;
+
+                       @Override
+                       public void shellDeactivated(ShellEvent e) {
+                               closeShell();
+                       }
+               });
+               super.open();
+       }
+
+       protected Node getContext() {
+               return context;
+       }
+
+}
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
new file mode 100644 (file)
index 0000000..317a7b5
--- /dev/null
@@ -0,0 +1,84 @@
+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
new file mode 100644 (file)
index 0000000..7f846c9
--- /dev/null
@@ -0,0 +1,44 @@
+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
new file mode 100644 (file)
index 0000000..566df88
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..ef24ee0
--- /dev/null
@@ -0,0 +1,350 @@
+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
new file mode 100644 (file)
index 0000000..3967c97
--- /dev/null
@@ -0,0 +1,12 @@
+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
new file mode 100644 (file)
index 0000000..4ca45d1
--- /dev/null
@@ -0,0 +1,9 @@
+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
new file mode 100644 (file)
index 0000000..11162e8
--- /dev/null
@@ -0,0 +1,101 @@
+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
new file mode 100644 (file)
index 0000000..b51d4fc
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..793079e
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..d282eeb
--- /dev/null
@@ -0,0 +1,165 @@
+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
new file mode 100644 (file)
index 0000000..f0b367f
--- /dev/null
@@ -0,0 +1,9 @@
+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
new file mode 100644 (file)
index 0000000..2f07931
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..7bc0f79
--- /dev/null
@@ -0,0 +1,113 @@
+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
new file mode 100644 (file)
index 0000000..c2393f2
--- /dev/null
@@ -0,0 +1,112 @@
+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
new file mode 100644 (file)
index 0000000..e3499ac
--- /dev/null
@@ -0,0 +1,145 @@
+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
new file mode 100644 (file)
index 0000000..3a4a60c
--- /dev/null
@@ -0,0 +1,155 @@
+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
new file mode 100644 (file)
index 0000000..5d3576f
--- /dev/null
@@ -0,0 +1,213 @@
+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
new file mode 100644 (file)
index 0000000..517e796
--- /dev/null
@@ -0,0 +1,74 @@
+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
new file mode 100644 (file)
index 0000000..e3a5cb4
--- /dev/null
@@ -0,0 +1,153 @@
+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
new file mode 100644 (file)
index 0000000..e461ed0
--- /dev/null
@@ -0,0 +1,37 @@
+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
new file mode 100644 (file)
index 0000000..514f753
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..fdafa98
--- /dev/null
@@ -0,0 +1,138 @@
+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
new file mode 100644 (file)
index 0000000..b880a63
--- /dev/null
@@ -0,0 +1,83 @@
+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
new file mode 100644 (file)
index 0000000..22ffeaf
--- /dev/null
@@ -0,0 +1,82 @@
+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
new file mode 100644 (file)
index 0000000..b83aaa2
--- /dev/null
@@ -0,0 +1,44 @@
+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
new file mode 100644 (file)
index 0000000..420154b
--- /dev/null
@@ -0,0 +1,149 @@
+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
new file mode 100644 (file)
index 0000000..7e12bec
--- /dev/null
@@ -0,0 +1,123 @@
+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
new file mode 100644 (file)
index 0000000..787c92e
--- /dev/null
@@ -0,0 +1,8 @@
+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
new file mode 100644 (file)
index 0000000..2f3d64d
--- /dev/null
@@ -0,0 +1,36 @@
+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
new file mode 100644 (file)
index 0000000..2f808a5
--- /dev/null
@@ -0,0 +1,71 @@
+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
new file mode 100644 (file)
index 0000000..934fa67
--- /dev/null
@@ -0,0 +1,35 @@
+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/SimpleNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java
new file mode 100644 (file)
index 0000000..cb235d7
--- /dev/null
@@ -0,0 +1,59 @@
+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
new file mode 100644 (file)
index 0000000..1ce3154
--- /dev/null
@@ -0,0 +1,80 @@
+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
new file mode 100644 (file)
index 0000000..32e5d30
--- /dev/null
@@ -0,0 +1,27 @@
+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
new file mode 100644 (file)
index 0000000..43df1fe
--- /dev/null
@@ -0,0 +1,41 @@
+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
new file mode 100644 (file)
index 0000000..c5dd733
--- /dev/null
@@ -0,0 +1,116 @@
+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
new file mode 100644 (file)
index 0000000..341b3ab
--- /dev/null
@@ -0,0 +1,190 @@
+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
new file mode 100644 (file)
index 0000000..455fb0d
--- /dev/null
@@ -0,0 +1,62 @@
+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
new file mode 100644 (file)
index 0000000..aa2e337
--- /dev/null
@@ -0,0 +1,120 @@
+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
new file mode 100644 (file)
index 0000000..5d421f6
--- /dev/null
@@ -0,0 +1,47 @@
+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
new file mode 100644 (file)
index 0000000..3678aab
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..19e3cc3
--- /dev/null
@@ -0,0 +1,2 @@
+/** 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
new file mode 100644 (file)
index 0000000..c82e666
--- /dev/null
@@ -0,0 +1,129 @@
+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
new file mode 100644 (file)
index 0000000..fb12399
--- /dev/null
@@ -0,0 +1,21 @@
+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
new file mode 100644 (file)
index 0000000..54b795f
--- /dev/null
@@ -0,0 +1,36 @@
+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
new file mode 100644 (file)
index 0000000..291d579
--- /dev/null
@@ -0,0 +1,98 @@
+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
new file mode 100644 (file)
index 0000000..016348c
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic SWT/JFace JCR helpers. */
+package org.argeo.eclipse.ui.jcr.util;
\ No newline at end of file
diff --git a/jcr/pom.xml b/jcr/pom.xml
new file mode 100644 (file)
index 0000000..74dcc7d
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <parent>
+               <groupId>org.argeo.commons</groupId>
+               <artifactId>argeo-commons</artifactId>
+               <version>2.3-SNAPSHOT</version>
+               <relativePath>..</relativePath>
+       </parent>
+       <groupId>org.argeo.commons</groupId>
+       <artifactId>jcr</artifactId>
+       <name>JCR</name>
+       <packaging>pom</packaging>
+       <modules>
+               <module>org.argeo.cms.jcr</module>
+               <module>org.argeo.cms.ui</module>
+       </modules>
+</project>
\ No newline at end of file
diff --git a/org.argeo.cms.e4/.classpath b/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/org.argeo.cms.e4/.project b/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/org.argeo.cms.e4/META-INF/.gitignore b/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/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/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/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/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/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/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/org.argeo.cms.e4/bnd.bnd b/org.argeo.cms.e4/bnd.bnd
deleted file mode 100644 (file)
index e4a4519..0000000
+++ /dev/null
@@ -1,15 +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,\
-*
diff --git a/org.argeo.cms.e4/build.properties b/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/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/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/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi b/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/org.argeo.cms.e4/pom.xml b/org.argeo.cms.e4/pom.xml
deleted file mode 100644 (file)
index dda9b4f..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <artifactId>argeo-commons</artifactId>
-               <version>2.3-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.e4</artifactId>
-       <name>CMS E4</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-
-               <!-- UI -->
-               <dependency>
-                       <groupId>org.argeo.commons.rap</groupId>
-                       <artifactId>org.argeo.swt.specific.rap</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp</groupId>
-                       <artifactId>argeo-tp-rap-e4</artifactId>
-                       <version>${version.argeo-tp}</version>
-                       <type>pom</type>
-                       <scope>provided</scope>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/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/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/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/org.argeo.cms.jcr/.classpath b/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/org.argeo.cms.jcr/.project b/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/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs b/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/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/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/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml b/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/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/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/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/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/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml b/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/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml b/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/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/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/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/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/org.argeo.cms.jcr/bnd.bnd b/org.argeo.cms.jcr/bnd.bnd
deleted file mode 100644 (file)
index 71071f6..0000000
+++ /dev/null
@@ -1,37 +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,\
-org.postgresql;version="[42,43)";resolution:=optional,\
-org.apache.jackrabbit.webdav.server,\
-org.apache.jackrabbit.webdav.jcr,\
-org.apache.commons.httpclient.cookie;resolution:=optional,\
-org.osgi.framework.namespace;version=0.0.0,\
-org.osgi.*;version=0.0.0,\
-org.osgi.service.http.whiteboard,\
-org.apache.jackrabbit.api,\
-org.apache.jackrabbit.commons,\
-org.apache.jackrabbit.spi,\
-org.apache.jackrabbit.spi2dav,\
-org.apache.jackrabbit.spi2davex,\
-org.apache.jackrabbit.webdav,\
-junit.*;resolution:=optional,\
-*
-
-Service-Component:\
-OSGI-INF/repositoryContextsFactory.xml,\
-OSGI-INF/jcrRepositoryFactory.xml,\
-OSGI-INF/jcrFsProvider.xml,\
-OSGI-INF/jcrDeployment.xml,\
-OSGI-INF/jcrServletContext.xml,\
-OSGI-INF/dataServletContext.xml,\
-OSGI-INF/filesServletContext.xml,\
-OSGI-INF/filesServlet.xml
diff --git a/org.argeo.cms.jcr/build.properties b/org.argeo.cms.jcr/build.properties
deleted file mode 100644 (file)
index 3ddcf97..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/jcrDeployment.xml,\
-               OSGI-INF/repositoryContextsFactory.xml,\
-               OSGI-INF/jcrRepositoryFactory.xml,\
-               OSGI-INF/jcrFsProvider.xml
-source.. = src/
-additional.bundles = org.apache.jackrabbit.core,\
-                     javax.jcr,\
-                     org.apache.jackrabbit.api,\
-                     org.apache.jackrabbit.data,\
-                     org.apache.jackrabbit.jcr.commons,\
-                     org.apache.jackrabbit.spi,\
-                     org.apache.jackrabbit.spi.commons,\
-                     org.slf4j.api,\
-                     org.apache.commons.collections,\
-                     EDU.oswego.cs.dl.util.concurrent,\
-                     org.apache.lucene,\
-                     org.apache.tika.core,\
-                     org.apache.commons.dbcp,\
-                     org.apache.commons.pool,\
-                     com.google.guava,\
-                     org.apache.jackrabbit.jcr2spi,\
-                     org.apache.jackrabbit.spi2dav,\
-                     org.apache.httpcomponents.httpclient,\
-                     org.apache.httpcomponents.httpcore,\
-                     org.apache.tika.parsers
diff --git a/org.argeo.cms.jcr/pom.xml b/org.argeo.cms.jcr/pom.xml
deleted file mode 100644 (file)
index 58de05b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <artifactId>argeo-commons</artifactId>
-               <version>2.3-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.jcr</artifactId>
-       <name>CMS JCR</name>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.servlet</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java
deleted file mode 100644 (file)
index b5d9adf..0000000
+++ /dev/null
@@ -1,276 +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) {
-               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
-               LoginContext loginContext;
-               try {
-                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
-                       loginContext.login();
-               } catch (LoginException e1) {
-                       throw new RuntimeException("Could not login as data admin", e1);
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentCl);
-               }
-               return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
-
-                       @Override
-                       public Session run() {
-                               try {
-                                       return repository.login(workspaceName);
-                               } catch (NoSuchWorkspaceException e) {
-                                       throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
-                               } catch (RepositoryException e) {
-                                       throw new RuntimeException("Cannot open data admin session", e);
-                               }
-                       }
-
-               });
-       }
-
-       /** Singleton. */
-       private CmsJcrUtils() {
-       }
-
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java
deleted file mode 100644 (file)
index 340d137..0000000
+++ /dev/null
@@ -1,481 +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 CmsJcrDeployment() {
-//             initTrackers();
-       }
-
-       public void start() {
-               dataModels = new DataModels(bc);
-
-               ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
-               repoContextSt.open();
-               //KernelUtils.asyncOpen(repoContextSt);
-
-//             nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
-
-               JcrInitUtils.addToDeployment(cmsDeployment);
-
-       }
-
-       public void stop() {
-//             if (nodeHttp != null)
-//                     nodeHttp.destroy();
-
-               try {
-                       for (ServiceReference<JackrabbitLocalRepository> sr : bc
-                                       .getServiceReferences(JackrabbitLocalRepository.class, null)) {
-                               bc.getService(sr).destroy();
-                       }
-               } catch (InvalidSyntaxException e1) {
-                       log.error("Cannot clean repositories", e1);
-               }
-
-       }
-
-       public void setCmsDeployment(CmsDeployment cmsDeployment) {
-               this.cmsDeployment = cmsDeployment;
-       }
-
-       /**
-        * Checks whether the deployment is available according to expectations, and
-        * mark it as available.
-        */
-//     private synchronized void checkReadiness() {
-//             if (isAvailable())
-//                     return;
-//             if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
-//                     String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
-//                     String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
-//                     availableSince = System.currentTimeMillis();
-//                     long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-//                     String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-//                     log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
-//                     if (log.isDebugEnabled()) {
-//                             log.debug("## state: " + state);
-//                             if (data != null)
-//                                     log.debug("## data: " + data);
-//                     }
-//                     long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
-//                     long initDuration = System.currentTimeMillis() - begin;
-//                     if (log.isTraceEnabled())
-//                             log.trace("Kernel initialization took " + initDuration + "ms");
-//                     tributeToFreeSoftware(initDuration);
-//             }
-//     }
-
-       private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
-//             if (availableSince != null) {
-//                     throw new IllegalStateException("Deployment is already available");
-//             }
-
-               // home
-               prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
-
-               // init from backup
-//             if (deployConfig.isFirstInit()) {
-//                     Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
-//                     if (Files.exists(restorePath)) {
-//                             if (log.isDebugEnabled())
-//                                     log.debug("Found backup " + restorePath + ", restoring it...");
-//                             LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
-//                             KernelUtils.doAsDataAdmin(logicalRestore);
-//                             log.info("Restored backup from " + restorePath);
-//                     }
-//             }
-
-               // init from repository
-               Collection<ServiceReference<Repository>> initRepositorySr;
-               try {
-                       initRepositorySr = bc.getServiceReferences(Repository.class,
-                                       "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
-               } catch (InvalidSyntaxException e1) {
-                       throw new IllegalArgumentException(e1);
-               }
-               Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
-               while (it.hasNext()) {
-                       ServiceReference<Repository> sr = it.next();
-                       Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
-                       Repository initRepository = bc.getService(sr);
-                       if (log.isDebugEnabled())
-                               log.debug("Found init repository " + labeledUri + ", copying it...");
-                       initFromRepository(deployedNodeRepository, initRepository);
-                       log.info("Node repository initialised from " + labeledUri);
-               }
-       }
-
-       /** Init from a (typically remote) repository. */
-       private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
-               Session initSession = null;
-               try {
-                       initSession = initRepository.login();
-                       workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
-                               if ("security".equals(workspaceName))
-                                       continue workspaces;
-                               if (log.isDebugEnabled())
-                                       log.debug("Copying workspace " + workspaceName + " from init repository...");
-                               long begin = System.currentTimeMillis();
-                               Session targetSession = null;
-                               Session sourceSession = null;
-                               try {
-                                       try {
-                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
-                                       } catch (IllegalArgumentException e) {// no such workspace
-                                               Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
-                                               try {
-                                                       adminSession.getWorkspace().createWorkspace(workspaceName);
-                                               } finally {
-                                                       Jcr.logout(adminSession);
-                                               }
-                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
-                                       }
-                                       sourceSession = initRepository.login(workspaceName);
-//                                     JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
-                                       // TODO deal with referenceable nodes
-                                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
-                                       targetSession.save();
-                                       long duration = System.currentTimeMillis() - begin;
-                                       if (log.isDebugEnabled())
-                                               log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
-                                                               + " s");
-                               } catch (Exception e) {
-                                       log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
-                               } finally {
-                                       Jcr.logout(sourceSession);
-                                       Jcr.logout(targetSession);
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException(e);
-               } finally {
-                       Jcr.logout(initSession);
-               }
-       }
-
-       private void prepareHomeRepository(RepositoryImpl deployedRepository) {
-               Session adminSession = KernelUtils.openAdminSession(deployedRepository);
-               try {
-                       argeoDataModelExtensionsAvailable = Arrays
-                                       .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
-                                       .contains(ArgeoNames.ARGEO_NAMESPACE);
-               } catch (RepositoryException e) {
-                       log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
-                       argeoDataModelExtensionsAvailable = false;
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-
-               // Publish home with the highest service ranking
-               Hashtable<String, Object> regProps = new Hashtable<>();
-               regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
-               regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               Repository egoRepository = new EgoRepository(deployedRepository, false);
-               bc.registerService(Repository.class, egoRepository, regProps);
-               registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
-
-               // Keyring only if Argeo extensions are available
-               if (argeoDataModelExtensionsAvailable) {
-                       new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
-
-                               @Override
-                               public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
-                                       NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
-                                       CallbackHandler callbackHandler = bc.getService(reference);
-                                       nodeKeyring.setDefaultCallbackHandler(callbackHandler);
-                                       bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
-                                                       nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
-                                       return callbackHandler;
-                               }
-
-                       }.open();
-               }
-       }
-
-       /** Session is logged out. */
-       private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
-               Session adminSession = KernelUtils.openAdminSession(repository);
-               try {
-                       Set<String> processed = new HashSet<String>();
-                       bundles: for (Bundle bundle : bc.getBundles()) {
-                               BundleWiring wiring = bundle.adapt(BundleWiring.class);
-                               if (wiring == null)
-                                       continue bundles;
-                               if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
-                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
-                               else {
-                                       List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-                                       for (BundleCapability capability : capabilities) {
-                                               String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
-                                               if (dataModelName.equals(cn))// process only own data model
-                                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
-                                       }
-                               }
-                       }
-               } finally {
-                       JcrUtils.logoutQuietly(adminSession);
-               }
-       }
-
-       private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
-                       boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
-               // recursively process requirements first
-               List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
-               for (BundleWire wire : requiredWires) {
-                       processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
-               }
-
-               List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
-               capabilities: for (BundleCapability capability : capabilities) {
-                       if (!importListedAbstractModels
-                                       && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
-                               continue capabilities;
-                       }
-                       boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
-                       if (publish)
-                               publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
-               }
-       }
-
-       private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
-                       Set<String> processed) {
-               Map<String, Object> attrs = capability.getAttributes();
-               String name = (String) attrs.get(DataModelNamespace.NAME);
-               if (processed.contains(name)) {
-                       if (log.isTraceEnabled())
-                               log.trace("Data model " + name + " has already been processed");
-                       return false;
-               }
-
-               // CND
-               String path = (String) attrs.get(DataModelNamespace.CND);
-               if (path != null) {
-                       File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
-                       if (!dataModel.exists()) {
-                               URL url = capability.getRevision().getBundle().getResource(path);
-                               if (url == null)
-                                       throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
-                               try (Reader reader = new InputStreamReader(url.openStream())) {
-                                       CndImporter.registerNodeTypes(reader, adminSession, true);
-                                       processed.add(name);
-                                       dataModel.getParentFile().mkdirs();
-                                       dataModel.createNewFile();
-                                       if (log.isDebugEnabled())
-                                               log.debug("Registered CND " + url);
-                               } catch (Exception e) {
-                                       log.error("Cannot import CND " + url, e);
-                               }
-                       }
-               }
-
-               if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
-                       return false;
-               // Non abstract
-               boolean isStandalone = isStandalone(name);
-               boolean publishLocalRepo;
-               if (isStandalone && name.equals(cn))// includes the node itself
-                       publishLocalRepo = true;
-               else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
-                       publishLocalRepo = true;
-               else
-                       publishLocalRepo = false;
-
-               return publishLocalRepo;
-       }
-
-       boolean isStandalone(String dataModelName) {
-               return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
-       }
-
-       private void publishLocalRepo(String dataModelName, Repository repository) {
-               Hashtable<String, Object> properties = new Hashtable<>();
-               properties.put(CmsConstants.CN, dataModelName);
-               LocalRepository localRepository;
-               String[] classes;
-               if (repository instanceof RepositoryImpl) {
-                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
-                                       JackrabbitLocalRepository.class.getName() };
-               } else {
-                       localRepository = new LocalRepository(repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
-               }
-               bc.registerService(classes, localRepository, properties);
-
-               // TODO make it configurable
-               registerRepositoryServlets(dataModelName, localRepository);
-               if (log.isTraceEnabled())
-                       log.trace("Published data model " + dataModelName);
-       }
-
-//     @Override
-//     public synchronized Long getAvailableSince() {
-//             return availableSince;
-//     }
-//
-//     public synchronized boolean isAvailable() {
-//             return availableSince != null;
-//     }
-
-       protected void registerRepositoryServlets(String alias, Repository repository) {
-               // FIXME re-enable it with a proper class loader
-//             registerRemotingServlet(alias, repository);
-//             registerWebdavServlet(alias, repository);
-       }
-
-       protected void registerWebdavServlet(String alias, Repository repository) {
-               CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
-               Hashtable<String, String> ip = new Hashtable<>();
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                               "/" + alias);
-
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
-                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
-               bc.registerService(Servlet.class, webdavServlet, ip);
-       }
-
-       protected void registerRemotingServlet(String alias, Repository repository) {
-               CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
-               Hashtable<String, String> ip = new Hashtable<>();
-               ip.put(CmsConstants.CN, alias);
-               // Properties ip = new Properties();
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                               "/" + alias);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
-                               "Negotiate");
-
-               // Looks like a bug in Jackrabbit remoting init
-               Path tmpDir;
-               try {
-                       tmpDir = Files.createTempDirectory("remoting_" + alias);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
-               }
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
-                               "remoting_" + alias);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
-                               JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
-
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
-                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
-               bc.registerService(Servlet.class, remotingServlet, ip);
-       }
-
-       private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
-
-               public RepositoryContextStc() {
-                       super(bc, RepositoryContext.class, null);
-               }
-
-               @Override
-               public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
-                       RepositoryContext repoContext = bc.getService(reference);
-                       String cn = (String) reference.getProperty(CmsConstants.CN);
-                       if (cn != null) {
-                               List<String> publishAsLocalRepo = new ArrayList<>();
-                               if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
-//                                     JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
-                                       prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
-                                       // TODO separate home repository
-                                       prepareHomeRepository(repoContext.getRepository());
-                                       registerRepositoryServlets(cn, repoContext.getRepository());
-                                       nodeAvailable = true;
-//                                     checkReadiness();
-                               } else {
-                                       prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
-                               }
-                               // Publish all at once, so that bundles with multiple CNDs are consistent
-                               for (String dataModelName : publishAsLocalRepo)
-                                       publishLocalRepo(dataModelName, repoContext.getRepository());
-                       }
-                       return repoContext;
-               }
-
-               @Override
-               public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
-               }
-
-               @Override
-               public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
-               }
-
-       }
-
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java
deleted file mode 100644 (file)
index 0f27fd0..0000000
+++ /dev/null
@@ -1,174 +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.CmsSession;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.jcr.JcrUtils;
-
-/**
- * Implements an open session in view patter: a new JCR session is created for
- * each request
- */
-public class CmsSessionProvider implements SessionProvider, Serializable {
-       private static final long serialVersionUID = -1358136599534938466L;
-
-       private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
-
-       private final String alias;
-
-       private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
-
-       public CmsSessionProvider(String alias) {
-               this.alias = alias;
-       }
-
-       public Session getSession(HttpServletRequest request, Repository rep, String workspace)
-                       throws javax.jcr.LoginException, ServletException, RepositoryException {
-
-               // a client is scanning parent URLs.
-//             if (workspace == null)
-//                     return null;
-
-//             CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
-               // FIXME retrieve CMS session
-               CmsSession cmsSession = null;
-               if (log.isTraceEnabled()) {
-                       log.trace("Get JCR session from " + cmsSession);
-               }
-               if (cmsSession == null)
-                       throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
-               CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
-               Session session = cmsDataSession.getDataSession(alias, workspace, rep);
-               cmsSessions.put(session, cmsDataSession);
-               return session;
-       }
-
-       public void releaseSession(Session session) {
-//             JcrUtils.logoutQuietly(session);
-               if (cmsSessions.containsKey(session)) {
-                       CmsDataSession cmsDataSession = cmsSessions.get(session);
-                       cmsDataSession.releaseDataSession(alias, session);
-               } else {
-                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       static class CmsDataSession {
-               private CmsSession cmsSession;
-
-               private Map<String, Session> dataSessions = new HashMap<>();
-               private Set<String> dataSessionsInUse = new HashSet<>();
-               private Set<Session> additionalDataSessions = new HashSet<>();
-
-               private CmsDataSession(CmsSession cmsSession) {
-                       this.cmsSession = cmsSession;
-               }
-
-               public Session newDataSession(String cn, String workspace, Repository repository) {
-                       checkValid();
-                       return login(repository, workspace);
-               }
-
-               public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
-                       checkValid();
-                       // FIXME make it more robust
-                       if (workspace == null)
-                               workspace = CmsConstants.SYS_WORKSPACE;
-                       String path = cn + '/' + workspace;
-                       if (dataSessionsInUse.contains(path)) {
-                               try {
-                                       wait(1000);
-                                       if (dataSessionsInUse.contains(path)) {
-                                               Session session = login(repository, workspace);
-                                               additionalDataSessions.add(session);
-                                               if (log.isTraceEnabled())
-                                                       log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
-                                               return session;
-                                       }
-                               } catch (InterruptedException e) {
-                                       // silent
-                               }
-                       }
-
-                       Session session = null;
-                       if (dataSessions.containsKey(path)) {
-                               session = dataSessions.get(path);
-                       } else {
-                               session = login(repository, workspace);
-                               dataSessions.put(path, session);
-                               if (log.isTraceEnabled())
-                                       log.trace("New data session " + path + " for " + cmsSession.getUserDn());
-                       }
-                       dataSessionsInUse.add(path);
-                       return session;
-               }
-
-               private Session login(Repository repository, String workspace) {
-                       try {
-                               return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
-                                       @Override
-                                       public Session run() throws Exception {
-                                               return repository.login(workspace);
-                                       }
-                               });
-                       } catch (PrivilegedActionException e) {
-                               throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
-                       }
-               }
-
-               public synchronized void releaseDataSession(String cn, Session session) {
-                       if (additionalDataSessions.contains(session)) {
-                               JcrUtils.logoutQuietly(session);
-                               additionalDataSessions.remove(session);
-                               if (log.isTraceEnabled())
-                                       log.trace("Remove additional data session " + session);
-                               return;
-                       }
-                       String path = cn + '/' + session.getWorkspace().getName();
-                       if (!dataSessionsInUse.contains(path))
-                               log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
-                       dataSessionsInUse.remove(path);
-                       Session registeredSession = dataSessions.get(path);
-                       if (session != registeredSession)
-                               log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
-                       if (log.isTraceEnabled())
-                               log.trace("Released data session " + session + " for " + path);
-                       notifyAll();
-               }
-
-               private void checkValid() {
-                       if (!cmsSession.isValid())
-                               throw new IllegalStateException(
-                                               "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
-               }
-
-               private void close() {
-                       // FIXME class this when CMS session is closed
-                       synchronized (this) {
-                               // TODO check data session in use ?
-                               for (String path : dataSessions.keySet())
-                                       JcrUtils.logoutQuietly(dataSessions.get(path));
-                               for (Session session : additionalDataSessions)
-                                       JcrUtils.logoutQuietly(session);
-                       }
-               }
-       }
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java b/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/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java
deleted file mode 100644 (file)
index 0f9db87..0000000
+++ /dev/null
@@ -1,40 +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, 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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java
deleted file mode 100644 (file)
index 4b240f0..0000000
+++ /dev/null
@@ -1,82 +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();
-                       }
-               }
-
-               if (maximumHttpConnections > 0) {
-                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
-                                       maximumHttpConnections);
-               } else {
-                       return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
-               }
-       }
-
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java
deleted file mode 100644 (file)
index e08f4d6..0000000
+++ /dev/null
@@ -1,125 +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;
-                                                       return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
-                                                                       ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
-
-                                                               @Override
-                                                               protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
-                                                                       HttpClientContext result = HttpClientContext.create();
-                                                                       result.setAuthCache(new NonSerialBasicAuthCache());
-                                                                       return result;
-                                                               }
-
-                                                       };
-                                               }
-                                       };
-                                       return RepositoryImpl.create(
-                                                       new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
-                               }
-                       };
-                       Map<String, String> params = new HashMap<String, String>();
-                       params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
-                       // FIXME make it configurable
-                       params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
-
-                       try {
-                               repository = repositoryFactory.getRepository(params);
-                               if (repository != null)
-                                       session = repository.login(workspace);
-                               else
-                                       throw new IllegalArgumentException("Repository " + repoUri + " not found");
-                       } catch (RepositoryException e) {
-                               e.printStackTrace();
-                       }
-
-               } else {
-                       Path path = Paths.get(uri.getPath());
-               }
-
-               try {
-                       Node rootNode = session.getRootNode();
-                       NodeIterator nit = rootNode.getNodes();
-                       while (nit.hasNext()) {
-                               System.out.println(nit.nextNode().getPath());
-                       }
-
-                       Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
-                       System.out.println("Created folder " + newNode.getPath());
-                       Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
-                       System.out.println("Created file " + newFile.getPath());
-                       try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
-                               System.out.println("Read " + reader.readLine());
-                       } catch (IOException e) {
-                               e.printStackTrace();
-                       }
-                       newNode.getParent().remove();
-                       System.out.println("Removed new nodes");
-               } catch (RepositoryException e) {
-                       e.printStackTrace();
-               }
-       }
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java
deleted file mode 100644 (file)
index f65432e..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.argeo.jackrabbit.unit;
-
-import java.net.URL;
-
-import javax.jcr.Repository;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.unit.AbstractJcrTestCase;
-
-/** Factorizes configuration of an in memory transient repository */
-public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase {
-       protected RepositoryImpl repositoryImpl;
-
-       // protected File getRepositoryFile() throws Exception {
-       // Resource res = new ClassPathResource(
-       // "org/argeo/jackrabbit/unit/repository-memory.xml");
-       // return res.getFile();
-       // }
-
-       public AbstractJackrabbitTestCase() {
-               URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config");
-               assert url != null;
-               System.setProperty("java.security.auth.login.config", url.toString());
-       }
-
-       protected Repository createRepository() throws Exception {
-               // Repository repository = new TransientRepository(getRepositoryFile(),
-               // getHomeDir());
-               RepositoryConfig repositoryConfig = RepositoryConfig.create(
-                               AbstractJackrabbitTestCase.class
-                                               .getResourceAsStream(getRepositoryConfigResource()),
-                               getHomeDir().getAbsolutePath());
-               RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
-               return repositoryImpl;
-       }
-
-       protected String getRepositoryConfigResource() {
-               return "repository-memory.xml";
-       }
-
-       @Override
-       protected void clearRepository(Repository repository) throws Exception {
-               RepositoryImpl repositoryImpl = (RepositoryImpl) repository;
-               if (repositoryImpl != null)
-                       repositoryImpl.shutdown();
-               FileUtils.deleteDirectory(getHomeDir());
-       }
-
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config
deleted file mode 100644 (file)
index 0313f91..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-TEST_JACKRABBIT_ADMIN {
-   org.argeo.cms.auth.DataAdminLoginModule requisite;
-};
-
-Jackrabbit {
-   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java
deleted file mode 100644 (file)
index 3b6143b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Helpers for unit tests with Jackrabbit repositories. */
-package org.argeo.jackrabbit.unit;
\ No newline at end of file
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml
deleted file mode 100644 (file)
index 348dc28..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.h2.Driver" />
-                       <param name="url" value="jdbc:h2:mem:jackrabbit" />
-                       <param name="user" value="sa" />
-                       <param name="password" value="" />
-                       <param name="databaseType" value="h2" />
-                       <param name="maxPoolSize" value="10" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="default" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schemaObjectPrefix" value="ds_" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="dev" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/repository/index" />
-               <param name="extractorPoolSize" value="2" />
-               <param name="supportHighlighting" value="true" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
-                       workspaceName="security" />
-               <AccessManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
-               <LoginModule
-                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
-                       <param name="anonymousId" value="anonymous" />
-                       <param name="adminId" value="admin" />
-               </LoginModule>
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml
deleted file mode 100644 (file)
index 8395424..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0"?>
-<!--
-
-    Copyright (C) 2007-2012 Argeo GmbH
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-            http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="main" configRootPath="/workspaces" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${rep.home}/repository/index" />
-                       <param name="directoryManagerClass"
-                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/repository/index" />
-               <param name="directoryManagerClass"
-                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
-                       workspaceName="security" />
-               <AccessManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
-               <LoginModule
-                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
-                       <param name="anonymousId" value="anonymous" />
-                       <param name="adminId" value="admin" />
-               </LoginModule>
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java
deleted file mode 100644 (file)
index bf5de12..0000000
+++ /dev/null
@@ -1,985 +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.security.Privilege;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * Utility class whose purpose is to make using JCR less verbose by
- * systematically using unchecked exceptions and returning <code>null</code>
- * when something is not found. This is especially useful when writing user
- * interfaces (such as with SWT) where listeners and callbacks expect unchecked
- * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
- */
-public class Jcr {
-       /**
-        * The name of a node which will be serialized as XML text, as per section 7.3.1
-        * of the JCR 2.0 specifications.
-        */
-       public final static String JCR_XMLTEXT = "jcr:xmltext";
-       /**
-        * The name of a property which will be serialized as XML text, as per section
-        * 7.3.1 of the JCR 2.0 specifications.
-        */
-       public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
-       /**
-        * <code>jcr:name</code>, when used in another context than
-        * {@link Property#JCR_NAME}, typically to name a node rather than a property.
-        */
-       public final static String JCR_NAME = "jcr:name";
-       /**
-        * <code>jcr:path</code>, when used in another context than
-        * {@link Property#JCR_PATH}, typically to name a node rather than a property.
-        */
-       public final static String JCR_PATH = "jcr:path";
-       /**
-        * <code>jcr:primaryType</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_PRIMARY_TYPE}.
-        */
-       public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
-       /**
-        * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_MIXIN_TYPES}.
-        */
-       public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
-       /**
-        * <code>jcr:uuid</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_UUID}.
-        */
-       public final static String JCR_UUID = "jcr:uuid";
-       /**
-        * <code>jcr:created</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_CREATED}.
-        */
-       public final static String JCR_CREATED = "jcr:created";
-       /**
-        * <code>jcr:createdBy</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_CREATED_BY}.
-        */
-       public final static String JCR_CREATED_BY = "jcr:createdBy";
-       /**
-        * <code>jcr:lastModified</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_LAST_MODIFIED}.
-        */
-       public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
-       /**
-        * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
-        * {@link Property#JCR_LAST_MODIFIED_BY}.
-        */
-       public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
-
-       /**
-        * @see Node#isNodeType(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean isNodeType(Node node, String nodeTypeName) {
-               try {
-                       return node.isNodeType(nodeTypeName);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
-               }
-       }
-
-       /**
-        * @see Node#hasNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean hasNodes(Node node) {
-               try {
-                       return node.hasNodes();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get whether " + node + " has children.", e);
-               }
-       }
-
-       /**
-        * @see Node#getParent()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getParent(Node node) {
-               try {
-                       return isRoot(node) ? null : node.getParent();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get parent of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getParent()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getParentPath(Node node) {
-               return getPath(getParent(node));
-       }
-
-       /**
-        * Whether this node is the root node.
-        * 
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static boolean isRoot(Node node) {
-               try {
-                       return node.getDepth() == 0;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get depth of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getPath()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getPath(Node node) {
-               try {
-                       return node.getPath();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get path of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getSession()
-        * @see Session#getWorkspace()
-        * @see Workspace#getName()
-        */
-       public static String getWorkspaceName(Node node) {
-               return session(node).getWorkspace().getName();
-       }
-
-       /**
-        * @see Node#getIdentifier()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getIdentifier(Node node) {
-               try {
-                       return node.getIdentifier();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get identifier of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getName()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getName(Node node) {
-               try {
-                       return node.getName();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name of " + node, e);
-               }
-       }
-
-       /**
-        * Returns the node name with its current index (useful for re-ordering).
-        * 
-        * @see Node#getName()
-        * @see Node#getIndex()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String getIndexedName(Node node) {
-               try {
-                       return node.getName() + "[" + node.getIndex() + "]";
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get name of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getProperty(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Property getProperty(Node node, String property) {
-               try {
-                       if (node.hasProperty(property))
-                               return node.getProperty(property);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get property " + property + " of " + node, e);
-               }
-       }
-
-       /**
-        * @see Node#getIndex()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static int getIndex(Node node) {
-               try {
-                       return node.getIndex();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get index of " + node, e);
-               }
-       }
-
-       /**
-        * If node has mixin {@link NodeType#MIX_TITLE}, return
-        * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
-        */
-       public static String getTitle(Node node) {
-               if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
-                       return get(node, Property.JCR_TITLE);
-               else
-                       return Jcr.getName(node);
-       }
-
-       /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
-       @SuppressWarnings("unchecked")
-       public static Iterable<Node> iterate(NodeIterator nodeIterator) {
-               return new Iterable<Node>() {
-
-                       @Override
-                       public Iterator<Node> iterator() {
-                               return nodeIterator;
-                       }
-               };
-       }
-
-       /**
-        * @return the children as an {@link Iterable} for use in for-each llops.
-        * @see Node#getNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Iterable<Node> nodes(Node node) {
-               try {
-                       return iterate(node.getNodes());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get children of " + node, e);
-               }
-       }
-
-       /**
-        * @return the children as a (possibly empty) {@link List}.
-        * @see Node#getNodes()
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static List<Node> getNodes(Node node) {
-               List<Node> nodes = new ArrayList<>();
-               try {
-                       if (node.hasNodes()) {
-                               NodeIterator nit = node.getNodes();
-                               while (nit.hasNext())
-                                       nodes.add(nit.nextNode());
-                               return nodes;
-                       } else
-                               return nodes;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get children of " + node, e);
-               }
-       }
-
-       /**
-        * @return the child or <code>null</node> if not found
-        * @see Node#getNode(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNode(Node node, String child) {
-               try {
-                       if (node.hasNode(child))
-                               return node.getNode(child);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get child of " + node, e);
-               }
-       }
-
-       /**
-        * @return the node at this path or <code>null</node> if not found
-        * @see Session#getNode(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNode(Session session, String path) {
-               try {
-                       if (session.nodeExists(path))
-                               return session.getNode(path);
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node " + path, e);
-               }
-       }
-
-       /**
-        * Add a node to this parent, setting its primary type and its mixins.
-        * 
-        * @param parent      the parent node
-        * @param name        the name of the node, if <code>null</code>, the primary
-        *                    type will be used (typically for XML structures)
-        * @param primaryType the primary type, if <code>null</code>
-        *                    {@link NodeType#NT_UNSTRUCTURED} will be used.
-        * @param mixins      the mixins
-        * @return the created node
-        * @see Node#addNode(String, String)
-        * @see Node#addMixin(String)
-        */
-       public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
-               if (name == null && primaryType == null)
-                       throw new IllegalArgumentException("Both node name and primary type cannot be null");
-               try {
-                       Node newNode = parent.addNode(name == null ? primaryType : name,
-                                       primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
-                       for (String mixin : mixins) {
-                               newNode.addMixin(mixin);
-                       }
-                       return newNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
-               }
-       }
-
-       /**
-        * Add an {@link NodeType#NT_BASE} node to this parent.
-        * 
-        * @param parent the parent node
-        * @param name   the name of the node, cannot be <code>null</code>
-        * @return the created node
-        * 
-        * @see Node#addNode(String)
-        */
-       public static Node addNode(Node parent, String name) {
-               if (name == null)
-                       throw new IllegalArgumentException("Node name cannot be null");
-               try {
-                       Node newNode = parent.addNode(name);
-                       return newNode;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
-               }
-       }
-
-       /**
-        * Add mixins to a node.
-        * 
-        * @param node   the node
-        * @param mixins the mixins
-        * @return the created node
-        * @see Node#addMixin(String)
-        */
-       public static void addMixin(Node node, String... mixins) {
-               try {
-                       for (String mixin : mixins) {
-                               node.addMixin(mixin);
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
-               }
-       }
-
-       /**
-        * Removes this node.
-        * 
-        * @see Node#remove()
-        */
-       public static void remove(Node node) {
-               try {
-                       node.remove();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot remove node " + node, e);
-               }
-       }
-
-       /**
-        * @return the node with htis id or <code>null</node> if not found
-        * @see Session#getNodeByIdentifier(String)
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Node getNodeById(Session session, String id) {
-               try {
-                       return session.getNodeByIdentifier(id);
-               } catch (ItemNotFoundException e) {
-                       return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get node with id " + id, e);
-               }
-       }
-
-       /**
-        * Set a property to the given value, or remove it if the value is
-        * <code>null</code>.
-        * 
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static void set(Node node, String property, Object value) {
-               try {
-                       if (!node.hasProperty(property)) {
-                               if (value != null) {
-                                       if (value instanceof List) {// multiple
-                                               List<?> lst = (List<?>) value;
-                                               String[] values = new String[lst.size()];
-                                               for (int i = 0; i < lst.size(); i++) {
-                                                       values[i] = lst.get(i).toString();
-                                               }
-                                               node.setProperty(property, values);
-                                       } else {
-                                               node.setProperty(property, value.toString());
-                                       }
-                               }
-                               return;
-                       }
-                       Property prop = node.getProperty(property);
-                       if (value == null) {
-                               prop.remove();
-                               return;
-                       }
-
-                       // multiple
-                       if (value instanceof List) {
-                               List<?> lst = (List<?>) value;
-                               String[] values = new String[lst.size()];
-                               // TODO better cast?
-                               for (int i = 0; i < lst.size(); i++) {
-                                       values[i] = lst.get(i).toString();
-                               }
-                               if (!prop.isMultiple())
-                                       prop.remove();
-                               node.setProperty(property, values);
-                               return;
-                       }
-
-                       // single
-                       if (prop.isMultiple()) {
-                               prop.remove();
-                               node.setProperty(property, value.toString());
-                               return;
-                       }
-
-                       if (value instanceof String)
-                               prop.setValue((String) value);
-                       else if (value instanceof Long)
-                               prop.setValue((Long) value);
-                       else if (value instanceof Integer)
-                               prop.setValue(((Integer) value).longValue());
-                       else if (value instanceof Double)
-                               prop.setValue((Double) value);
-                       else if (value instanceof Float)
-                               prop.setValue(((Float) value).doubleValue());
-                       else if (value instanceof Calendar)
-                               prop.setValue((Calendar) value);
-                       else if (value instanceof BigDecimal)
-                               prop.setValue((BigDecimal) value);
-                       else if (value instanceof Boolean)
-                               prop.setValue((Boolean) value);
-                       else if (value instanceof byte[])
-                               JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
-                       else if (value instanceof Instant) {
-                               Instant instant = (Instant) value;
-                               GregorianCalendar calendar = new GregorianCalendar();
-                               calendar.setTime(Date.from(instant));
-                               prop.setValue(calendar);
-                       } else // try with toString()
-                               prop.setValue(value.toString());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
-               }
-       }
-
-       /**
-        * Get property as {@link String}.
-        * 
-        * @return the value of
-        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
-        *         <code>null</code> if the property does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String get(Node node, String property) {
-               return get(node, property, null);
-       }
-
-       /**
-        * Get property as a {@link String}. If the property is multiple it returns the
-        * first value.
-        * 
-        * @return the value of
-        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
-        *         <code>defaultValue</code> if the property does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static String get(Node node, String property, String defaultValue) {
-               try {
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               if (!p.isMultiple())
-                                       return p.getString();
-                               else {
-                                       Value[] values = p.getValues();
-                                       if (values.length == 0)
-                                               return defaultValue;
-                                       else
-                                               return values[0].getString();
-                               }
-                       } else
-                               return defaultValue;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get property as a {@link Value}.
-        * 
-        * @return {@link Node#getProperty(String)} or <code>null</code> if the property
-        *         does not exist.
-        * @throws JcrException caused by {@link RepositoryException}
-        */
-       public static Value getValue(Node node, String property) {
-               try {
-                       if (node.hasProperty(property))
-                               return node.getProperty(property).getValue();
-                       else
-                               return null;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get property doing a best effort to cast it as the target object.
-        * 
-        * @return the value of {@link Node#getProperty(String)} or
-        *         <code>defaultValue</code> if the property does not exist.
-        * @throws IllegalArgumentException if the value could not be cast
-        * @throws JcrException             in case of unexpected
-        *                                  {@link RepositoryException}
-        */
-       @SuppressWarnings("unchecked")
-       public static <T> T getAs(Node node, String property, T defaultValue) {
-               try {
-                       // TODO deal with multiple
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               try {
-                                       if (p.isMultiple()) {
-                                               throw new UnsupportedOperationException("Multiple values properties are not supported");
-                                       }
-                                       Value value = p.getValue();
-                                       return (T) get(value);
-                               } catch (ClassCastException e) {
-                                       throw new IllegalArgumentException(
-                                                       "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
-                               }
-                       } else {
-                               return defaultValue;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
-               }
-       }
-       
-       public static <T> T getAs(Node node, String property, Class<T> clss) {
-               if(String.class.isAssignableFrom(clss)) {
-                       return (T)get(node,property);
-               }       else    if(Long.class.isAssignableFrom(clss)) {
-                       return (T)get(node,property);
-               }else {
-                       throw new IllegalArgumentException("Unsupported format "+clss);
-               }
-       }
-
-       /**
-        * Get a multiple property as a list, doing a best effort to cast it as the
-        * target list.
-        * 
-        * @return the value of {@link Node#getProperty(String)}.
-        * @throws IllegalArgumentException if the value could not be cast
-        * @throws JcrException             in case of unexpected
-        *                                  {@link RepositoryException}
-        */
-       public static <T> List<T> getMultiple(Node node, String property) {
-               try {
-                       if (node.hasProperty(property)) {
-                               Property p = node.getProperty(property);
-                               return getMultiple(p);
-                       } else {
-                               return null;
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
-               }
-       }
-
-       /**
-        * Get a multiple property as a list, doing a best effort to cast it as the
-        * target list.
-        */
-       @SuppressWarnings("unchecked")
-       public static <T> List<T> getMultiple(Property p) {
-               try {
-                       List<T> res = new ArrayList<>();
-                       if (!p.isMultiple()) {
-                               res.add((T) get(p.getValue()));
-                               return res;
-                       }
-                       Value[] values = p.getValues();
-                       for (Value value : values) {
-                               res.add((T) get(value));
-                       }
-                       return res;
-               } catch (ClassCastException | RepositoryException e) {
-                       throw new IllegalArgumentException("Cannot get property " + p, e);
-               }
-       }
-
-       /** Cast a {@link Value} to a standard Java object. */
-       public static Object get(Value value) {
-               Binary binary = null;
-               try {
-                       switch (value.getType()) {
-                       case PropertyType.STRING:
-                               return value.getString();
-                       case PropertyType.DOUBLE:
-                               return (Double) value.getDouble();
-                       case PropertyType.LONG:
-                               return (Long) value.getLong();
-                       case PropertyType.BOOLEAN:
-                               return (Boolean) value.getBoolean();
-                       case PropertyType.DATE:
-                               return value.getDate();
-                       case PropertyType.BINARY:
-                               binary = value.getBinary();
-                               byte[] arr = null;
-                               try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
-                                       IOUtils.copy(in, out);
-                                       arr = out.toByteArray();
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot read binary from " + value, e);
-                               }
-                               return arr;
-                       default:
-                               return value.getString();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot cast value from " + value, e);
-               } finally {
-                       if (binary != null)
-                               binary.dispose();
-               }
-       }
-
-       /**
-        * Retrieves the {@link Session} related to this node.
-        * 
-        * @deprecated Use {@link #getSession(Node)} instead.
-        */
-       @Deprecated
-       public static Session session(Node node) {
-               return getSession(node);
-       }
-
-       /** Retrieves the {@link Session} related to this node. */
-       public static Session getSession(Node node) {
-               try {
-                       return node.getSession();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve session related to " + node, e);
-               }
-       }
-
-       /** Retrieves the root node related to this session. */
-       public static Node getRootNode(Session session) {
-               try {
-                       return session.getRootNode();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get root node for " + session, e);
-               }
-       }
-
-       /** Whether this item exists. */
-       public static boolean itemExists(Session session, String path) {
-               try {
-                       return session.itemExists(path);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check whether " + path + " exists", e);
-               }
-       }
-
-       /**
-        * Saves the {@link Session} related to this node. Note that all other unrelated
-        * modifications in this session will also be saved.
-        */
-       public static void save(Node node) {
-               try {
-                       Session session = node.getSession();
-//                     if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
-//                             set(node, Property.JCR_LAST_MODIFIED, Instant.now());
-//                             set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
-//                     }
-                       if (session.hasPendingChanges())
-                               session.save();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot save session related to " + node + " in workspace "
-                                       + session(node).getWorkspace().getName(), e);
-               }
-       }
-
-       /** Login to a JCR repository. */
-       public static Session login(Repository repository, String workspace) {
-               try {
-                       return repository.login(workspace);
-               } catch (RepositoryException e) {
-                       throw new IllegalArgumentException("Cannot login to repository", e);
-               }
-       }
-
-       /** Safely and silently logs out a session. */
-       public static void logout(Session session) {
-               try {
-                       if (session != null)
-                               if (session.isLive())
-                                       session.logout();
-               } catch (Exception e) {
-                       // silent
-               }
-       }
-
-       /** Safely and silently logs out the underlying session. */
-       public static void logout(Node node) {
-               Jcr.logout(session(node));
-       }
-
-       /*
-        * SECURITY
-        */
-       /**
-        * Add a single privilege to a node.
-        * 
-        * @see Privilege
-        */
-       public static void addPrivilege(Node node, String principal, String privilege) {
-               try {
-                       Session session = node.getSession();
-                       JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
-               }
-       }
-
-       /*
-        * VERSIONING
-        */
-       /** Get checked out status. */
-       public static boolean isCheckedOut(Node node) {
-               try {
-                       return node.isCheckedOut();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot retrieve checked out status of " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkpoint(String) */
-       public static void checkpoint(Node node) {
-               try {
-                       versionManager(node).checkpoint(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check in " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkin(String) */
-       public static void checkin(Node node) {
-               try {
-                       versionManager(node).checkin(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check in " + node, e);
-               }
-       }
-
-       /** @see VersionManager#checkout(String) */
-       public static void checkout(Node node) {
-               try {
-                       versionManager(node).checkout(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot check out " + node, e);
-               }
-       }
-
-       /** Get the {@link VersionManager} related to this node. */
-       public static VersionManager versionManager(Node node) {
-               try {
-                       return node.getSession().getWorkspace().getVersionManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get version manager from " + node, e);
-               }
-       }
-
-       /** Get the {@link VersionHistory} related to this node. */
-       public static VersionHistory getVersionHistory(Node node) {
-               try {
-                       return versionManager(node).getVersionHistory(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get version history from " + node, e);
-               }
-       }
-
-       /**
-        * The linear versions of this version history in reverse order and without the
-        * root version.
-        */
-       public static List<Version> getLinearVersions(VersionHistory versionHistory) {
-               try {
-                       List<Version> lst = new ArrayList<>();
-                       VersionIterator vit = versionHistory.getAllLinearVersions();
-                       while (vit.hasNext())
-                               lst.add(vit.nextVersion());
-                       lst.remove(0);
-                       Collections.reverse(lst);
-                       return lst;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get linear versions from " + versionHistory, e);
-               }
-       }
-
-       /** The frozen node related to this {@link Version}. */
-       public static Node getFrozenNode(Version version) {
-               try {
-                       return version.getFrozenNode();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get frozen node from " + version, e);
-               }
-       }
-
-       /** Get the base {@link Version} related to this node. */
-       public static Version getBaseVersion(Node node) {
-               try {
-                       return versionManager(node).getBaseVersion(node.getPath());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get base version from " + node, e);
-               }
-       }
-
-       /*
-        * FILES
-        */
-       /**
-        * Returns the size of this file.
-        * 
-        * @see NodeType#NT_FILE
-        */
-       public static long getFileSize(Node fileNode) {
-               try {
-                       if (!fileNode.isNodeType(NodeType.NT_FILE))
-                               throw new IllegalArgumentException(fileNode + " must be a file.");
-                       return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get file size of " + fileNode, e);
-               }
-       }
-
-       /** Returns the size of this {@link Binary}. */
-       public static long getBinarySize(Binary binaryArg) {
-               try {
-                       try (Bin binary = new Bin(binaryArg)) {
-                               return binary.getSize();
-                       }
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get file size of binary " + binaryArg, e);
-               }
-       }
-
-       // QUERY
-       /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
-       public static Query createQuery(QueryManager qm, String sql, Object... args) {
-               // fix single quotes
-               sql = sql.replaceAll("'", "''");
-               String query = MessageFormat.format(sql, args);
-               try {
-                       return qm.createQuery(query, Query.JCR_SQL2);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
-               }
-       }
-
-       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
-       public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
-               Query query = createQuery(qm, sql, args);
-               try {
-                       return query.execute().getNodes();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
-               }
-       }
-
-       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
-       public static NodeIterator executeQuery(Session session, String sql, Object... args) {
-               QueryManager queryManager;
-               try {
-                       queryManager = session.getWorkspace().getQueryManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get query manager from session " + session, e);
-               }
-               return executeQuery(queryManager, sql, args);
-       }
-
-       /**
-        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
-        * single node at most.
-        * 
-        * @return the node or <code>null</code> if not found.
-        */
-       public static Node getNode(QueryManager qm, String sql, Object... args) {
-               NodeIterator nit = executeQuery(qm, sql, args);
-               if (nit.hasNext()) {
-                       Node node = nit.nextNode();
-                       if (nit.hasNext())
-                               throw new IllegalStateException(
-                                               "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
-                       return node;
-               } else {
-                       return null;
-               }
-       }
-
-       /**
-        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
-        * single node at most.
-        * 
-        * @return the node or <code>null</code> if not found.
-        */
-       public static Node getNode(Session session, String sql, Object... args) {
-               QueryManager queryManager;
-               try {
-                       queryManager = session.getWorkspace().getQueryManager();
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get query manager from session " + session, e);
-               }
-               return getNode(queryManager, sql, args);
-       }
-
-       /** Singleton. */
-       private Jcr() {
-
-       }
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd b/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/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java
deleted file mode 100644 (file)
index 84e8cd3..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.argeo.jcr.unit;
-
-import java.io.File;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.io.FileUtils;
-import org.argeo.api.cms.CmsLog;
-import org.argeo.jcr.JcrException;
-
-import junit.framework.TestCase;
-
-/** Base for unit tests with a JCR repository. */
-public abstract class AbstractJcrTestCase extends TestCase {
-       private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class);
-
-       private Repository repository;
-       private Session session = null;
-
-       public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN";
-
-       // protected abstract File getRepositoryFile() throws Exception;
-
-       protected abstract Repository createRepository() throws Exception;
-
-       protected abstract void clearRepository(Repository repository) throws Exception;
-
-       @Override
-       protected void setUp() throws Exception {
-               File homeDir = getHomeDir();
-               FileUtils.deleteDirectory(homeDir);
-               repository = createRepository();
-       }
-
-       @Override
-       protected void tearDown() throws Exception {
-               if (session != null) {
-                       session.logout();
-                       if (log.isTraceEnabled())
-                               log.trace("Logout session");
-               }
-               clearRepository(repository);
-       }
-
-       protected Session session() {
-               if (session != null && session.isLive())
-                       return session;
-               Session session;
-               if (getLoginContext() != null) {
-                       LoginContext lc;
-                       try {
-                               lc = new LoginContext(getLoginContext());
-                               lc.login();
-                       } catch (LoginException e) {
-                               throw new IllegalStateException("JAAS login failed", e);
-                       }
-                       session = Subject.doAs(lc.getSubject(), new PrivilegedAction<Session>() {
-
-                               @Override
-                               public Session run() {
-                                       return login();
-                               }
-
-                       });
-               } else
-                       session = login();
-               this.session = session;
-               return this.session;
-       }
-
-       protected String getLoginContext() {
-               return null;
-       }
-
-       protected Session login() {
-               try {
-                       if (log.isTraceEnabled())
-                               log.trace("Login session");
-                       Subject subject = Subject.getSubject(AccessController.getContext());
-                       if (subject != null)
-                               return getRepository().login();
-                       else
-                               return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray()));
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot login to repository", e);
-               }
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       /**
-        * enables children class to set an existing repository in case it is not
-        * deleted on startup, to test migration by instance
-        */
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       protected File getHomeDir() {
-               File homeDir = new File(System.getProperty("java.io.tmpdir"),
-                               AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name"));
-               return homeDir;
-       }
-
-}
diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java
deleted file mode 100644 (file)
index c6e7415..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Helpers for unit tests with JCR repositories. */
-package org.argeo.jcr.unit;
\ No newline at end of file
diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/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/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java b/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/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/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/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java b/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/org.argeo.cms.servlet/.classpath b/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/org.argeo.cms.servlet/.project b/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/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/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/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/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/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/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/org.argeo.cms.servlet/bnd.bnd b/org.argeo.cms.servlet/bnd.bnd
deleted file mode 100644 (file)
index b539a49..0000000
+++ /dev/null
@@ -1,11 +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,\
-*
-
-Service-Component:\
-OSGI-INF/jettyServiceFactory.xml,\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml
diff --git a/org.argeo.cms.servlet/build.properties b/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/org.argeo.cms.servlet/pom.xml b/org.argeo.cms.servlet/pom.xml
deleted file mode 100644 (file)
index a60b42d..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <version>2.3-SNAPSHOT</version>
-               <artifactId>argeo-commons</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.servlet</artifactId>
-       <packaging>jar</packaging>
-       <name>CMS Servlet</name>
-       <description>CMS components depending on the Servlet APIs</description>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java
deleted file mode 100644 (file)
index 95912e4..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.util.Locale;
-import java.util.Objects;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthSession;
-
-public class ServletHttpRequest implements RemoteAuthRequest {
-       private final HttpServletRequest request;
-
-       public ServletHttpRequest(HttpServletRequest request) {
-               Objects.requireNonNull(request);
-               this.request = request;
-       }
-
-       @Override
-       public RemoteAuthSession getSession() {
-               return new ServletHttpSession(request.getSession(false));
-       }
-
-       @Override
-       public RemoteAuthSession createSession() {
-               return new ServletHttpSession(request.getSession(true));
-       }
-
-       @Override
-       public Locale getLocale() {
-               return request.getLocale();
-       }
-
-       @Override
-       public Object getAttribute(String key) {
-               return request.getAttribute(key);
-       }
-
-       @Override
-       public void setAttribute(String key, Object object) {
-               request.setAttribute(key, object);
-       }
-
-       @Override
-       public String getHeader(String key) {
-               return request.getHeader(key);
-       }
-
-       @Override
-       public String getRemoteAddr() {
-               return request.getRemoteAddr();
-       }
-
-       @Override
-       public int getLocalPort() {
-               return request.getLocalPort();
-       }
-
-       @Override
-       public int getRemotePort() {
-               return request.getRemotePort();
-       }
-}
diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/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/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/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/org.argeo.cms.swt/.classpath b/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/org.argeo.cms.swt/.project b/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/org.argeo.cms.swt/META-INF/.gitignore b/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/org.argeo.cms.swt/bnd.bnd b/org.argeo.cms.swt/bnd.bnd
deleted file mode 100644 (file)
index 9d4eae6..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Import-Package: org.eclipse.swt,\
-                               org.eclipse.jface.window,\
-                               org.eclipse.core.commands.common,\
-                               *
-
-Bundle-ActivationPolicy: lazy
-                               
\ No newline at end of file
diff --git a/org.argeo.cms.swt/build.properties b/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/org.argeo.cms.swt/icons/actions/add.png b/org.argeo.cms.swt/icons/actions/add.png
deleted file mode 100644 (file)
index 5c06bf0..0000000
Binary files a/org.argeo.cms.swt/icons/actions/add.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/actions/close-all.png b/org.argeo.cms.swt/icons/actions/close-all.png
deleted file mode 100644 (file)
index 81bfc95..0000000
Binary files a/org.argeo.cms.swt/icons/actions/close-all.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/actions/delete.png b/org.argeo.cms.swt/icons/actions/delete.png
deleted file mode 100644 (file)
index 9712723..0000000
Binary files a/org.argeo.cms.swt/icons/actions/delete.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/actions/edit.png b/org.argeo.cms.swt/icons/actions/edit.png
deleted file mode 100644 (file)
index ad3db9f..0000000
Binary files a/org.argeo.cms.swt/icons/actions/edit.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/actions/save-all.png b/org.argeo.cms.swt/icons/actions/save-all.png
deleted file mode 100644 (file)
index f48ed32..0000000
Binary files a/org.argeo.cms.swt/icons/actions/save-all.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/actions/save.png b/org.argeo.cms.swt/icons/actions/save.png
deleted file mode 100644 (file)
index 1c58ada..0000000
Binary files a/org.argeo.cms.swt/icons/actions/save.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/active.gif b/org.argeo.cms.swt/icons/active.gif
deleted file mode 100644 (file)
index 7d24707..0000000
Binary files a/org.argeo.cms.swt/icons/active.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/add.gif b/org.argeo.cms.swt/icons/add.gif
deleted file mode 100644 (file)
index 252d7eb..0000000
Binary files a/org.argeo.cms.swt/icons/add.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/add.png b/org.argeo.cms.swt/icons/add.png
deleted file mode 100644 (file)
index c7edfec..0000000
Binary files a/org.argeo.cms.swt/icons/add.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/addFolder.gif b/org.argeo.cms.swt/icons/addFolder.gif
deleted file mode 100644 (file)
index d3f43d9..0000000
Binary files a/org.argeo.cms.swt/icons/addFolder.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/addPrivileges.gif b/org.argeo.cms.swt/icons/addPrivileges.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/org.argeo.cms.swt/icons/addPrivileges.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/addRepo.gif b/org.argeo.cms.swt/icons/addRepo.gif
deleted file mode 100644 (file)
index 26d81c0..0000000
Binary files a/org.argeo.cms.swt/icons/addRepo.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/addWorkspace.png b/org.argeo.cms.swt/icons/addWorkspace.png
deleted file mode 100644 (file)
index bbee775..0000000
Binary files a/org.argeo.cms.swt/icons/addWorkspace.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/adminLog.gif b/org.argeo.cms.swt/icons/adminLog.gif
deleted file mode 100644 (file)
index 6ef3bca..0000000
Binary files a/org.argeo.cms.swt/icons/adminLog.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/batch.gif b/org.argeo.cms.swt/icons/batch.gif
deleted file mode 100644 (file)
index b8ca14a..0000000
Binary files a/org.argeo.cms.swt/icons/batch.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/begin.gif b/org.argeo.cms.swt/icons/begin.gif
deleted file mode 100755 (executable)
index feb8e94..0000000
Binary files a/org.argeo.cms.swt/icons/begin.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/binary.png b/org.argeo.cms.swt/icons/binary.png
deleted file mode 100644 (file)
index fdf4f82..0000000
Binary files a/org.argeo.cms.swt/icons/binary.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/browser.gif b/org.argeo.cms.swt/icons/browser.gif
deleted file mode 100644 (file)
index 6c7320c..0000000
Binary files a/org.argeo.cms.swt/icons/browser.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/bundles.gif b/org.argeo.cms.swt/icons/bundles.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/org.argeo.cms.swt/icons/bundles.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/changePassword.gif b/org.argeo.cms.swt/icons/changePassword.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/org.argeo.cms.swt/icons/changePassword.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/clear.gif b/org.argeo.cms.swt/icons/clear.gif
deleted file mode 100644 (file)
index 6bc10f9..0000000
Binary files a/org.argeo.cms.swt/icons/clear.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/close-all.png b/org.argeo.cms.swt/icons/close-all.png
deleted file mode 100644 (file)
index 85d4d42..0000000
Binary files a/org.argeo.cms.swt/icons/close-all.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/commit.gif b/org.argeo.cms.swt/icons/commit.gif
deleted file mode 100755 (executable)
index 876f3eb..0000000
Binary files a/org.argeo.cms.swt/icons/commit.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/delete.png b/org.argeo.cms.swt/icons/delete.png
deleted file mode 100644 (file)
index 676a39d..0000000
Binary files a/org.argeo.cms.swt/icons/delete.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/dumpNode.gif b/org.argeo.cms.swt/icons/dumpNode.gif
deleted file mode 100644 (file)
index 14eb1be..0000000
Binary files a/org.argeo.cms.swt/icons/dumpNode.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/file.gif b/org.argeo.cms.swt/icons/file.gif
deleted file mode 100644 (file)
index ef30288..0000000
Binary files a/org.argeo.cms.swt/icons/file.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/folder.gif b/org.argeo.cms.swt/icons/folder.gif
deleted file mode 100644 (file)
index 42e027c..0000000
Binary files a/org.argeo.cms.swt/icons/folder.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/getSize.gif b/org.argeo.cms.swt/icons/getSize.gif
deleted file mode 100644 (file)
index b05bf3e..0000000
Binary files a/org.argeo.cms.swt/icons/getSize.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/group.png b/org.argeo.cms.swt/icons/group.png
deleted file mode 100644 (file)
index cc6683a..0000000
Binary files a/org.argeo.cms.swt/icons/group.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/home.gif b/org.argeo.cms.swt/icons/home.gif
deleted file mode 100644 (file)
index fd0c669..0000000
Binary files a/org.argeo.cms.swt/icons/home.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/home.png b/org.argeo.cms.swt/icons/home.png
deleted file mode 100644 (file)
index 5eb0967..0000000
Binary files a/org.argeo.cms.swt/icons/home.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/import_fs.png b/org.argeo.cms.swt/icons/import_fs.png
deleted file mode 100644 (file)
index d7c890c..0000000
Binary files a/org.argeo.cms.swt/icons/import_fs.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/installed.gif b/org.argeo.cms.swt/icons/installed.gif
deleted file mode 100644 (file)
index 2988716..0000000
Binary files a/org.argeo.cms.swt/icons/installed.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/log.gif b/org.argeo.cms.swt/icons/log.gif
deleted file mode 100644 (file)
index e3ecc55..0000000
Binary files a/org.argeo.cms.swt/icons/log.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/logout.png b/org.argeo.cms.swt/icons/logout.png
deleted file mode 100644 (file)
index f2952fa..0000000
Binary files a/org.argeo.cms.swt/icons/logout.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/maintenance.gif b/org.argeo.cms.swt/icons/maintenance.gif
deleted file mode 100644 (file)
index e5690ec..0000000
Binary files a/org.argeo.cms.swt/icons/maintenance.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/node.gif b/org.argeo.cms.swt/icons/node.gif
deleted file mode 100644 (file)
index 364c0e7..0000000
Binary files a/org.argeo.cms.swt/icons/node.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/nodes.gif b/org.argeo.cms.swt/icons/nodes.gif
deleted file mode 100644 (file)
index bba3dbc..0000000
Binary files a/org.argeo.cms.swt/icons/nodes.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/osgi_explorer.gif b/org.argeo.cms.swt/icons/osgi_explorer.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/org.argeo.cms.swt/icons/osgi_explorer.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/password.gif b/org.argeo.cms.swt/icons/password.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/org.argeo.cms.swt/icons/password.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/person-logged-in.png b/org.argeo.cms.swt/icons/person-logged-in.png
deleted file mode 100644 (file)
index 87acc14..0000000
Binary files a/org.argeo.cms.swt/icons/person-logged-in.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/person.png b/org.argeo.cms.swt/icons/person.png
deleted file mode 100644 (file)
index 7d979a5..0000000
Binary files a/org.argeo.cms.swt/icons/person.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/query.png b/org.argeo.cms.swt/icons/query.png
deleted file mode 100644 (file)
index 54c089d..0000000
Binary files a/org.argeo.cms.swt/icons/query.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/refresh.png b/org.argeo.cms.swt/icons/refresh.png
deleted file mode 100644 (file)
index 71b3481..0000000
Binary files a/org.argeo.cms.swt/icons/refresh.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/remote_connected.gif b/org.argeo.cms.swt/icons/remote_connected.gif
deleted file mode 100644 (file)
index 1492b4e..0000000
Binary files a/org.argeo.cms.swt/icons/remote_connected.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/remote_disconnected.gif b/org.argeo.cms.swt/icons/remote_disconnected.gif
deleted file mode 100644 (file)
index 6c54da9..0000000
Binary files a/org.argeo.cms.swt/icons/remote_disconnected.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/remove.gif b/org.argeo.cms.swt/icons/remove.gif
deleted file mode 100644 (file)
index 0ae6dec..0000000
Binary files a/org.argeo.cms.swt/icons/remove.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/removePrivileges.gif b/org.argeo.cms.swt/icons/removePrivileges.gif
deleted file mode 100644 (file)
index aa78fd2..0000000
Binary files a/org.argeo.cms.swt/icons/removePrivileges.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/rename.gif b/org.argeo.cms.swt/icons/rename.gif
deleted file mode 100644 (file)
index 8048405..0000000
Binary files a/org.argeo.cms.swt/icons/rename.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/repositories.gif b/org.argeo.cms.swt/icons/repositories.gif
deleted file mode 100644 (file)
index c13bea1..0000000
Binary files a/org.argeo.cms.swt/icons/repositories.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/repository_connected.gif b/org.argeo.cms.swt/icons/repository_connected.gif
deleted file mode 100644 (file)
index a15fa55..0000000
Binary files a/org.argeo.cms.swt/icons/repository_connected.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/repository_disconnected.gif b/org.argeo.cms.swt/icons/repository_disconnected.gif
deleted file mode 100644 (file)
index 4576dc5..0000000
Binary files a/org.argeo.cms.swt/icons/repository_disconnected.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/resolved.gif b/org.argeo.cms.swt/icons/resolved.gif
deleted file mode 100644 (file)
index f4a1ea1..0000000
Binary files a/org.argeo.cms.swt/icons/resolved.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/role.gif b/org.argeo.cms.swt/icons/role.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/org.argeo.cms.swt/icons/role.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/rollback.gif b/org.argeo.cms.swt/icons/rollback.gif
deleted file mode 100755 (executable)
index c753995..0000000
Binary files a/org.argeo.cms.swt/icons/rollback.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/save-all.png b/org.argeo.cms.swt/icons/save-all.png
deleted file mode 100644 (file)
index b68a29b..0000000
Binary files a/org.argeo.cms.swt/icons/save-all.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/save.gif b/org.argeo.cms.swt/icons/save.gif
deleted file mode 100644 (file)
index 654ad7b..0000000
Binary files a/org.argeo.cms.swt/icons/save.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/save.png b/org.argeo.cms.swt/icons/save.png
deleted file mode 100644 (file)
index f27ef2d..0000000
Binary files a/org.argeo.cms.swt/icons/save.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/save_security.png b/org.argeo.cms.swt/icons/save_security.png
deleted file mode 100644 (file)
index ca41dc9..0000000
Binary files a/org.argeo.cms.swt/icons/save_security.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/save_security_disabled.png b/org.argeo.cms.swt/icons/save_security_disabled.png
deleted file mode 100644 (file)
index fb7d08d..0000000
Binary files a/org.argeo.cms.swt/icons/save_security_disabled.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/security.gif b/org.argeo.cms.swt/icons/security.gif
deleted file mode 100644 (file)
index 57fb95e..0000000
Binary files a/org.argeo.cms.swt/icons/security.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/service_published.gif b/org.argeo.cms.swt/icons/service_published.gif
deleted file mode 100644 (file)
index 17f771a..0000000
Binary files a/org.argeo.cms.swt/icons/service_published.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/service_referenced.gif b/org.argeo.cms.swt/icons/service_referenced.gif
deleted file mode 100644 (file)
index c24a95f..0000000
Binary files a/org.argeo.cms.swt/icons/service_referenced.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/sort.gif b/org.argeo.cms.swt/icons/sort.gif
deleted file mode 100644 (file)
index 23c5d0b..0000000
Binary files a/org.argeo.cms.swt/icons/sort.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/starting.gif b/org.argeo.cms.swt/icons/starting.gif
deleted file mode 100644 (file)
index 563743d..0000000
Binary files a/org.argeo.cms.swt/icons/starting.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/sync.gif b/org.argeo.cms.swt/icons/sync.gif
deleted file mode 100644 (file)
index b4fa052..0000000
Binary files a/org.argeo.cms.swt/icons/sync.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/user.gif b/org.argeo.cms.swt/icons/user.gif
deleted file mode 100644 (file)
index 90a0014..0000000
Binary files a/org.argeo.cms.swt/icons/user.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/users.gif b/org.argeo.cms.swt/icons/users.gif
deleted file mode 100644 (file)
index 2de7edd..0000000
Binary files a/org.argeo.cms.swt/icons/users.gif and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/workgroup.png b/org.argeo.cms.swt/icons/workgroup.png
deleted file mode 100644 (file)
index 7fef996..0000000
Binary files a/org.argeo.cms.swt/icons/workgroup.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/workgroup.xcf b/org.argeo.cms.swt/icons/workgroup.xcf
deleted file mode 100644 (file)
index f517c82..0000000
Binary files a/org.argeo.cms.swt/icons/workgroup.xcf and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/workspace_connected.png b/org.argeo.cms.swt/icons/workspace_connected.png
deleted file mode 100644 (file)
index 0430baa..0000000
Binary files a/org.argeo.cms.swt/icons/workspace_connected.png and /dev/null differ
diff --git a/org.argeo.cms.swt/icons/workspace_disconnected.png b/org.argeo.cms.swt/icons/workspace_disconnected.png
deleted file mode 100644 (file)
index fddcb8c..0000000
Binary files a/org.argeo.cms.swt/icons/workspace_disconnected.png and /dev/null differ
diff --git a/org.argeo.cms.swt/pom.xml b/org.argeo.cms.swt/pom.xml
deleted file mode 100644 (file)
index f31cfa0..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <version>2.3-SNAPSHOT</version>
-               <artifactId>argeo-commons</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.swt</artifactId>
-       <name>CMS SWT</name>
-       <dependencies>
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.commons</groupId> -->
-<!--                   <artifactId>org.argeo.util</artifactId> -->
-<!--                   <version>2.1.89-SNAPSHOT</version> -->
-<!--           </dependency> -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.servlet</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-
-               <!-- Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons.rap</groupId>
-                       <artifactId>org.argeo.swt.specific.rap</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-               <!-- UI -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.rwt</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.commands</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.jface</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java
deleted file mode 100644 (file)
index a94d707..0000000
+++ /dev/null
@@ -1,251 +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
-        */
-       public static GridLayout noSpaceGridLayout() {
-               return noSpaceGridLayout(new GridLayout());
-       }
-
-       public static GridLayout noSpaceGridLayout(int columns) {
-               return noSpaceGridLayout(new GridLayout(columns, false));
-       }
-
-       /** @return the same layout, with spaces removed. */
-       public static GridLayout noSpaceGridLayout(GridLayout layout) {
-               layout.horizontalSpacing = 0;
-               layout.verticalSpacing = 0;
-               layout.marginWidth = 0;
-               layout.marginHeight = 0;
-               return layout;
-       }
-
-       public static GridData fillAll() {
-               return new GridData(SWT.FILL, SWT.FILL, true, true);
-       }
-
-       public static GridData fillWidth() {
-               return grabWidth(SWT.FILL, SWT.FILL);
-       }
-
-       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
-               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
-       }
-
-       public static GridData fillHeight() {
-               return grabHeight(SWT.FILL, SWT.FILL);
-       }
-
-       public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
-               return new GridData(horizontalAlignment, horizontalAlignment, false, true);
-       }
-
-       /*
-        * ROW LAYOUT
-        */
-       /** @return the same layout, with margins removed. */
-       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
-               rowLayout.marginTop = 0;
-               rowLayout.marginBottom = 0;
-               rowLayout.marginLeft = 0;
-               rowLayout.marginRight = 0;
-               return rowLayout;
-       }
-
-       public static RowLayout noMarginsRowLayout(int type) {
-               return noMarginsRowLayout(new RowLayout(type));
-       }
-
-       public static RowData rowData16px() {
-               return new RowData(16, 16);
-       }
-
-       /*
-        * FORM LAYOUT
-        */
-       public static FormData coverAll() {
-               FormData fdLabel = new FormData();
-               fdLabel.top = new FormAttachment(0, 0);
-               fdLabel.left = new FormAttachment(0, 0);
-               fdLabel.right = new FormAttachment(100, 0);
-               fdLabel.bottom = new FormAttachment(100, 0);
-               return fdLabel;
-       }
-
-       /*
-        * STYLING
-        */
-
-       /** Style widget */
-       public static <T extends Widget> T style(T widget, String style) {
-               if (style == null)
-                       return widget;// does nothing
-               EclipseUiSpecificUtils.setStyleData(widget, style);
-               if (widget instanceof Control) {
-                       CmsView cmsView = getCmsView((Control) widget);
-                       if (cmsView != null)
-                               cmsView.applyStyles(widget);
-               }
-               return widget;
-       }
-
-       /** Style widget */
-       public static <T extends Widget> T style(T widget, CmsStyle style) {
-               return style(widget, style.style());
-       }
-
-       /** Enable markups on widget */
-       public static <T extends Widget> T markup(T widget) {
-               EclipseUiSpecificUtils.setMarkupData(widget);
-               return widget;
-       }
-
-       /** Disable markup validation. */
-       public static <T extends Widget> T disableMarkupValidation(T widget) {
-               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
-               return widget;
-       }
-
-       /**
-        * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
-        * 
-        * @param widget the widget to style and to use in order to display text
-        * @param txt    the object to display via its <code>toString()</code> method.
-        *               This argument should not be null, but if it is null and
-        *               assertions are disabled "<null>" is displayed instead; if
-        *               assertions are enabled the call will fail.
-        * 
-        * @see markup
-        */
-       public static <T extends Widget> T text(T widget, Object txt) {
-               assert txt != null;
-               String str = txt != null ? txt.toString() : "<null>";
-               markup(widget);
-               if (widget instanceof Label)
-                       ((Label) widget).setText(str);
-               else if (widget instanceof Button)
-                       ((Button) widget).setText(str);
-               else if (widget instanceof Text)
-                       ((Text) widget).setText(str);
-               else
-                       throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
-               return widget;
-       }
-
-       /** A {@link Label} with markup activated. */
-       public static Label lbl(Composite parent, Object txt) {
-               return text(new Label(parent, SWT.NONE), txt);
-       }
-
-       /** A read-only {@link Text} whose content can be copy/pasted. */
-       public static Text txt(Composite parent, Object txt) {
-               return text(new Text(parent, SWT.NONE), txt);
-       }
-
-       /** Dispose all children of a Composite */
-       public static void clear(Composite composite) {
-               if (composite.isDisposed())
-                       return;
-               for (Control child : composite.getChildren())
-                       child.dispose();
-       }
-}
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java
deleted file mode 100644 (file)
index 9c8680c..0000000
+++ /dev/null
@@ -1,336 +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;
-
-       public CmsLogin(CmsView cmsView) {
-               this.cmsView = cmsView;
-               CmsContext nodeState = null;// = Activator.getNodeState();
-               // FIXME reactivate locales
-               if (nodeState != null) {
-                       defaultLocale = nodeState.getDefaultLocale();
-                       List<Locale> locales = nodeState.getLocales();
-                       if (locales != null)
-                               localeChoice = new LocaleChoice(locales, defaultLocale);
-               } else {
-                       defaultLocale = Locale.getDefault();
-               }
-               loginSelectionListener = new SelectionListener() {
-                       private static final long serialVersionUID = -8832133363830973578L;
-
-                       @Override
-                       public void widgetSelected(SelectionEvent e) {
-                               login();
-                       }
-
-                       @Override
-                       public void widgetDefaultSelected(SelectionEvent e) {
-                       }
-               };
-       }
-
-       protected boolean isAnonymous() {
-               return cmsView.isAnonymous();
-       }
-
-       public final void createUi(Composite parent) {
-               this.parent = parent;
-               createContents(parent);
-       }
-
-       protected void createContents(Composite parent) {
-               defaultCreateContents(parent);
-       }
-
-       public final void defaultCreateContents(Composite parent) {
-               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               Composite credentialsBlock = createCredentialsBlock(parent);
-               if (parent instanceof Shell) {
-                       credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
-               }
-       }
-
-       public final Composite createCredentialsBlock(Composite parent) {
-               if (isAnonymous()) {
-                       return anonymousUi(parent);
-               } else {
-                       return userUi(parent);
-               }
-       }
-
-       public Composite getCredentialsBlock() {
-               return credentialsBlock;
-       }
-
-       protected Composite userUi(Composite parent) {
-               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
-               credentialsBlock = new Composite(parent, SWT.NONE);
-               credentialsBlock.setLayout(new GridLayout());
-               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
-
-               specificUserUi(credentialsBlock);
-
-               Label l = new Label(credentialsBlock, SWT.NONE);
-               CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
-               l.setText(CmsMsg.logout.lead(locale));
-               GridData lData = CmsSwtUtils.fillWidth();
-               lData.widthHint = 120;
-               l.setLayoutData(lData);
-
-               l.addMouseListener(new MouseAdapter() {
-                       private static final long serialVersionUID = 6444395812777413116L;
-
-                       public void mouseDown(MouseEvent e) {
-                               logout();
-                       }
-               });
-               return credentialsBlock;
-       }
-
-       /** To be overridden */
-       protected void specificUserUi(Composite parent) {
-
-       }
-
-       protected Composite anonymousUi(Composite parent) {
-               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
-               // We need a composite for the traversal
-               credentialsBlock = new Composite(parent, SWT.NONE);
-               credentialsBlock.setLayout(new GridLayout());
-               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
-               CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
-
-               Integer textWidth = 120;
-               if (parent instanceof Shell)
-                       CmsSwtUtils.style(parent, CMS_USER_MENU);
-               // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
-               usernameT = new Text(credentialsBlock, SWT.BORDER);
-               usernameT.setMessage(username.lead(locale));
-               CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
-               GridData gd = CmsSwtUtils.fillWidth();
-               gd.widthHint = textWidth;
-               usernameT.setLayoutData(gd);
-
-               // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
-               passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
-               passwordT.setMessage(password.lead(locale));
-               CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
-               gd = CmsSwtUtils.fillWidth();
-               gd.widthHint = textWidth;
-               passwordT.setLayoutData(gd);
-
-               TraverseListener tl = new TraverseListener() {
-                       private static final long serialVersionUID = -1158892811534971856L;
-
-                       public void keyTraversed(TraverseEvent e) {
-                               if (e.detail == SWT.TRAVERSE_RETURN)
-                                       login();
-                       }
-               };
-               credentialsBlock.addTraverseListener(tl);
-               usernameT.addTraverseListener(tl);
-               passwordT.addTraverseListener(tl);
-               parent.setTabList(new Control[] { credentialsBlock });
-               credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
-
-               // Button
-               Button loginButton = new Button(credentialsBlock, SWT.PUSH);
-               loginButton.setText(CmsMsg.login.lead(locale));
-               loginButton.setLayoutData(CmsSwtUtils.fillWidth());
-               loginButton.addSelectionListener(loginSelectionListener);
-
-               extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
-               if (localeChoice != null)
-                       createLocalesBlock(credentialsBlock);
-               return credentialsBlock;
-       }
-
-       /**
-        * To be overridden in order to provide custom login button and other links.
-        */
-       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
-                       SelectionListener loginSelectionListener) {
-
-       }
-
-       protected void updateLocale(Locale selectedLocale) {
-               // save already entered values
-               String usernameStr = usernameT.getText();
-               char[] pwd = passwordT.getTextChars();
-
-               for (Control child : parent.getChildren())
-                       child.dispose();
-               createContents(parent);
-               if (parent.getParent() != null)
-                       parent.getParent().layout(true, true);
-               else
-                       parent.layout();
-               usernameT.setText(usernameStr);
-               passwordT.setTextChars(pwd);
-       }
-
-       protected Composite createLocalesBlock(final Composite parent) {
-               Composite c = new Composite(parent, SWT.NONE);
-               CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
-               c.setLayout(CmsSwtUtils.noSpaceGridLayout());
-               c.setLayoutData(CmsSwtUtils.fillAll());
-
-               SelectionListener selectionListener = new SelectionAdapter() {
-                       private static final long serialVersionUID = 4891637813567806762L;
-
-                       public void widgetSelected(SelectionEvent event) {
-                               Button button = (Button) event.widget;
-                               if (button.getSelection()) {
-                                       localeChoice.setSelectedIndex((Integer) event.widget.getData());
-                                       updateLocale(localeChoice.getSelectedLocale());
-                               }
-                       };
-               };
-
-               List<Locale> locales = localeChoice.getLocales();
-               for (Integer i = 0; i < locales.size(); i++) {
-                       Locale locale = locales.get(i);
-                       Button button = new Button(c, SWT.RADIO);
-                       CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
-                       button.setData(i);
-                       button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
-                       // button.addListener(SWT.Selection, listener);
-                       button.addSelectionListener(selectionListener);
-                       if (i == localeChoice.getSelectedIndex())
-                               button.setSelection(true);
-               }
-               return c;
-       }
-
-       protected boolean login() {
-               // TODO use CmsVie in order to retrieve subject?
-               // Subject subject = cmsView.getLoginContext().getSubject();
-               // LoginContext loginContext = cmsView.getLoginContext();
-               try {
-                       //
-                       // LOGIN
-                       //
-                       // loginContext.logout();
-                       LoginContext loginContext;
-                       if (subject == null)
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
-                       else
-                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
-                       loginContext.login();
-                       cmsView.authChange(loginContext);
-                       return true;
-               } catch (LoginException e) {
-                       if (log.isTraceEnabled())
-                               log.warn("Login failed: " + e.getMessage(), e);
-                       else
-                               log.warn("Login failed: " + e.getMessage());
-
-                       try {
-                               Thread.sleep(3000);
-                       } catch (InterruptedException e2) {
-                               // silent
-                       }
-                       // ErrorFeedback.show("Login failed", e);
-                       return false;
-               }
-               // catch (LoginException e) {
-               // log.error("Cannot login", e);
-               // return false;
-               // }
-       }
-
-       protected void logout() {
-               cmsView.logout();
-               cmsView.navigateTo("~");
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               for (Callback callback : callbacks) {
-                       if (callback instanceof NameCallback && usernameT != null)
-                               ((NameCallback) callback).setName(usernameT.getText());
-                       else if (callback instanceof PasswordCallback && passwordT != null)
-                               ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
-                       else if (callback instanceof RemoteAuthCallback) {
-                               ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
-                               ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
-                       } else if (callback instanceof LanguageCallback) {
-                               Locale toUse = null;
-                               if (localeChoice != null)
-                                       toUse = localeChoice.getSelectedLocale();
-                               else if (defaultLocale != null)
-                                       toUse = defaultLocale;
-
-                               if (toUse != null) {
-                                       ((LanguageCallback) callback).setLocale(toUse);
-                                       UiContext.setLocale(toUse);
-                               }
-
-                       }
-               }
-       }
-
-       public void setSubject(Subject subject) {
-               this.subject = subject;
-       }
-
-}
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java
deleted file mode 100644 (file)
index f6a35f1..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.swt.auth;
-
-import org.argeo.api.cms.CmsView;
-import org.argeo.cms.swt.CmsSwtUtils;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** The site-related user menu */
-public class CmsLoginShell extends CmsLogin {
-       private final Shell shell;
-
-       public CmsLoginShell(CmsView cmsView) {
-               super(cmsView);
-               shell = createShell();
-//             createUi(shell);
-       }
-
-       /** To be overridden. */
-       protected Shell createShell() {
-               Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
-               shell.setMaximized(true);
-               return shell;
-       }
-
-       /** To be overridden. */
-       public void open() {
-               CmsSwtUtils.style(shell, CMS_USER_MENU);
-               shell.open();
-       }
-
-       @Override
-       protected boolean login() {
-               boolean success = false;
-               try {
-                       success = super.login();
-                       return success;
-               } finally {
-                       if (success)
-                               closeShell();
-                       else {
-                               for (Control child : shell.getChildren())
-                                       child.dispose();
-                               createUi(shell);
-                               shell.layout();
-                               // TODO error message
-                       }
-               }
-       }
-
-       @Override
-       protected void logout() {
-               closeShell();
-               super.logout();
-       }
-
-       protected void closeShell() {
-               if (!shell.isDisposed()) {
-                       shell.close();
-                       shell.dispose();
-               }
-       }
-
-       public Shell getShell() {
-               return shell;
-       }
-       
-       public void createUi(){
-               createUi(shell);
-       }
-}
diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/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/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/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/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png
deleted file mode 100644 (file)
index ce2f2a8..0000000
Binary files a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png and /dev/null differ
diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png
deleted file mode 100644 (file)
index c31f37e..0000000
Binary files a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png and /dev/null differ
diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/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/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/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/org.argeo.cms.ui/.classpath b/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/org.argeo.cms.ui/.project b/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/org.argeo.cms.ui/META-INF/.gitignore b/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/org.argeo.cms.ui/bnd.bnd b/org.argeo.cms.ui/bnd.bnd
deleted file mode 100644 (file)
index ed84241..0000000
+++ /dev/null
@@ -1,22 +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,\
-*
-
-## 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/org.argeo.cms.ui/build.properties b/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/org.argeo.cms.ui/icons/loading.gif b/org.argeo.cms.ui/icons/loading.gif
deleted file mode 100644 (file)
index 3288d10..0000000
Binary files a/org.argeo.cms.ui/icons/loading.gif and /dev/null differ
diff --git a/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png b/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png
deleted file mode 100644 (file)
index 0396506..0000000
Binary files a/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png and /dev/null differ
diff --git a/org.argeo.cms.ui/icons/noPic-square-640px.png b/org.argeo.cms.ui/icons/noPic-square-640px.png
deleted file mode 100644 (file)
index 8e3abb5..0000000
Binary files a/org.argeo.cms.ui/icons/noPic-square-640px.png and /dev/null differ
diff --git a/org.argeo.cms.ui/pom.xml b/org.argeo.cms.ui/pom.xml
deleted file mode 100644 (file)
index b68cb9a..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-       <modelVersion>4.0.0</modelVersion>
-       <parent>
-               <groupId>org.argeo.commons</groupId>
-               <artifactId>argeo-commons</artifactId>
-               <version>2.3-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.ui</artifactId>
-       <name>CMS UI</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.swt</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.jcr</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-               </dependency>
-               
-               <!-- Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons.rap</groupId>
-                       <artifactId>org.argeo.swt.specific.rap</artifactId>
-                       <version>2.3-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-
-               <!-- UI -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.rwt</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.commands</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.jface</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-
-               <!-- TODO move it to specific -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.filedialog</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.fileupload</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/DefaultImageManager.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java
deleted file mode 100644 (file)
index 316cb51..0000000
+++ /dev/null
@@ -1,55 +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) {
-               super(CmsUiUtils.getCmsView());
-               this.context = context;
-               createUi();
-               if (source == null)
-                       throw new CmsException("Source control cannot be null.");
-               this.source = source;
-               open();
-       }
-
-       @Override
-       protected Shell createShell() {
-               return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
-       }
-
-       @Override
-       public void open() {
-               Shell shell = getShell();
-               shell.pack();
-               shell.layout();
-               shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
-               shell.addShellListener(new ShellAdapter() {
-                       private static final long serialVersionUID = 5178980294808435833L;
-
-                       @Override
-                       public void shellDeactivated(ShellEvent e) {
-                               closeShell();
-                       }
-               });
-               super.open();
-       }
-
-       protected Node getContext() {
-               return context;
-       }
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java b/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/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/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/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/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/pom.xml b/pom.xml
index 0a265cf64a93f9a1a16e83f55625f9364649a5c0..9221b4720e1e53a8dad7d50f865473f4f4a7433c 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                <module>org.argeo.cms.tp</module>
                <module>org.argeo.cms</module>
                <module>org.argeo.cms.pgsql</module>
-               <module>org.argeo.cms.servlet</module>
-               <module>org.argeo.cms.jcr</module>
-               <!-- CMS UX -->
-               <module>org.argeo.cms.swt</module>
-               <module>org.argeo.cms.ui</module>
-               <module>org.argeo.cms.e4</module>
+               <!-- CMS SWT -->
+               <module>eclipse</module>
+               <!-- CMS JCR -->
+               <module>jcr</module>
                <!-- Eclipse RAP/RCP specific -->
                <module>rcp</module>
                <module>rap</module>