Merge remote-tracking branch 'origin/unstable' into testing
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 27 Feb 2022 08:29:12 +0000 (09:29 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 27 Feb 2022 08:29:12 +0000 (09:29 +0100)
Conflicts:
cnf/build.bnd
cnf/maven.bnd
demo/pom.xml
dep/org.argeo.dep.cms.client/pom.xml
dep/org.argeo.dep.cms.e4.rap/pom.xml
dep/org.argeo.dep.cms.ext/pom.xml
dep/org.argeo.dep.cms.node/pom.xml
dep/org.argeo.dep.cms.ui.rap/pom.xml
dep/pom.xml
dist/argeo-cli/pom.xml
dist/argeo-node/pom.xml
dist/containers/pom.xml
dist/osgi-boot/pom.xml
dist/pom.xml
org.argeo.api/pom.xml
org.argeo.cms.e4.rap/pom.xml
org.argeo.cms.e4/pom.xml
org.argeo.cms.ui.rap/pom.xml
org.argeo.cms.ui.theme/pom.xml
org.argeo.cms.ui/pom.xml
org.argeo.cms/pom.xml
org.argeo.core/pom.xml
org.argeo.eclipse.ui.rap/pom.xml
org.argeo.eclipse.ui/pom.xml
org.argeo.enterprise/pom.xml
org.argeo.jcr/pom.xml
org.argeo.maintenance/pom.xml
org.argeo.osgi.boot/pom.xml
pom.xml
sdk/pom.xml

1894 files changed:
.gitignore
.gitmodules [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile-rcp.mk [new file with mode: 0644]
branch.mk [new file with mode: 0644]
cnf/argeo.bnd [deleted file]
cnf/build.bnd
cnf/maven.bnd [deleted file]
cnf/testing.bnd
cnf/unstable.bnd
configure [new file with mode: 0644]
demo/.gitignore [deleted file]
demo/all.policy [deleted file]
demo/argeo_node_osgiboot.properties [deleted file]
demo/cms-cluster_0.properties [deleted file]
demo/cms-cluster_1.properties [deleted file]
demo/cms-e4-rap.properties [deleted file]
demo/cms-local.properties [deleted file]
demo/cms-pgsql-ldap.properties [deleted file]
demo/init/node/.gitignore [deleted file]
demo/init/node/ou=roles,ou=node.ldif [deleted file]
demo/log4j.properties [deleted file]
demo/pom.xml [deleted file]
demo/ssl/.gitignore [deleted file]
demo/ssl/openssl.cnf [deleted file]
demo/ssl/openssl_root.cnf [deleted file]
demo/ssl/ssl.sh [deleted file]
dep/.gitignore [deleted file]
dep/cnf/maven.bnd [deleted file]
dep/org.argeo.dep.cms.client/bnd.bnd [deleted file]
dep/org.argeo.dep.cms.client/build.properties [deleted file]
dep/org.argeo.dep.cms.client/p2.inf [deleted file]
dep/org.argeo.dep.cms.client/pom.xml [deleted file]
dep/org.argeo.dep.cms.e4.rap/bnd.bnd [deleted file]
dep/org.argeo.dep.cms.e4.rap/build.properties [deleted file]
dep/org.argeo.dep.cms.e4.rap/p2.inf [deleted file]
dep/org.argeo.dep.cms.e4.rap/pom.xml [deleted file]
dep/org.argeo.dep.cms.ext/bnd.bnd [deleted file]
dep/org.argeo.dep.cms.ext/build.properties [deleted file]
dep/org.argeo.dep.cms.ext/p2.inf [deleted file]
dep/org.argeo.dep.cms.ext/pom.xml [deleted file]
dep/org.argeo.dep.cms.node/bnd.bnd [deleted file]
dep/org.argeo.dep.cms.node/build.properties [deleted file]
dep/org.argeo.dep.cms.node/p2.inf [deleted file]
dep/org.argeo.dep.cms.node/pom.xml [deleted file]
dep/org.argeo.dep.cms.ui.rap/bnd.bnd [deleted file]
dep/org.argeo.dep.cms.ui.rap/p2.inf [deleted file]
dep/org.argeo.dep.cms.ui.rap/pom.xml [deleted file]
dep/pom.xml [deleted file]
dist/argeo-cli/assembly/argeo-cli.xml [deleted file]
dist/argeo-cli/base/bin/argeo [deleted file]
dist/argeo-cli/base/etc/argeo-cli/log4j.properties [deleted file]
dist/argeo-cli/base/etc/argeo-cli/settings.sh [deleted file]
dist/argeo-cli/native-image/jni-config.json [deleted file]
dist/argeo-cli/native-image/proxy-config.json [deleted file]
dist/argeo-cli/native-image/reflect-config.json [deleted file]
dist/argeo-cli/native-image/resource-config.json [deleted file]
dist/argeo-cli/pom.xml [deleted file]
dist/argeo-cli/rpm/usr/bin/argeo [deleted file]
dist/argeo-node/assembly/cms-e4-rap.xml [deleted file]
dist/argeo-node/base/bin/argeo-cms [deleted file]
dist/argeo-node/base/bin/argeo-cms.jsh [deleted file]
dist/argeo-node/base/etc/argeo.d/config-template.ini [deleted file]
dist/argeo-node/base/etc/argeo.d/jvm.args [deleted file]
dist/argeo-node/base/etc/argeo.d/jvm.args.debug [deleted file]
dist/argeo-node/base/etc/argeo.d/log4j.properties [deleted file]
dist/argeo-node/base/etc/argeo/argeo.ini [deleted file]
dist/argeo-node/base/etc/argeo/conf.d/app-template.txt [deleted file]
dist/argeo-node/base/etc/argeo/log4j.properties [deleted file]
dist/argeo-node/base/etc/argeo/settings.sh [deleted file]
dist/argeo-node/base/share/argeo/SETUP.txt [deleted file]
dist/argeo-node/base/share/argeo/all.policy [deleted file]
dist/argeo-node/base/share/argeo/argeo-pgsql-setup.sql [deleted file]
dist/argeo-node/base/share/argeo/argeo-slapd-setup.inf [deleted file]
dist/argeo-node/base/share/argeo/cms.jsh [deleted file]
dist/argeo-node/base/share/argeo/config.ini [deleted file]
dist/argeo-node/base/share/argeo/jvm.args [deleted file]
dist/argeo-node/pom.xml [deleted file]
dist/argeo-node/rpm/usr/lib/systemd/system/argeo.service [deleted file]
dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service [deleted file]
dist/argeo-node/rpm/usr/lib/systemd/user/argeo@.service [deleted file]
dist/argeo-node/rpm/usr/sbin/argeoctl [deleted file]
dist/containers/argeo2-all [deleted file]
dist/containers/argeo2-builder [deleted file]
dist/containers/argeo2-java [deleted file]
dist/containers/argeo2-lists [deleted file]
dist/containers/argeo2-node [deleted file]
dist/containers/argeo2-node-snapshots [deleted file]
dist/containers/argeo2-rpmfactory.repo [deleted file]
dist/containers/argeo2-snapshots.repo [deleted file]
dist/containers/argeo2-tp [deleted file]
dist/containers/buildah-common [deleted file]
dist/containers/buildah-login [deleted file]
dist/containers/buildah-metadata [deleted file]
dist/containers/dev-settings.sh [deleted file]
dist/containers/filtered/buildah-metadata [deleted file]
dist/containers/maven.conf [deleted file]
dist/containers/pom.xml [deleted file]
dist/osgi-boot/assembly/osgi-boot.xml [deleted file]
dist/osgi-boot/base/bin/a2sh [deleted file]
dist/osgi-boot/pom.xml [deleted file]
dist/osgi-boot/rpm/usr/bin/a2sh [deleted file]
dist/osgi-boot/rpm/usr/share/osgi/boot/framework.args [deleted file]
dist/pom.xml [deleted file]
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/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/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/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]
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/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/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/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/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/RowColumnLabelProvider.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]
jni/.cproject [new file with mode: 0644]
jni/.project [new file with mode: 0644]
jni/.settings/language.settings.xml [new file with mode: 0644]
jni/.settings/org.eclipse.cdt.core.prefs [new file with mode: 0644]
jni/Makefile [new file with mode: 0644]
jni/jni.mk [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/.gitignore [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/Makefile [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c [new file with mode: 0644]
jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h [new file with mode: 0644]
org.argeo.api.acr/.classpath [new file with mode: 0644]
org.argeo.api.acr/.project [new file with mode: 0644]
org.argeo.api.acr/bnd.bnd [new file with mode: 0644]
org.argeo.api.acr/build.properties [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/Content.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/CrName.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java [new file with mode: 0644]
org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java [new file with mode: 0644]
org.argeo.api.cms/.classpath [new file with mode: 0644]
org.argeo.api.cms/.project [new file with mode: 0644]
org.argeo.api.cms/META-INF/.gitignore [new file with mode: 0644]
org.argeo.api.cms/bnd.bnd [new file with mode: 0644]
org.argeo.api.cms/build.properties [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java [new file with mode: 0644]
org.argeo.api.uuid/.classpath [new file with mode: 0644]
org.argeo.api.uuid/.project [new file with mode: 0644]
org.argeo.api.uuid/META-INF/.gitignore [new file with mode: 0644]
org.argeo.api.uuid/bnd.bnd [new file with mode: 0644]
org.argeo.api.uuid/build.properties [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/APM.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/AsyncUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/NoOpUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidBinaryUtils.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/UuidHolder.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/DirectLibuuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/LibuuidFactory.java [new file with mode: 0644]
org.argeo.api.uuid/src/org/argeo/api/uuid/package-info.java [new file with mode: 0644]
org.argeo.api/.classpath [deleted file]
org.argeo.api/.project [deleted file]
org.argeo.api/META-INF/.gitignore [deleted file]
org.argeo.api/bnd.bnd [deleted file]
org.argeo.api/build.properties [deleted file]
org.argeo.api/pom.xml [deleted file]
org.argeo.api/src/org/argeo/api/ArgeoLogListener.java [deleted file]
org.argeo.api/src/org/argeo/api/ArgeoLogger.java [deleted file]
org.argeo.api/src/org/argeo/api/DataAdminLoginModule.java [deleted file]
org.argeo.api/src/org/argeo/api/DataModelNamespace.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeConstants.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeDeployment.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeInstance.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeNames.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeOID.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeState.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeTypes.java [deleted file]
org.argeo.api/src/org/argeo/api/NodeUtils.java [deleted file]
org.argeo.api/src/org/argeo/api/PublishNamespace.java [deleted file]
org.argeo.api/src/org/argeo/api/ldap.cnd [deleted file]
org.argeo.api/src/org/argeo/api/node.cnd [deleted file]
org.argeo.api/src/org/argeo/api/package-info.java [deleted file]
org.argeo.api/src/org/argeo/api/security/AnonymousPrincipal.java [deleted file]
org.argeo.api/src/org/argeo/api/security/CryptoKeyring.java [deleted file]
org.argeo.api/src/org/argeo/api/security/DataAdminPrincipal.java [deleted file]
org.argeo.api/src/org/argeo/api/security/Keyring.java [deleted file]
org.argeo.api/src/org/argeo/api/security/NodeSecurityUtils.java [deleted file]
org.argeo.api/src/org/argeo/api/security/PBEKeySpecCallback.java [deleted file]
org.argeo.api/src/org/argeo/api/security/package-info.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/ArrayTabularRow.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/TabularColumn.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/TabularContent.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/TabularRow.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/TabularRowIterator.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/TabularWriter.java [deleted file]
org.argeo.api/src/org/argeo/api/tabular/package-info.java [deleted file]
org.argeo.cms.e4.rap/.classpath [deleted file]
org.argeo.cms.e4.rap/.project [deleted file]
org.argeo.cms.e4.rap/META-INF/.gitignore [deleted file]
org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml [deleted file]
org.argeo.cms.e4.rap/bnd.bnd [deleted file]
org.argeo.cms.e4.rap/build.properties [deleted file]
org.argeo.cms.e4.rap/pom.xml [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java [deleted file]
org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java [deleted file]
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.pgsql/.classpath [new file with mode: 0644]
org.argeo.cms.pgsql/.project [new file with mode: 0644]
org.argeo.cms.pgsql/bnd.bnd [new file with mode: 0644]
org.argeo.cms.pgsql/build.properties [new file with mode: 0644]
org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java [new file with mode: 0644]
org.argeo.cms.ui.rap/.classpath [deleted file]
org.argeo.cms.ui.rap/.project [deleted file]
org.argeo.cms.ui.rap/META-INF/.gitignore [deleted file]
org.argeo.cms.ui.rap/bnd.bnd [deleted file]
org.argeo.cms.ui.rap/build.properties [deleted file]
org.argeo.cms.ui.rap/pom.xml [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java [deleted file]
org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java [deleted file]
org.argeo.cms.ui.theme/.classpath [deleted file]
org.argeo.cms.ui.theme/.project [deleted file]
org.argeo.cms.ui.theme/META-INF/.gitignore [deleted file]
org.argeo.cms.ui.theme/bnd.bnd [deleted file]
org.argeo.cms.ui.theme/build.properties [deleted file]
org.argeo.cms.ui.theme/icons/actions/add.png [deleted file]
org.argeo.cms.ui.theme/icons/actions/close-all.png [deleted file]
org.argeo.cms.ui.theme/icons/actions/delete.png [deleted file]
org.argeo.cms.ui.theme/icons/actions/edit.png [deleted file]
org.argeo.cms.ui.theme/icons/actions/save-all.png [deleted file]
org.argeo.cms.ui.theme/icons/actions/save.png [deleted file]
org.argeo.cms.ui.theme/icons/active.gif [deleted file]
org.argeo.cms.ui.theme/icons/add.gif [deleted file]
org.argeo.cms.ui.theme/icons/add.png [deleted file]
org.argeo.cms.ui.theme/icons/addFolder.gif [deleted file]
org.argeo.cms.ui.theme/icons/addPrivileges.gif [deleted file]
org.argeo.cms.ui.theme/icons/addRepo.gif [deleted file]
org.argeo.cms.ui.theme/icons/addWorkspace.png [deleted file]
org.argeo.cms.ui.theme/icons/adminLog.gif [deleted file]
org.argeo.cms.ui.theme/icons/batch.gif [deleted file]
org.argeo.cms.ui.theme/icons/begin.gif [deleted file]
org.argeo.cms.ui.theme/icons/binary.png [deleted file]
org.argeo.cms.ui.theme/icons/browser.gif [deleted file]
org.argeo.cms.ui.theme/icons/bundles.gif [deleted file]
org.argeo.cms.ui.theme/icons/changePassword.gif [deleted file]
org.argeo.cms.ui.theme/icons/clear.gif [deleted file]
org.argeo.cms.ui.theme/icons/close-all.png [deleted file]
org.argeo.cms.ui.theme/icons/commit.gif [deleted file]
org.argeo.cms.ui.theme/icons/delete.png [deleted file]
org.argeo.cms.ui.theme/icons/dumpNode.gif [deleted file]
org.argeo.cms.ui.theme/icons/file.gif [deleted file]
org.argeo.cms.ui.theme/icons/folder.gif [deleted file]
org.argeo.cms.ui.theme/icons/getSize.gif [deleted file]
org.argeo.cms.ui.theme/icons/group.png [deleted file]
org.argeo.cms.ui.theme/icons/home.gif [deleted file]
org.argeo.cms.ui.theme/icons/home.png [deleted file]
org.argeo.cms.ui.theme/icons/import_fs.png [deleted file]
org.argeo.cms.ui.theme/icons/installed.gif [deleted file]
org.argeo.cms.ui.theme/icons/log.gif [deleted file]
org.argeo.cms.ui.theme/icons/logout.png [deleted file]
org.argeo.cms.ui.theme/icons/maintenance.gif [deleted file]
org.argeo.cms.ui.theme/icons/node.gif [deleted file]
org.argeo.cms.ui.theme/icons/nodes.gif [deleted file]
org.argeo.cms.ui.theme/icons/osgi_explorer.gif [deleted file]
org.argeo.cms.ui.theme/icons/password.gif [deleted file]
org.argeo.cms.ui.theme/icons/person-logged-in.png [deleted file]
org.argeo.cms.ui.theme/icons/person.png [deleted file]
org.argeo.cms.ui.theme/icons/query.png [deleted file]
org.argeo.cms.ui.theme/icons/refresh.png [deleted file]
org.argeo.cms.ui.theme/icons/remote_connected.gif [deleted file]
org.argeo.cms.ui.theme/icons/remote_disconnected.gif [deleted file]
org.argeo.cms.ui.theme/icons/remove.gif [deleted file]
org.argeo.cms.ui.theme/icons/removePrivileges.gif [deleted file]
org.argeo.cms.ui.theme/icons/rename.gif [deleted file]
org.argeo.cms.ui.theme/icons/repositories.gif [deleted file]
org.argeo.cms.ui.theme/icons/repository_connected.gif [deleted file]
org.argeo.cms.ui.theme/icons/repository_disconnected.gif [deleted file]
org.argeo.cms.ui.theme/icons/resolved.gif [deleted file]
org.argeo.cms.ui.theme/icons/role.gif [deleted file]
org.argeo.cms.ui.theme/icons/rollback.gif [deleted file]
org.argeo.cms.ui.theme/icons/save-all.png [deleted file]
org.argeo.cms.ui.theme/icons/save.gif [deleted file]
org.argeo.cms.ui.theme/icons/save.png [deleted file]
org.argeo.cms.ui.theme/icons/save_security.png [deleted file]
org.argeo.cms.ui.theme/icons/save_security_disabled.png [deleted file]
org.argeo.cms.ui.theme/icons/security.gif [deleted file]
org.argeo.cms.ui.theme/icons/service_published.gif [deleted file]
org.argeo.cms.ui.theme/icons/service_referenced.gif [deleted file]
org.argeo.cms.ui.theme/icons/sort.gif [deleted file]
org.argeo.cms.ui.theme/icons/starting.gif [deleted file]
org.argeo.cms.ui.theme/icons/sync.gif [deleted file]
org.argeo.cms.ui.theme/icons/user.gif [deleted file]
org.argeo.cms.ui.theme/icons/users.gif [deleted file]
org.argeo.cms.ui.theme/icons/workgroup.png [deleted file]
org.argeo.cms.ui.theme/icons/workgroup.xcf [deleted file]
org.argeo.cms.ui.theme/icons/workspace_connected.png [deleted file]
org.argeo.cms.ui.theme/icons/workspace_disconnected.png [deleted file]
org.argeo.cms.ui.theme/pom.xml [deleted file]
org.argeo.cms.ui.theme/rap/argeo-studio.css [deleted file]
org.argeo.cms.ui.theme/src/org/argeo/cms/ui/theme/CmsImages.java [deleted file]
org.argeo.cms.ui.theme/src/org/argeo/cms/ui/theme/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/AbstractCmsApp.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsConstants.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditable.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsImageManager.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.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/CmsView.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/MvcProvider.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/UxContext.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/SingleValueDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/package-info.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/internal/rwt/UserUi.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/useradmin/PickUpUserDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UsersImages.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsEvent.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.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/CmsStyle.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/LoginEntryPoint.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/SimpleUxContext.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/auth/AbstractLoginDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLoginShell.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CompositeCallbackHandler.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/DefaultLoginDialog.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/DynamicCallbackHandler.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/LocaleChoice.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/package-info.java [deleted file]
org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java [deleted file]
org.argeo.cms/.classpath
org.argeo.cms/.settings/org.eclipse.jdt.core.prefs [deleted file]
org.argeo.cms/OSGI-INF/cmsContext.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsDeployment.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsState.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/cmsUserManager.xml
org.argeo.cms/OSGI-INF/dataServletContext.xml [deleted file]
org.argeo.cms/OSGI-INF/deployConfig.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/filesServlet.xml [deleted file]
org.argeo.cms/OSGI-INF/filesServletContext.xml [deleted file]
org.argeo.cms/OSGI-INF/jcrServletContext.xml [deleted file]
org.argeo.cms/OSGI-INF/nodeDeployment.xml [deleted file]
org.argeo.cms/OSGI-INF/nodeInstance.xml [deleted file]
org.argeo.cms/OSGI-INF/nodeState.xml [deleted file]
org.argeo.cms/OSGI-INF/nodeUserAdmin.xml [new file with mode: 0644]
org.argeo.cms/OSGI-INF/pkgServlet.xml [deleted file]
org.argeo.cms/OSGI-INF/pkgServletContext.xml [deleted file]
org.argeo.cms/OSGI-INF/simpleTransactionManager.xml [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/build.properties
org.argeo.cms/ext/test/org/argeo/cms/security/PasswordBasedEncryptionTest.java [deleted file]
org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java [deleted file]
org.argeo.cms/ext/test/org/argeo/cms/tabular/JcrTabularTest.java [deleted file]
org.argeo.cms/pom.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CmsUserManager.java
org.argeo.cms/src/org/argeo/cms/LocaleUtils.java
org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/argeo.cnd [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/AnonymousLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java
org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/IdentLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/KeyringLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallback.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallbackHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthSession.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/SingleUserLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminUtils.java
org.argeo.cms/src/org/argeo/cms/auth/ident/IdentClient.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/ident/OpenSslDecryptor.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/ident/package-info.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java [deleted file]
org.argeo.cms/src/org/argeo/cms/cli/package-info.java [deleted file]
org.argeo.cms/src/org/argeo/cms/dn.cnd [deleted file]
org.argeo.cms/src/org/argeo/cms/fs/CmsFsUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java
org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/LinkServlet.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/WebCmsSessionImpl.java
org.argeo.cms/src/org/argeo/cms/internal/http/client/SpnegoAuthScheme.java
org.argeo.cms/src/org/argeo/cms/internal/http/protectedHandlers.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/http/webdav-config.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/JackrabbitType.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/RepoConf.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-h2.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-localfs.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-memory.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsInstance.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsPaths.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspace.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/EgoRepository.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeKeyRing.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepositoryFactory.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/DataModelNamespace.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/osgi/PublishNamespace.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/security/AbstractKeyring.java
org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/security/JcrKeyring.java [deleted file]
org.argeo.cms/src/org/argeo/cms/security/Keyring.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java [deleted file]
org.argeo.cms/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java [deleted file]
org.argeo.cms/src/org/argeo/cms/servlet/ServletAuthUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/CsvTabularWriter.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/JcrTabularRowIterator.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/JcrTabularWriter.java [deleted file]
org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/tabular/package-info.java
org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java [deleted file]
org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java [deleted file]
org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java [deleted file]
org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java [deleted file]
org.argeo.cms/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java [deleted file]
org.argeo.cms/src/org/argeo/security/jackrabbit/package-info.java [deleted file]
org.argeo.core/.classpath [deleted file]
org.argeo.core/.project [deleted file]
org.argeo.core/.settings/org.eclipse.jdt.core.prefs [deleted file]
org.argeo.core/META-INF/.gitignore [deleted file]
org.argeo.core/bnd.bnd [deleted file]
org.argeo.core/build.properties [deleted file]
org.argeo.core/ext/test/log4j.properties [deleted file]
org.argeo.core/ext/test/org/argeo/fs/FsUtilsTest.java [deleted file]
org.argeo.core/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java [deleted file]
org.argeo.core/pom.xml [deleted file]
org.argeo.core/src/org/argeo/cli/CommandArgsException.java [deleted file]
org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java [deleted file]
org.argeo.core/src/org/argeo/cli/CommandsCli.java [deleted file]
org.argeo.core/src/org/argeo/cli/DescribedCommand.java [deleted file]
org.argeo.core/src/org/argeo/cli/HelpCommand.java [deleted file]
org.argeo.core/src/org/argeo/cli/fs/FileSync.java [deleted file]
org.argeo.core/src/org/argeo/cli/fs/FsCommands.java [deleted file]
org.argeo.core/src/org/argeo/cli/fs/PathSync.java [deleted file]
org.argeo.core/src/org/argeo/cli/fs/SyncFileVisitor.java [deleted file]
org.argeo.core/src/org/argeo/cli/fs/package-info.java [deleted file]
org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java [deleted file]
org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java [deleted file]
org.argeo.core/src/org/argeo/cli/jcr/package-info.java [deleted file]
org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml [deleted file]
org.argeo.core/src/org/argeo/cli/package-info.java [deleted file]
org.argeo.core/src/org/argeo/cli/posix/Echo.java [deleted file]
org.argeo.core/src/org/argeo/cli/posix/PosixCommands.java [deleted file]
org.argeo.core/src/org/argeo/cli/posix/package-info.java [deleted file]
org.argeo.core/src/org/argeo/fs/BasicSyncFileVisitor.java [deleted file]
org.argeo.core/src/org/argeo/fs/FsUtils.java [deleted file]
org.argeo.core/src/org/argeo/fs/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/client/JackrabbitClient.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/fs/DavexFsProvider.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/fs/fs-memory.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/fs/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/repository-h2.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/repository-localfs.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/repository-memory.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/repository-postgresql-ds.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/repository-postgresql.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/security/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/unit/jaas.config [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/unit/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/unit/repository-h2.xml [deleted file]
org.argeo.core/src/org/argeo/jackrabbit/unit/repository-memory.xml [deleted file]
org.argeo.core/src/org/argeo/jcr/proxy/AbstractUrlProxy.java [deleted file]
org.argeo.core/src/org/argeo/jcr/proxy/ResourceProxy.java [deleted file]
org.argeo.core/src/org/argeo/jcr/proxy/ResourceProxyServlet.java [deleted file]
org.argeo.core/src/org/argeo/jcr/proxy/package-info.java [deleted file]
org.argeo.core/src/org/argeo/jcr/unit/AbstractJcrTestCase.java [deleted file]
org.argeo.core/src/org/argeo/jcr/unit/package-info.java [deleted file]
org.argeo.core/src/org/argeo/sync/SyncException.java [deleted file]
org.argeo.core/src/org/argeo/sync/SyncResult.java [deleted file]
org.argeo.core/src/org/argeo/sync/package-info.java [deleted file]
org.argeo.eclipse.ui.rap/.classpath [deleted file]
org.argeo.eclipse.ui.rap/.project [deleted file]
org.argeo.eclipse.ui.rap/META-INF/.gitignore [deleted file]
org.argeo.eclipse.ui.rap/bnd.bnd [deleted file]
org.argeo.eclipse.ui.rap/build.properties [deleted file]
org.argeo.eclipse.ui.rap/pom.xml [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFile.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFileService.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/SingleSourcingException.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/UiContext.java [deleted file]
org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/package-info.java [deleted file]
org.argeo.eclipse.ui/.classpath [deleted file]
org.argeo.eclipse.ui/.project [deleted file]
org.argeo.eclipse.ui/META-INF/.gitignore [deleted file]
org.argeo.eclipse.ui/bnd.bnd [deleted file]
org.argeo.eclipse.ui/build.properties [deleted file]
org.argeo.eclipse.ui/pom.xml [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/ColumnDefinition.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/ColumnViewerComparator.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/EclipseJcrMonitor.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/EclipseUiException.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/EclipseUiUtils.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/FileProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/GenericTableComparator.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/IListProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDown.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/Selected.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/TreeParent.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/SingleValue.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsTableViewer.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiConstants.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiException.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiUtils.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/ParentDir.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/file.png [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/folder.png [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/parts/package-info.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/util/SingleSourcingConstants.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/util/ViewerUtils.java [deleted file]
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/util/package-info.java [deleted file]
org.argeo.enterprise/.classpath [deleted file]
org.argeo.enterprise/.project [deleted file]
org.argeo.enterprise/META-INF/.gitignore [deleted file]
org.argeo.enterprise/bnd.bnd [deleted file]
org.argeo.enterprise/build.properties [deleted file]
org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/BasicTestConstants.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifUserAdminTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/UserAdminConfTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/basic.ldif [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/CsvParserEncodingTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/CsvParserParseFileTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/CsvParserTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/CsvParserWithQuotedSeparatorTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/CsvWriterTest.java [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/ReferenceFile.csv [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/TestParse-ISO.csv [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/TestParse-UTF-8.csv [deleted file]
org.argeo.enterprise/ext/test/org/argeo/util/ThroughputTest.java [deleted file]
org.argeo.enterprise/pom.xml [deleted file]
org.argeo.enterprise/src/org/argeo/ident/IdentClient.java [deleted file]
org.argeo.enterprise/src/org/argeo/ident/OpenSslDecryptor.java [deleted file]
org.argeo.enterprise/src/org/argeo/ident/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/AttributesDictionary.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/AuthPassword.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/Distinguished.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.csv [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdapObjs.csv [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdapObjs.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdifParser.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/LdifWriter.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/SharedSecret.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/SpecifiedName.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java [deleted file]
org.argeo.enterprise/src/org/argeo/naming/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/internal/EnterpriseActivator.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumAD.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumOCD.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/metatype/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/provisioning/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryGroup.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryUser.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifAuthorization.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifGroup.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/TokenUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryException.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/jaas-os.cfg [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java [deleted file]
org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransaction.java [deleted file]
org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransactionManager.java [deleted file]
org.argeo.enterprise/src/org/argeo/transaction/simple/UuidXid.java [deleted file]
org.argeo.enterprise/src/org/argeo/transaction/simple/package-info.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/CsvParser.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/CsvParserWithLinesAsMap.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/CsvWriter.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/DictionaryKeys.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/DigestUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/DirH.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/LangUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/OS.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/PasswordEncryption.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/ServiceChannel.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/StreamUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/Tester.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/TesterStatus.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/Throughput.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/UuidUtils.java [deleted file]
org.argeo.enterprise/src/org/argeo/util/package-info.java [deleted file]
org.argeo.init/.classpath [new file with mode: 0644]
org.argeo.init/.project [new file with mode: 0644]
org.argeo.init/META-INF/.gitignore [new file with mode: 0644]
org.argeo.init/bnd.bnd [new file with mode: 0644]
org.argeo.init/build.properties [new file with mode: 0644]
org.argeo.init/src/META-INF/services/java.lang.System$LoggerFinder [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/RuntimeContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/Service.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Branch.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Component.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Contribution.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Exception.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Module.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/A2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/FsA2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/FsM2Source.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/OsgiContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/a2/package-info.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/logging/ThinLogging.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/Activator.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/AdminThread.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/DistributionBundle.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/Launcher.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/Main.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiBootDiagnostics.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/log4j.properties [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/node.policy [new file with mode: 0644]
org.argeo.init/src/org/argeo/init/osgi/package-info.java [new file with mode: 0644]
org.argeo.jcr/.classpath [deleted file]
org.argeo.jcr/.project [deleted file]
org.argeo.jcr/META-INF/.gitignore [deleted file]
org.argeo.jcr/bnd.bnd [deleted file]
org.argeo.jcr/build.properties [deleted file]
org.argeo.jcr/pom.xml [deleted file]
org.argeo.jcr/src/org/argeo/jcr/Bin.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/CollectionNodeIterator.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/DefaultJcrListener.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/Jcr.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrAuthorizations.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrCallback.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrException.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrMonitor.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrxName.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/JcrxType.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/PropertyDiff.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/SimplePrincipal.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/VersionDiff.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/BinaryChannel.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrFsException.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/Text.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/fs/package-info.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/jcrx.cnd [deleted file]
org.argeo.jcr/src/org/argeo/jcr/package-info.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl [deleted file]
org.argeo.maintenance/.classpath [deleted file]
org.argeo.maintenance/.project [deleted file]
org.argeo.maintenance/.settings/org.eclipse.jdt.core.prefs [deleted file]
org.argeo.maintenance/META-INF/.gitignore [deleted file]
org.argeo.maintenance/bnd.bnd [deleted file]
org.argeo.maintenance/build.properties [deleted file]
org.argeo.maintenance/pom.xml [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/SimpleRoleRegistration.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/backup/package-info.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/package-info.java [deleted file]
org.argeo.osgi.boot/.classpath [deleted file]
org.argeo.osgi.boot/.project [deleted file]
org.argeo.osgi.boot/META-INF/.gitignore [deleted file]
org.argeo.osgi.boot/bnd.bnd [deleted file]
org.argeo.osgi.boot/build.properties [deleted file]
org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java [deleted file]
org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootNoRuntimeTest.java [deleted file]
org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootRuntimeTest.java [deleted file]
org.argeo.osgi.boot/pom.xml [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/a2/package-info.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/Activator.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/AdminThread.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/BundlesSet.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/DistributionBundle.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/Launcher.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/Main.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/NodeRunner.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootConstants.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootDiagnostics.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBuilder.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/EquinoxUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/package-info.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/AntPathMatcher.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/CollectionUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/ObjectUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/PathMatcher.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/StringUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/SystemPropertyUtils.java [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/log4j.properties [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/node.policy [deleted file]
org.argeo.osgi.boot/src/org/argeo/osgi/boot/package-info.java [deleted file]
org.argeo.util/.classpath [new file with mode: 0644]
org.argeo.util/.project [new file with mode: 0644]
org.argeo.util/META-INF/.gitignore [new file with mode: 0644]
org.argeo.util/bnd.bnd [new file with mode: 0644]
org.argeo.util/build.properties [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/metatype/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/transaction/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/CsvParser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/CsvWriter.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/DictionaryKeys.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/DigestUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/DirH.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/FsUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/LangUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/OS.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/PasswordEncryption.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/ServiceChannel.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/StreamUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/Tester.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/TesterStatus.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/Throughput.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/AuthPassword.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/Distinguished.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdapObjs.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdifParser.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/LdifWriter.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/NamingUtils.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/NodeOID.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/SharedSecret.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/SrvRecord.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/naming/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/package-info.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/Component.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/Register.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/Singleton.java [new file with mode: 0644]
org.argeo.util/src/org/argeo/util/register/StaticRegister.java [new file with mode: 0644]
pom.xml [deleted file]
rap/org.argeo.cms.e4.rap/.classpath [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/.project [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/META-INF/.gitignore [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/bnd.bnd [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/build.properties [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java [new file with mode: 0644]
rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/.classpath [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/.project [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/META-INF/.gitignore [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/bnd.bnd [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/build.properties [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java [new file with mode: 0644]
rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/.classpath [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/.project [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/META-INF/.gitignore [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/bnd.bnd [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/build.properties [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java [new file with mode: 0644]
rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/.classpath [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/.gitignore [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/.project [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/argeo-companion.properties [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/bnd.bnd [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/build.properties [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/log4j.properties [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/plugin.xml [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java [new file with mode: 0644]
rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/.classpath [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/.gitignore [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/.project [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/bnd.bnd [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/build.properties [new file with mode: 0644]
rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/.classpath [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/.gitignore [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/.project [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/META-INF/.gitignore [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/bnd.bnd [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/build.properties [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/.classpath [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/.gitignore [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/.project [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/bnd.bnd [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/build.properties [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java [new file with mode: 0644]
rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java [new file with mode: 0644]
sdk/.gitignore [new file with mode: 0644]
sdk/all.policy [new file with mode: 0644]
sdk/argeo-build [new submodule]
sdk/argeo-init.properties [new file with mode: 0644]
sdk/build.sh [deleted file]
sdk/cms-cluster_0.properties [new file with mode: 0644]
sdk/cms-cluster_1.properties [new file with mode: 0644]
sdk/cms-e4-rap.properties [new file with mode: 0644]
sdk/cms-local.properties [new file with mode: 0644]
sdk/cms-pgsql-ldap.properties [new file with mode: 0644]
sdk/configure.sh [deleted file]
sdk/deploy/argeo-init/etc/argeo.d/jvm.args [new file with mode: 0644]
sdk/deploy/argeo-init/etc/argeo.d/jvm.args.debug [new file with mode: 0644]
sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/SETUP.txt [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/all.policy [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/argeo-pgsql-setup.sql [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/argeo-slapd-setup.inf [new file with mode: 0644]
sdk/deploy/argeo-init/usr/share/argeo/jvm.args [new file with mode: 0644]
sdk/ecj.args [deleted file]
sdk/init/node/.gitignore [new file with mode: 0644]
sdk/init/node/ou=roles,ou=node.ldif [new file with mode: 0644]
sdk/pom.xml [deleted file]
sdk/ssl/.gitignore [new file with mode: 0644]
sdk/ssl/openssl.cnf [new file with mode: 0644]
sdk/ssl/openssl_root.cnf [new file with mode: 0644]
sdk/ssl/ssl.sh [new file with mode: 0644]

index 492e809ea5293775af439187d46c91da46ee80da..c306476f0ea5396b0638b3f42c036df17ba18d31 100644 (file)
@@ -2,3 +2,6 @@
 **/target/
 **/generated/
 **/MANIFEST.MF
+/build/
+/sdk.mk
+/output/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..d728334
--- /dev/null
@@ -0,0 +1,4 @@
+[submodule "sdk/argeo-build"]
+       path = sdk/argeo-build
+       url = http://git.argeo.org/cc0/argeo-build.git
+       branch = testing
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..daea875
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,58 @@
+include sdk.mk
+.PHONY: clean all osgi jni
+
+all: osgi jni
+       mkdir -p $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
+       mv -v $(A2_OUTPUT)/$(A2_CATEGORY)/*.rap.$(MAJOR).$(MINOR).jar $(A2_OUTPUT)/$(A2_CATEGORY).eclipse.rap
+       touch $(BUILD_BASE)/*.rap/bnd.bnd
+
+A2_CATEGORY = org.argeo.cms
+
+BUNDLES = \
+org.argeo.init \
+org.argeo.util \
+org.argeo.api.uuid \
+org.argeo.api.acr \
+org.argeo.api.cms \
+org.argeo.cms \
+org.argeo.cms.pgsql \
+eclipse/org.argeo.cms.servlet \
+eclipse/org.argeo.cms.swt \
+eclipse/org.argeo.cms.e4 \
+rap/org.argeo.cms.ui.rap \
+rap/org.argeo.swt.specific.rap \
+rap/org.argeo.cms.e4.rap \
+jcr/org.argeo.cms.jcr \
+jcr/org.argeo.cms.ui \
+
+JAVADOC_BUNDLES =  \
+org.argeo.api.uuid \
+org.argeo.api.acr \
+org.argeo.api.cms
+
+JAVADOC_PACKAGES =  \
+org.argeo.api.uuid \
+org.argeo.api.acr \
+org.argeo.api.cms
+
+A2_OUTPUT = $(SDK_BUILD_BASE)/a2
+A2_BASE = $(A2_OUTPUT)
+
+VPATH = .:eclipse:rap:jcr
+
+DEP_CATEGORIES = \
+org.argeo.tp \
+org.argeo.tp.apache \
+org.argeo.tp.jetty \
+org.argeo.tp.eclipse.equinox \
+org.argeo.tp.eclipse.rap \
+org.argeo.tp.jcr
+
+jni:
+       $(MAKE) -C jni
+
+clean:
+       rm -rf $(BUILD_BASE)
+       $(MAKE) -C jni clean
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
diff --git a/Makefile-rcp.mk b/Makefile-rcp.mk
new file mode 100644 (file)
index 0000000..8811d24
--- /dev/null
@@ -0,0 +1,32 @@
+include sdk.mk
+.PHONY: clean all osgi
+
+all: osgi
+
+A2_CATEGORY = org.argeo.cms.eclipse.rcp
+
+BUNDLES = \
+rcp/org.argeo.cms.e4.rcp \
+rcp/org.argeo.cms.ui.rcp \
+rcp/org.argeo.swt.minidesktop \
+rcp/org.argeo.swt.specific.rcp \
+
+A2_OUTPUT = $(SDK_BUILD_BASE)/a2
+A2_BASE = $(A2_OUTPUT)
+
+DEP_CATEGORIES = \
+org.argeo.cms \
+org.argeo.tp \
+org.argeo.tp.apache \
+org.argeo.tp.jetty \
+org.argeo.tp.eclipse.equinox \
+org.argeo.tp.eclipse.rcp \
+org.argeo.tp.jcr
+
+
+clean:
+       rm -rf $(BUILD_BASE)
+
+VPATH = .:rcp
+
+include  $(SDK_SRC_BASE)/sdk/argeo-build/osgi.mk
\ No newline at end of file
diff --git a/branch.mk b/branch.mk
new file mode 100644 (file)
index 0000000..936a675
--- /dev/null
+++ b/branch.mk
@@ -0,0 +1 @@
+include $(SDK_SRC_BASE)/cnf/testing.bnd
diff --git a/cnf/argeo.bnd b/cnf/argeo.bnd
deleted file mode 100644 (file)
index d1c001a..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-# Common
-Bundle-Version: ${version.released}${qualifier}
-Private-Package: *.internal.*
-Export-Package: !*.internal.*, *
--consumer-policy : ${range;[==,=+)}
--savemanifest : META-INF/MANIFEST.MF
--includeresource.default : OSGI-INF/=-OSGI-INF/,e4xmi/=-e4xmi/,icons/=-icons/,img/=-img/
--compression STORE
--source true
--removeheaders = Bnd-LastModified,Build-Jdk,Built-By,Tool,Created-By
-Automatic-Module-Name: ${bsn}
-SLC-Category=${category}
--groupid=${category}
index c59a50e14ae07b3336ef542075e1c5ca0149efee..a464edc1fa01eafdc3a058a9630ed2a1de22b625 100644 (file)
@@ -1,3 +1,3 @@
 -include: \
-${workspace}/cnf/argeo.bnd, \
-${workspace}/cnf/testing.bnd
+${workspace}/cnf/testing.bnd, \
+${workspace}/sdk/argeo-build/argeo.bnd, \
diff --git a/cnf/maven.bnd b/cnf/maven.bnd
deleted file mode 100644 (file)
index 1b962bc..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
--include: \
-../cnf/argeo.bnd, \
-../cnf/testing.bnd
index ed5ee7abe79b2688db10b48f20b717e2846f2547..25289aaa023e94ca6d663f2767726403c0310656 100644 (file)
@@ -1,4 +1,6 @@
-version.released=2.1.104
+MAJOR=2
+MINOR=1
+MICRO=104
 qualifier=.next
 
 category=org.argeo.commons
index 5d4e39fc3fd0cfaf3fc1236295d2b5ff206f71ae..033f17b990a67a4adecf3eb28f2aac0af1418260 100644 (file)
@@ -1,4 +1,6 @@
-version.released=2.3.4
+MAJOR=2
+MINOR=3
+MICRO=5
 qualifier=.next
 
 category=org.argeo.commons
diff --git a/configure b/configure
new file mode 100644 (file)
index 0000000..9b3e980
--- /dev/null
+++ b/configure
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# We build where we are
+SDK_BUILD_BASE=$(pwd -P)/output
+
+# Source are located where this script is
+SDK_SRC_BASE="$(cd "$(dirname "$0")"; pwd -P)"
+
+SDK_MK=$SDK_SRC_BASE/sdk.mk
+
+#echo SDK_BUILD_BASE=$SDK_BUILD_BASE
+#echo SDK_SRC_BASE=$SDK_SRC_BASE
+#echo SDK_MK=$SDK_MK
+
+if [ -f "$SDK_MK" ]; 
+then
+
+echo "File $SDK_MK already exists. Remove it in order to configure a new build location:"
+echo "rm $SDK_MK"
+exit 1
+
+else
+
+if [ -z "$JAVA_HOME" ]
+then
+echo "Environment variable JAVA_HOME must be set"
+exit 1
+fi
+
+# Create build directory, so that it can be used right away
+# and we check whether we have the rights
+mkdir -p $SDK_BUILD_BASE
+if [ -f "$SDK_MK" ];
+then
+echo "Cannot create $SDK_BUILD_BASE, SDK configuration has failed."
+exit 2
+fi
+
+# Generate sdk.mk
+cat > "$SDK_MK" <<EOF
+SDK_SRC_BASE := $SDK_SRC_BASE
+SDK_BUILD_BASE := $SDK_BUILD_BASE
+JAVA_HOME := $JAVA_HOME
+
+include \$(SDK_SRC_BASE)/branch.mk
+EOF
+
+
+echo SDK was configured.
+echo "JAVA_HOME        : $JAVA_HOME"
+echo "Base for sources : $SDK_SRC_BASE"
+echo "Base for builds  : $SDK_BUILD_BASE"
+exit 0
+fi
+
diff --git a/demo/.gitignore b/demo/.gitignore
deleted file mode 100644 (file)
index 45dfa56..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/exec/
diff --git a/demo/all.policy b/demo/all.policy
deleted file mode 100644 (file)
index facb613..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-};
\ No newline at end of file
diff --git a/demo/argeo_node_osgiboot.properties b/demo/argeo_node_osgiboot.properties
deleted file mode 100644 (file)
index 64ff5d5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-argeo.osgi.baseUrl=http://forge.argeo.org/data/java/argeo-2.1/
-argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.65/org.argeo.dep.cms.sdk-2.1.65.jar
-#argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.67/org.argeo.dep.cms.sdk-2.1.67.jar
-#argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.68-SNAPSHOT/org.argeo.dep.cms.sdk-2.1.68-SNAPSHOT.jar
-
-argeo.osgi.boot.debug=true
-
-argeo.osgi.start.1.osgiboot=org.argeo.osgi.boot
-argeo.osgi.start.2.node=org.eclipse.equinox.http.servlet,org.eclipse.equinox.http.jetty,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi
-argeo.osgi.start.3.node=org.argeo.cms,org.eclipse.gemini.blueprint.extender,org.eclipse.equinox.http.registry
-
-java.security.manager=
-java.security.policy=file:../../all.policy
-
-argeo.node.repo.type=localfs
-org.osgi.service.http.port=7070
-log4j.configuration=file:../../log4j.properties
-
-# DON'T CHANGE BELOW
-org.eclipse.rap.workbenchAutostart=false
-org.eclipse.equinox.http.jetty.autostart=false
\ No newline at end of file
diff --git a/demo/cms-cluster_0.properties b/demo/cms-cluster_0.properties
deleted file mode 100644 (file)
index d0c3fb2..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
-
-# Local
-org.osgi.service.http.port=7070
-argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
-argeo.node.repo.type=postgresql_cluster_ds
-argeo.node.repo.clusterId=03233754-16c3-49a1-8a00-58bf89a65182
-argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
-argeo.node.repo.dbuser=argeo
-argeo.node.repo.dbpassword=argeo
-
-# Logging
-log4j.configuration=file:../../log4j.properties
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/demo/cms-cluster_1.properties b/demo/cms-cluster_1.properties
deleted file mode 100644 (file)
index b5e60f8..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
-
-# Local
-org.osgi.service.http.port=7071
-argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
-argeo.node.repo.type=postgresql_cluster_ds
-argeo.node.repo.clusterId=52463fa3-2917-4814-9ff7-685c41cbc7c7
-argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
-argeo.node.repo.dbuser=argeo
-argeo.node.repo.dbpassword=argeo
-
-# Logging
-log4j.configuration=file:../../log4j.properties
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/demo/cms-e4-rap.properties b/demo/cms-e4-rap.properties
deleted file mode 100644 (file)
index 1497d7c..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
-
-# Local
-argeo.node.repo.type=h2
-org.osgi.service.http.port=7070
-#org.eclipse.equinox.http.jetty.http.host=[IP address to listen to]
-#org.osgi.service.http.port.secure=7073
-#org.eclipse.equinox.http.jetty.websocket.enabled=true
-
-# Logging
-log4j.configuration=file:../../log4j.properties
-
-# SSL
-#org.osgi.service.http.port.secure=7073
-#org.eclipse.equinox.http.jetty.https.enabled=true
-#org.eclipse.equinox.http.jetty.ssl.keystore=data/node.p12
-#org.eclipse.equinox.http.jetty.ssl.keystoretype=PKCS12
-#org.eclipse.equinox.http.jetty.ssl.password=changeit
-#org.eclipse.equinox.http.jetty.ssl.wantclientauth=true
-
-# Hardened
-#org.osgi.framework.security=osgi
-#java.security.policy=file:../../all.policy
-
-# Internationalisation
-#argeo.i18n.locales=en,fr,ru
-#eclipse.registry.MultiLanguage=true
-#argeo.i18n.defaultLocale=en
-
-# Tuning
-# Number of DB connections
-#argeo.node.repo.maxPoolSize=10
-# Max amount of memory available to Jackrabbit caches
-#argeo.node.repo.maxCacheMB=16
-# Persistence level cache
-#argeo.node.repo.bundleCacheMB=8
-# Search, see http://wiki.apache.org/jackrabbit/Search
-#argeo.node.repo.extractorPoolSize=0
-#argeo.node.repo.searchCacheSize=1000
-#argeo.node.repo.maxVolatileIndexSize=1048576
-
-# Legacy
-#argeo.node.transaction.manager=bitronix
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/demo/cms-local.properties b/demo/cms-local.properties
deleted file mode 100644 (file)
index e8ae494..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
-
-# Local
-argeo.node.repo.type=h2
-org.osgi.service.http.port=7070
-argeo.node.useradmin.uris=os:///
-
-# Logging
-log4j.configuration=file:../../log4j.properties
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/demo/cms-pgsql-ldap.properties b/demo/cms-pgsql-ldap.properties
deleted file mode 100644 (file)
index 3f9aaff..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-argeo.osgi.start.5.node=\
-org.argeo.cms.e4.rap
-
-# Local
-org.osgi.service.http.port=7070
-argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
-argeo.node.repo.type=postgresql_ds
-argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo
-argeo.node.repo.dbuser=argeo
-argeo.node.repo.dbpassword=argeo
-
-# Logging
-log4j.configuration=file:../../log4j.properties
-
-# DON'T CHANGE BELOW
-org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
diff --git a/demo/init/node/.gitignore b/demo/init/node/.gitignore
deleted file mode 100644 (file)
index f619744..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-/krb5.keytab
-/krb5.keytab.old
-/*.p12
-/*.jks
\ No newline at end of file
diff --git a/demo/init/node/ou=roles,ou=node.ldif b/demo/init/node/ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index ffa9073..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-dn: cn=admin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=People,dc=example,dc=com
-
-dn: cn=userAdmin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-member: cn=admin,ou=roles,ou=node
-cn: userAdmin
-
diff --git a/demo/log4j.properties b/demo/log4j.properties
deleted file mode 100644 (file)
index bf3f291..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-log4j.rootLogger=WARN, development
-
-log4j.logger.org.argeo=DEBUG
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m %n
-
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
diff --git a/demo/pom.xml b/demo/pom.xml
deleted file mode 100644 (file)
index 22d23f8..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>demo</artifactId>
-       <name>Commons Demo</name>
-       <packaging>pom</packaging>
-       <build>
-       </build>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.osgi</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.osgi.boot</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-       <profiles>
-               <profile>
-                       <id>argeo_node_rap</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.argeo.maven.plugins</groupId>
-                                               <artifactId>argeo-osgi-plugin</artifactId>
-                                               <configuration>
-                                                       <systemPropertiesFile>argeo_node_rap.properties</systemPropertiesFile>
-                                                       <execDir>exec/argeo_node_rap</execDir>
-                                               </configuration>
-                                       </plugin>
-                               </plugins>
-                       </build>
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                       </dependencies>
-               </profile>
-               <profile>
-                       <id>cms-e4-rap</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.argeo.maven.plugins</groupId>
-                                               <artifactId>argeo-osgi-plugin</artifactId>
-                                               <configuration>
-                                                       <systemPropertiesFile>cms-e4-rap.properties</systemPropertiesFile>
-                                                       <execDir>exec/cms-e4-rap</execDir>
-                                               </configuration>
-                                       </plugin>
-                               </plugins>
-                       </build>
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                       </dependencies>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/demo/ssl/.gitignore b/demo/ssl/.gitignore
deleted file mode 100644 (file)
index bc77402..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/CA/
-/*.p12
-/*.jks
-/nssdb/
-/*.pem
-/old/
-/rootCA/
diff --git a/demo/ssl/openssl.cnf b/demo/ssl/openssl.cnf
deleted file mode 100644 (file)
index 05bb6f7..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-dir            = ./CA          # Where everything is kept
-
-[ ca ]
-default_ca     = CA_default            # The default ca section
-
-[ CA_default ]
-certs          = $dir/certs            # Where the issued certs are kept
-crl_dir                = $dir/crl              # Where the issued crl are kept
-database       = $dir/index.txt        # database index file.
-new_certs_dir  = $dir/newcerts         # default place for new certs.
-certificate    = $dir/cacert.pem       # The CA certificate
-serial         = $dir/serial           # The current serial number
-crlnumber      = $dir/crlnumber        # the current crl number
-crl            = $dir/crl.pem          # The current CRL
-private_key    = $dir/private/cakey.pem # The private key
-x509_extensions        = usr_cert              # The extentions to add to the cert
-name_opt       = ca_default            # Subject Name options
-cert_opt       = ca_default            # Certificate field options
-crl_extensions = crl_ext
-default_days   = 365                   # how long to certify for
-default_crl_days= 30                   # how long before next CRL
-default_md     = default               # use public key default MD
-preserve       = no                    # keep passed DN ordering
-policy         = policy_match
-
-[ policy_match ]
-countryName            = optional
-stateOrProvinceName    = optional
-organizationName       = optional
-organizationalUnitName = optional
-commonName             = optional
-emailAddress           = optional
-
-[ policy_anything ]
-countryName            = optional
-stateOrProvinceName    = optional
-localityName           = optional
-organizationName       = optional
-organizationalUnitName = optional
-commonName             = optional
-emailAddress           = optional
-
-[ req ]
-default_bits           = 4096
-default_md             = sha1
-default_keyfile        = privkey.pem
-distinguished_name     = req_distinguished_name
-attributes             = req_attributes
-x509_extensions        = v3_ca # The extensions to add to the self signed cert
-
-# Passwords for private keys if not present they will be prompted for
-input_password = demo
-output_password = demo
-
-string_mask = utf8only
-req_extensions = v3_req # The extensions to add to a certificate request
-
-[ req_distinguished_name ]
-countryName                    = Country Name (2 letter code)
-countryName_min                        = 2
-countryName_max                        = 2
-#stateOrProvinceName           = State or Province Name (full name)
-#localityName                  = Locality Name (eg, city)
-0.organizationName             = Organization Name (eg, company)
-organizationalUnitName         = Organizational Unit Name (eg, section)
-commonName                     = Common Name (eg, your name or your server\'s hostname)
-commonName_max                 = 64
-emailAddress                   = Email Address
-emailAddress_max               = 64
-# SET-ex3                      = SET extension number 3
-
-##
-## DEFAULT VALUES
-##
-countryName_default            = DE
-#stateOrProvinceName_default   = Berlin
-#localityName_default  = Berlin
-0.organizationName_default     = Example
-organizationalUnitName_default = Certificate Authorities
-commonName_default     = Intermediate CA
-
-[ req_attributes ]
-#challengePassword             = A challenge password
-#challengePassword_min         = 4
-#challengePassword_max         = 20
-#unstructuredName              = An optional company name
-
-[ usr_cert ]
-basicConstraints=CA:FALSE
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-subjectAltName=email:move
-issuerAltName=issuer:copy
-
-[ v3_req ]
-basicConstraints = CA:FALSE
-keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-[ v3_ca ]
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid:always,issuer
-basicConstraints = critical, CA:true
-keyUsage = critical, digitalSignature, cRLSign, keyCertSign
-
-[ v3_intermediate_ca ]
-# Extensions for a typical intermediate CA (`man x509v3_config`).
-subjectKeyIdentifier = hash
-authorityKeyIdentifier = keyid:always,issuer
-basicConstraints = critical, CA:true, pathlen:0
-keyUsage = critical, digitalSignature, cRLSign, keyCertSign
-
-[ crl_ext ]
-issuerAltName=issuer:copy
-authorityKeyIdentifier=keyid:always
-
-[ server_ext ]
-extendedKeyUsage=serverAuth
-
-[ user_ext ]
-extendedKeyUsage=clientAuth,emailProtection
diff --git a/demo/ssl/openssl_root.cnf b/demo/ssl/openssl_root.cnf
deleted file mode 100644 (file)
index c689459..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-dir            = ./rootCA              # Where everything is kept
-
-[ ca ]
-default_ca     = CA_default            # The default ca section
-
-[ CA_default ]
-certs          = $dir/certs            # Where the issued certs are kept
-crl_dir                = $dir/crl              # Where the issued crl are kept
-database       = $dir/index.txt        # database index file.
-new_certs_dir  = $dir/newcerts         # default place for new certs.
-certificate    = $dir/cacert.pem       # The CA certificate
-serial         = $dir/serial           # The current serial number
-crlnumber      = $dir/crlnumber        # the current crl number
-crl            = $dir/crl.pem          # The current CRL
-private_key    = $dir/private/cakey.pem # The private key
-x509_extensions        = usr_cert              # The extentions to add to the cert
-name_opt       = ca_default            # Subject Name options
-cert_opt       = ca_default            # Certificate field options
-crl_extensions = crl_ext
-default_days   = 3650          # how long to certify for
-default_crl_days= 30                   # how long before next CRL
-default_md     = default               # use public key default MD
-preserve       = no                    # keep passed DN ordering
-policy         = policy_match
-
-[ policy_match ]
-countryName            = optional
-stateOrProvinceName    = optional
-organizationName       = optional
-organizationalUnitName = optional
-commonName             = optional
-emailAddress           = optional
-
-[ policy_anything ]
-countryName            = optional
-stateOrProvinceName    = optional
-localityName           = optional
-organizationName       = optional
-organizationalUnitName = optional
-commonName             = optional
-emailAddress           = optional
-
-[ req ]
-default_bits           = 4096
-default_md             = sha1
-default_keyfile        = privkey.pem
-distinguished_name     = req_distinguished_name
-attributes             = req_attributes
-x509_extensions        = v3_ca # The extensions to add to the self signed cert
-
-# Passwords for private keys if not present they will be prompted for
-input_password = demo
-output_password = demo
-
-string_mask = utf8only
-req_extensions = v3_req # The extensions to add to a certificate request
-
-[ req_distinguished_name ]
-countryName                    = Country Name (2 letter code)
-countryName_min                        = 2
-countryName_max                        = 2
-#stateOrProvinceName           = State or Province Name (full name)
-#localityName                  = Locality Name (eg, city)
-0.organizationName             = Organization Name (eg, company)
-organizationalUnitName         = Organizational Unit Name (eg, section)
-commonName                     = Common Name (eg, your name or your server\'s hostname)
-commonName_max                 = 64
-emailAddress                   = Email Address
-emailAddress_max               = 64
-# SET-ex3                      = SET extension number 3
-
-##
-## DEFAULT VALUES
-##
-countryName_default            = DE
-#stateOrProvinceName_default   = Berlin
-#localityName_default  = Berlin
-0.organizationName_default     = Example
-organizationalUnitName_default = Certificate Authorities
-commonName_default     = Root CA
-
-[ req_attributes ]
-#challengePassword             = A challenge password
-#challengePassword_min         = 4
-#challengePassword_max         = 20
-#unstructuredName              = An optional company name
-
-[ usr_cert ]
-basicConstraints=CA:FALSE
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid,issuer
-subjectAltName=email:move
-issuerAltName=issuer:copy
-
-[ v3_req ]
-basicConstraints = CA:FALSE
-keyUsage = nonRepudiation, digitalSignature, keyEncipherment
-
-[ v3_ca ]
-subjectKeyIdentifier=hash
-authorityKeyIdentifier=keyid:always,issuer
-basicConstraints = critical, CA:true
-keyUsage = critical, digitalSignature, cRLSign, keyCertSign
-
-[ v3_intermediate_ca ]
-# Extensions for a typical intermediate CA (`man x509v3_config`).
-subjectKeyIdentifier = hash
-authorityKeyIdentifier = keyid:always,issuer
-basicConstraints = critical, CA:true, pathlen:0
-keyUsage = critical, digitalSignature, cRLSign, keyCertSign
-
-[ crl_ext ]
-issuerAltName=issuer:copy
-authorityKeyIdentifier=keyid:always
-
-[ server_ext ]
-extendedKeyUsage=serverAuth
-
-[ user_ext ]
-extendedKeyUsage=clientAuth,emailProtection
diff --git a/demo/ssl/ssl.sh b/demo/ssl/ssl.sh
deleted file mode 100644 (file)
index 1caa4b3..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/bin/sh
-
-# COMPLETELY UNSAFE - FOR DEVELOPMENT ONLY
-# Run this script from its directory
-# all *.p12 passwords are 'demo'
-# all *.jks passwords are 'changeit'
-
-# Fail if any error
-set -e
-
-ROOT_CA_DN="/C=DE/O=Example/OU=Certificate Authorities/CN=Root CA/"
-INTERMEDIATE_CA_DN="/C=DE/O=Example/OU=Certificate Authorities/CN=Intermediate CA/"
-SERVER_DN=/C=DE/O=Example/OU=Systems/CN=$HOSTNAME/
-USERS_BASE_DN=/DC=com/DC=example/OU=People
-
-echo -- Init directory structures
-mkdir -p ./rootCA/{certs,crl,csr,newcerts,private}
-mkdir -p ./CA/{certs,crl,csr,newcerts,private}
-
-#
-# Root CA
-#
-export OPENSSL_CONF=./openssl_root.cnf
-export CATOP=./rootCA
-echo -- Create root CA in $CATOP
-touch $CATOP/index.txt
-openssl req -new -newkey rsa:4096 -extensions v3_ca \
- -subj "$ROOT_CA_DN" \
- -keyout $CATOP/private/cakey.pem -passout pass:demo -out ca_csr.pem \
- 2>/dev/null # quiet
-openssl ca -create_serial -selfsign -batch -passin pass:demo -in ca_csr.pem -out $CATOP/cacert.pem \
- 2>/dev/null # quiet
-
-echo -- Create intermediate CA in ./CA
-openssl req -new -newkey rsa:4096 -extensions v3_intermediate_ca \
- -subj "$INTERMEDIATE_CA_DN" \
- -keyout ./CA/private/cakey.pem -passout pass:demo -out ica_csr.pem \
- 2>/dev/null # quiet
-openssl ca -batch -passin pass:demo -in ica_csr.pem -out ./CA/cacert.pem \
- 2>/dev/null # quiet
-
-#
-# Intermediate CA
-#      
-export OPENSSL_CONF=./openssl.cnf
-export CATOP=./CA
-
-# create index and serial
-touch $CATOP/index.txt
-openssl x509 -in $CATOP/cacert.pem -noout -next_serial -out $CATOP/serial \
- 2>/dev/null # quiet
-
-echo -- Create server key and certificate
-openssl req -new -newkey rsa:4096 -extensions server_ext \
- -subj $SERVER_DN \
- -keyout node_key.pem -passout pass:demo -out node_csr.pem \
- 2>/dev/null # quiet
-openssl ca -batch -passin pass:demo -in node_csr.pem -out node_crt.pem \
- 2>/dev/null # quiet
-
-# create CA chain
-cat node_crt.pem ./CA/cacert.pem ./rootCA/cacert.pem > chain.pem
-
-# convert to p12
-openssl pkcs12 -export -passin pass:demo -passout pass:changeit \
- -name "$HOSTNAME" -inkey node_key.pem -in chain.pem \
- -out node.p12 \
- 2>/dev/null # quiet
-
-echo -- Import Certificate Authority into keystore
-keytool -importcert -noprompt -keystore node.p12 -storepass changeit \
- -alias "rootCA" -file ./rootCA/cacert.pem
-keytool -importcert -noprompt -keystore node.p12 -storepass changeit \
- -alias "CA" -file ./CA/cacert.pem
-
-echo -- Copy node.p12 to ../init/node
-cp node.p12 ../init/node/
-
-echo -- Create 'root' user client certificate root.p12
-openssl req -new -newkey rsa:4096 -extensions user_ext \
- -subj $USERS_BASE_DN/UID=root/ \
- -keyout newkey.pem -passout pass:demo -out newcsr.pem \
- 2>/dev/null # quiet
-
-openssl ca -preserveDN -batch -passin pass:demo -in newcsr.pem -out newcrt.pem \
- 2>/dev/null # quiet
-
-# create new CA chain
-#cat newcrt.pem ./CA/cacert.pem ./rootCA/cacert.pem > newchain.pem
-openssl pkcs12 -export -passin pass:demo -passout pass:demo \
- -name "root" -inkey newkey.pem -in chain.pem \
- -out root.p12 \
- 2>/dev/null # quiet
-
-# demo user
-#openssl req -new -newkey rsa:4096 -extensions user_ext -days 365 \
-# -subj $USERS_BASE_DN/UID=demo/ \
-# -keyout newkey.pem -passout pass:demo -out newcsr.pem
-#openssl ca -preserveDN -batch -passin pass:demo -in newcsr.pem -out newcrt.pem
-#openssl pkcs12 -export -passin pass:demo -passout pass:demo \
-# -name "demo" -inkey newkey.pem -in newcrt.pem \
-# -out demo.p12
-
-# Self-signed
-#openssl req -x509 -new -newkey rsa:4096 -extensions server_ext -days 365 \
-# -subj $SERVER_DN \
-# -keyout newkey.pem -passout pass:demo -out newcrt.pem
-# Self-signed server certificate
-#openssl pkcs12 -export -passin pass:demo -passout pass:changeit \
-# -name "jetty" -inkey newkey.pem -in newcrt.pem \
-# -certfile ./CA/cacert.pem \
-# -out server.p12
-
-echo ## Clean up
-rm -vf *.pem
diff --git a/dep/.gitignore b/dep/.gitignore
deleted file mode 100644 (file)
index b5f8bb4..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-*/feature.xml
-*/modularDistribution.csv
-*/*-maven.target
diff --git a/dep/cnf/maven.bnd b/dep/cnf/maven.bnd
deleted file mode 100644 (file)
index 4bd5c0c..0000000
+++ /dev/null
@@ -1 +0,0 @@
--include: ../../cnf/maven.bnd
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.client/bnd.bnd b/dep/org.argeo.dep.cms.client/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dep/org.argeo.dep.cms.client/build.properties b/dep/org.argeo.dep.cms.client/build.properties
deleted file mode 100644 (file)
index edef3d9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-bin.includes = feature.xml,\
-               modularDistribution.csv
diff --git a/dep/org.argeo.dep.cms.client/p2.inf b/dep/org.argeo.dep.cms.client/p2.inf
deleted file mode 100644 (file)
index 0423aa5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-properties.1.name=org.eclipse.equinox.p2.type.category
-properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.client/pom.xml b/dep/org.argeo.dep.cms.client/pom.xml
deleted file mode 100644 (file)
index d1eca2f..0000000
+++ /dev/null
@@ -1,364 +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.1-SNAPSHOT</version>
-               <artifactId>dep</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.dep.cms.client</artifactId>
-       <name>CMS Client</name>
-       <dependencies>
-
-               <!-- Argeo Commons -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.enterprise</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.jcr</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.core</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-
-               <!-- Third Parties -->
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.jcr</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.el-api</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.interceptor-api</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.enterprise.cdi-api</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.transaction-api</artifactId>
-               </dependency>
-               
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.log4j</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>org.slf4j.log4j12</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>org.slf4j.api</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>org.slf4j.commons.logging</artifactId>
-               </dependency>
-
-               <!-- OSGi annotations -->
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp.sdk</groupId> -->
-<!--                   <artifactId>osgi.annotation</artifactId> -->
-<!--           </dependency> -->
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp.sdk</groupId> -->
-<!--                   <artifactId>org.osgi.service.metatype.annotations</artifactId> -->
-<!--           </dependency> -->
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp.sdk</groupId> -->
-<!--                   <artifactId>org.osgi.service.component.annotations</artifactId> -->
-<!--           </dependency> -->
-
-
-               <dependency>
-                       <groupId>org.argeo.tp.bouncycastle</groupId>
-                       <artifactId>bcpkix</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.bouncycastle</groupId>
-                       <artifactId>bcpg</artifactId>
-               </dependency>
-
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.httpcomponents.httpcore</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.httpcomponents.httpclient</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.io</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.codec</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.exec</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.cli</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.httpclient</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.net</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.collections</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.compress</artifactId>
-               </dependency>
-
-               <!-- Equinox -->
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.osgi.util</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.util</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.cm</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.osgi.services</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.registry</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.preferences</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.common</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.event</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.app</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.ds</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.metatype</artifactId>
-               </dependency>
-
-               <!-- Console -->
-               <dependency>
-                       <groupId>org.argeo.tp.apache.felix</groupId>
-                       <artifactId>org.apache.felix.scr</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.felix</groupId>
-                       <artifactId>org.apache.felix.gogo.runtime</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.felix</groupId>
-                       <artifactId>org.apache.felix.gogo.shell</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.console</artifactId>
-               </dependency>
-
-               <!-- Jackrabbit client -->
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.api</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.jcr.commons</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.spi</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.spi.commons</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.webdav</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.spi2dav</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.jcr2dav</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.jcr2spi</artifactId>
-               </dependency>
-
-               <!-- Test only -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.osgi.boot</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <scope>test</scope>
-               </dependency>
-       </dependencies>
-
-       <profiles>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-argeo</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-client${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-client-tp${argeo.rpm.suffix}</require>
-                                                                               <require>argeo-osgi-boot${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source-tp</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-client-tp${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.e4.rap/bnd.bnd b/dep/org.argeo.dep.cms.e4.rap/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dep/org.argeo.dep.cms.e4.rap/build.properties b/dep/org.argeo.dep.cms.e4.rap/build.properties
deleted file mode 100644 (file)
index edef3d9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-bin.includes = feature.xml,\
-               modularDistribution.csv
diff --git a/dep/org.argeo.dep.cms.e4.rap/p2.inf b/dep/org.argeo.dep.cms.e4.rap/p2.inf
deleted file mode 100644 (file)
index 0423aa5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-properties.1.name=org.eclipse.equinox.p2.type.category
-properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.e4.rap/pom.xml b/dep/org.argeo.dep.cms.e4.rap/pom.xml
deleted file mode 100644 (file)
index 23e1852..0000000
+++ /dev/null
@@ -1,391 +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.1-SNAPSHOT</version>
-               <artifactId>dep</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
-       <name>CMS Platform Eclipse 4 RAP</name>
-       <dependencies>
-
-               <!-- Argeo Commons -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <type>pom</type>
-               </dependency>
-
-               <!-- E4 Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.e4</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.e4.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-
-               <!-- SVG and CSS -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.xmlgraphics.commons</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.w3c.dom.svg</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.batik.i18n</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.batik.util</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.batik.css</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.jsoup</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.w3c.css.sac</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>com.steadystate.css</artifactId> -->
-               <!-- </dependency> -->
-
-               <!-- Misc Third Parties -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.bouncycastle</groupId> -->
-               <!-- <artifactId>bcmail</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.bouncycastle</groupId> -->
-               <!-- <artifactId>bcpg</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache.ant</groupId> -->
-               <!-- <artifactId>org.apache.ant</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache.ant</groupId> -->
-               <!-- <artifactId>org.apache.ant.launch</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.quartz-scheduler.quartz</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.quartz-scheduler.quartz.jobs</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.javax</groupId> -->
-               <!-- <artifactId>javax.mail</artifactId> -->
-               <!-- </dependency> -->
-
-               <!-- Nebula -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.nebula.widgets.richtext</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.nebula.widgets.grid</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.nebula.jface.gridviewer</artifactId>
-               </dependency>
-
-               <!-- Eclipse Core -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.databinding</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.databinding.beans</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.runtime</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.databinding.property</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>com.ibm.icu</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.contenttype</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.jface.databinding</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.jobs</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.expressions</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.databinding.observable</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.help</artifactId>
-               </dependency>
-
-               <!-- Dependencies required / provided by Eclipse 4 -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.apache.commons.jxpath</artifactId>
-               </dependency>
-
-               <!-- Eclipse 4 -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.e4</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.emf.common</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.emf.ecore</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.emf.ecore.change</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.emf.ecore.xmi</artifactId>
-               </dependency>
-
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.workbench.renderers.swt</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.di</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.di</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.workbench.addons.swt</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.commands</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.bindings</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.workbench.swt</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.di.extensions.supplier</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.model.workbench</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.emf.xpath</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.contexts</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.services</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.di.annotations</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.services</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.core.di.extensions</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.e4.ui.workbench</artifactId>
-               </dependency>
-
-               <!-- SDK -->
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.junit</artifactId>
-                       <scope>test</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.hamcrest</artifactId>
-                       <scope>test</scope>
-               </dependency>
-
-       </dependencies>
-       <dependencyManagement>
-       </dependencyManagement>
-       <profiles>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-argeo</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-e4-rap${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-ui-rap${argeo.rpm.suffix}</require>
-                                                                               <require>argeo-cms-e4-rap-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source-tp</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-e4-rap-tp${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-ui-rap-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.ext/bnd.bnd b/dep/org.argeo.dep.cms.ext/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dep/org.argeo.dep.cms.ext/build.properties b/dep/org.argeo.dep.cms.ext/build.properties
deleted file mode 100644 (file)
index edef3d9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-bin.includes = feature.xml,\
-               modularDistribution.csv
diff --git a/dep/org.argeo.dep.cms.ext/p2.inf b/dep/org.argeo.dep.cms.ext/p2.inf
deleted file mode 100644 (file)
index 0423aa5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-properties.1.name=org.eclipse.equinox.p2.type.category
-properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.ext/pom.xml b/dep/org.argeo.dep.cms.ext/pom.xml
deleted file mode 100644 (file)
index 9043493..0000000
+++ /dev/null
@@ -1,144 +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.1-SNAPSHOT</version>
-               <artifactId>dep</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.dep.cms.ext</artifactId>
-       <name>CMS Optional Third Parties</name>
-       <description>Bulky generic third parties which are not required by the CMS, but necessary for upper layers.</description>
-       <dependencies>
-               <!-- Additional Third Parties -->
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.xml.bind</artifactId>
-               </dependency>
-
-               <!-- Jackson JSON processor -->
-               <dependency>
-                       <groupId>org.argeo.tp.jackson</groupId>
-                       <artifactId>com.fasterxml.jackson.core.jackson-core</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jackson</groupId>
-                       <artifactId>com.fasterxml.jackson.core.jackson-databind</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jackson</groupId>
-                       <artifactId>com.fasterxml.jackson.core.jackson-annotations</artifactId>
-               </dependency>
-
-               <!-- Mail -->
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.activation</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.mail</artifactId>
-               </dependency>
-
-               <!-- POI requirements -->
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.math3</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.collections4</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xml.security</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xmlbeans</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xalan</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xalan.serializer</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xml.resolver</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.xerces</artifactId>
-               </dependency>
-
-       </dependencies>
-
-       <profiles>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source-tp</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-ext-tp${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-node-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.node/bnd.bnd b/dep/org.argeo.dep.cms.node/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dep/org.argeo.dep.cms.node/build.properties b/dep/org.argeo.dep.cms.node/build.properties
deleted file mode 100644 (file)
index edef3d9..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-bin.includes = feature.xml,\
-               modularDistribution.csv
diff --git a/dep/org.argeo.dep.cms.node/p2.inf b/dep/org.argeo.dep.cms.node/p2.inf
deleted file mode 100644 (file)
index 0423aa5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-properties.1.name=org.eclipse.equinox.p2.type.category
-properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.node/pom.xml b/dep/org.argeo.dep.cms.node/pom.xml
deleted file mode 100644 (file)
index 8dc2dd5..0000000
+++ /dev/null
@@ -1,335 +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.1-SNAPSHOT</version>
-               <artifactId>dep</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.dep.cms.node</artifactId>
-       <name>CMS Node</name>
-       <dependencies>
-
-               <!-- Parent dependencies -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.client</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <type>pom</type>
-               </dependency>
-
-               <!-- Argeo Commons -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.maintenance</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-
-               <!-- CMS Dependencies -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>bitronix.tm</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.misc</groupId> -->
-               <!-- <artifactId>org.joda.time</artifactId> -->
-               <!-- </dependency> -->
-
-               <!-- Misc -->
-               <dependency>
-                       <groupId>org.argeo.tp.bouncycastle</groupId>
-                       <artifactId>bcprov</artifactId>
-               </dependency>
-
-               <!-- Apache Commons -->
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.dbcp</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.pool</artifactId>
-               </dependency>
-
-               <!-- Javax -->
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.annotation</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.inject</artifactId>
-               </dependency>
-
-               <!-- Jackrabbit Repository -->
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.data</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.core</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.jackrabbit</groupId>
-                       <artifactId>org.apache.jackrabbit.server</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>EDU.oswego.cs.dl.util.concurrent</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache.commons</groupId>
-                       <artifactId>org.apache.commons.fileupload</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.tika.core</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.tika.parsers</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.apache</groupId>
-                       <artifactId>org.apache.lucene</artifactId>
-               </dependency>
-
-               <!-- Required by Jackrabbit 2.12 -->
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>com.google.guava</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>com.google.guava.failureaccess</artifactId>
-               </dependency>
-
-               <!-- Database drivers -->
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>org.h2</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>org.postgresql.jdbc42</artifactId>
-               </dependency>
-
-               <!-- Third Parties -->
-               <dependency>
-                       <groupId>org.argeo.tp.misc</groupId>
-                       <artifactId>com.google.gson</artifactId>
-               </dependency>
-
-               <!-- Aries -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.aries.util</artifactId> -->
-               <!-- </dependency> -->
-               <!-- <dependency> -->
-               <!-- <groupId>org.argeo.tp.apache</groupId> -->
-               <!-- <artifactId>org.apache.aries.spifly.dynamic.bundle</artifactId> -->
-               <!-- </dependency> -->
-
-               <!-- Servlet -->
-               <dependency>
-                       <groupId>org.argeo.tp.javax</groupId>
-                       <artifactId>javax.servlet</artifactId>
-               </dependency>
-
-               <!-- HTTP Server -->
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.http.servlet</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.equinox.http.jetty</artifactId>
-               </dependency>
-
-               <!-- Jetty -->
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.client</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.continuation</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.http</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.io</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.jmx</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.security</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.server</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.servlet</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.servlets</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.util</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.jetty</groupId>
-                       <artifactId>org.eclipse.jetty.xml</artifactId>
-               </dependency>
-
-
-       </dependencies>
-
-       <profiles>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-argeo</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-node${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-client${argeo.rpm.suffix}</require>
-                                                                               <require>argeo-cms-node-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source-tp</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-node-tp${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-client-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.ui.rap/bnd.bnd b/dep/org.argeo.dep.cms.ui.rap/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dep/org.argeo.dep.cms.ui.rap/p2.inf b/dep/org.argeo.dep.cms.ui.rap/p2.inf
deleted file mode 100644 (file)
index 0423aa5..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-properties.1.name=org.eclipse.equinox.p2.type.category
-properties.1.value=true
\ No newline at end of file
diff --git a/dep/org.argeo.dep.cms.ui.rap/pom.xml b/dep/org.argeo.dep.cms.ui.rap/pom.xml
deleted file mode 100644 (file)
index 93ab967..0000000
+++ /dev/null
@@ -1,215 +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.1-SNAPSHOT</version>
-               <artifactId>dep</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.dep.cms.ui.rap</artifactId>
-       <name>CMS Platform UI RAP</name>
-       <dependencies>
-
-               <!-- Argeo Commons -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.node</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <type>pom</type>
-               </dependency>
-
-               <!-- RWT -->
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.rwt</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.rwt.osgi</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.core.commands</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.jface</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.filedialog</artifactId>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.rap.e4</groupId>
-                       <artifactId>org.eclipse.rap.fileupload</artifactId>
-               </dependency>
-
-               <!-- Argeo Commons UI -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui.theme</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-
-               <!-- SDK -->
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.junit</artifactId>
-                       <scope>test</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.hamcrest</artifactId>
-                       <scope>test</scope>
-               </dependency>
-
-       </dependencies>
-       <dependencyManagement>
-       </dependencyManagement>
-       <profiles>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-argeo</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-ui-rap${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-node${argeo.rpm.suffix}</require>
-                                                                               <require>argeo-cms-ui-rap-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>prepare-source-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <descriptorRefs>
-                                                                               <descriptorRef>a2-source-tp</descriptorRef>
-                                                                       </descriptorRefs>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-tp</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cms-ui-rap-tp${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>${project.build.directory}/${project.artifactId}-${project.version}-a2-source-tp</location>
-                                                                                                       <includes>
-                                                                                                               <include>**/*.jar</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-node-tp${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dep/pom.xml b/dep/pom.xml
deleted file mode 100644 (file)
index b83b288..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-<?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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>dep</artifactId>
-       <name>Commons Modular Distributions</name>
-       <packaging>pom</packaging>
-       <modules>
-               <module>org.argeo.dep.cms.client</module>
-               <module>org.argeo.dep.cms.node</module>
-               <module>org.argeo.dep.cms.ui.rap</module>
-               <module>org.argeo.dep.cms.e4.rap</module>
-               <module>org.argeo.dep.cms.ext</module>
-       </modules>
-       <build>
-               <plugins>
-                       <plugin>
-                               <groupId>org.codehaus.mojo</groupId>
-                               <artifactId>properties-maven-plugin</artifactId>
-                               <configuration>
-                                       <quiet>true</quiet>
-                                       <files>
-                                               <file>../../cnf/${version.context}.bnd</file>
-                                       </files>
-                               </configuration>
-                       </plugin>
-                       <plugin>
-                               <groupId>org.apache.felix</groupId>
-                               <artifactId>maven-bundle-plugin</artifactId>
-                               <configuration>
-                                       <instructions>
-                                               <SLC-ModularDistribution>default</SLC-ModularDistribution>
-                                       </instructions>
-                               </configuration>
-                       </plugin>
-                       <plugin>
-                               <groupId>org.argeo.maven.plugins</groupId>
-                               <artifactId>argeo-osgi-plugin</artifactId>
-                               <executions>
-                                       <execution>
-                                               <id>generate-descriptors</id>
-                                               <goals>
-                                                       <goal>descriptors</goal>
-                                               </goals>
-                                               <phase>generate-resources</phase>
-                                       </execution>
-                               </executions>
-                       </plugin>
-                       <plugin>
-                               <artifactId>maven-assembly-plugin</artifactId>
-                               <configuration>
-                                       <attach>false</attach>
-                               </configuration>
-                       </plugin>
-               </plugins>
-       </build>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.tp.equinox</groupId>
-                       <artifactId>org.eclipse.osgi</artifactId>
-                       <scope>test</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.junit</artifactId>
-                       <scope>test</scope>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.tp.sdk</groupId>
-                       <artifactId>org.hamcrest</artifactId>
-                       <scope>test</scope>
-               </dependency>
-       </dependencies>
-       <profiles>
-               <profile>
-                       <id>a2-target</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <configuration>
-                                                       <descriptorRefs>
-                                                               <descriptorRef>a2-target</descriptorRef>
-                                                       </descriptorRefs>
-                                               </configuration>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>check-osgi</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.argeo.maven.plugins</groupId>
-                                               <artifactId>argeo-osgi-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>check-osgi</id>
-                                                               <phase>test</phase>
-                                                               <goals>
-                                                                       <goal>equinox</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <onlyCheck>true</onlyCheck>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.osgi.boot</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                                       <scope>test</scope>
-                               </dependency>
-                       </dependencies>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/dist/argeo-cli/assembly/argeo-cli.xml b/dist/argeo-cli/assembly/argeo-cli.xml
deleted file mode 100644 (file)
index cf5b0fe..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<assembly
-       xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
-       <id>dist</id>
-       <baseDirectory></baseDirectory>
-       <formats>
-               <format>dir</format>
-       </formats>
-       <fileSets>
-               <fileSet>
-                       <directory>base/bin</directory>
-                       <outputDirectory>bin</outputDirectory>
-                       <fileMode>0755</fileMode>
-                       <includes>
-                               <include>**</include>
-                       </includes>
-               </fileSet>
-               <fileSet>
-                       <directory>base/etc</directory>
-                       <outputDirectory>etc</outputDirectory>
-                       <fileMode>0644</fileMode>
-                       <includes>
-                               <include>**</include>
-                       </includes>
-               </fileSet>
-       </fileSets>
-       <dependencySets>
-               <dependencySet>
-                       <unpack>false</unpack>
-                       <outputFileNameMapping>${artifact.groupId}/${artifact.artifactId}-${artifact.version}.${artifact.extension}</outputFileNameMapping>
-                       <outputDirectory>share/osgi</outputDirectory>
-                       <useTransitiveDependencies>true</useTransitiveDependencies>
-                       <useTransitiveFiltering>true</useTransitiveFiltering>
-                       <scope>compile</scope>
-                       <excludes>
-                               <exclude>org.argeo.tp:argeo-tp</exclude>
-                       </excludes>
-               </dependencySet>
-       </dependencySets>
-</assembly>
\ No newline at end of file
diff --git a/dist/argeo-cli/base/bin/argeo b/dist/argeo-cli/base/bin/argeo
deleted file mode 100755 (executable)
index a4d37e4..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-JVM=java
-
-APP=argeo-cli
-BASE_DIR="$(cd "$(dirname "$0")/.."; pwd -P)"
-CONF_DIR=$BASE_DIR/etc/$APP
-
-# Overwrite variables
-if [ -f $CONF_DIR/settings.sh ];then
-       . $CONF_DIR/settings.sh
-fi
-
-CLASSPATH=
-for i in $BASE_DIR/share/osgi/*/; do
-       CLASSPATH=$CLASSPATH:"$i*";
-done;
-
-$JVM \
-       -Dlog4j.configuration="file:$CONF_DIR/log4j.properties" \
-       $JAVA_OPTS -cp $CLASSPATH \
-       org.argeo.cms.cli.ArgeoCli $*
\ No newline at end of file
diff --git a/dist/argeo-cli/base/etc/argeo-cli/log4j.properties b/dist/argeo-cli/base/etc/argeo-cli/log4j.properties
deleted file mode 100644 (file)
index d93b234..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-log4j.rootLogger=WARN, console
-
-log4j.logger.org.argeo=DEBUG
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%-5p %m%n
-
-log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.file.File=/var/log/argeo/argeo.csv
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{ISO8601};"%m";%c;%p%n
-log4j.appender.file.bufferedIO=true
-log4j.appender.file.immediateFlush=false
diff --git a/dist/argeo-cli/base/etc/argeo-cli/settings.sh b/dist/argeo-cli/base/etc/argeo-cli/settings.sh
deleted file mode 100644 (file)
index b20d4d8..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-export LANG=en_US.utf8
-JAVA_OPTS="-showversion -Xmx128m"
-
-# JMX
-#JAVA_OPTS="-showversion -Xmx512m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-
-# Development
-#JAVA_OPTS="-ea -agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n -showversion -Xmx512m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-
-# JMX over server tunnel
-#JAVA_OPTS="-showversion -Xmx2048m -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.rmi.port=7084 -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-# and then: ssh root@remote-host -L 7084:127.0.0.1:7084 -N
\ No newline at end of file
diff --git a/dist/argeo-cli/native-image/jni-config.json b/dist/argeo-cli/native-image/jni-config.json
deleted file mode 100644 (file)
index 0d4f101..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-[
-]
diff --git a/dist/argeo-cli/native-image/proxy-config.json b/dist/argeo-cli/native-image/proxy-config.json
deleted file mode 100644 (file)
index e12143a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-[
-  ["org.apache.sshd.common.channel.ChannelListener"],
-  ["org.apache.sshd.common.forward.PortForwardingEventListener"],
-  ["org.apache.sshd.common.session.SessionListener"]
-]
diff --git a/dist/argeo-cli/native-image/reflect-config.json b/dist/argeo-cli/native-image/reflect-config.json
deleted file mode 100644 (file)
index 2404ce2..0000000
+++ /dev/null
@@ -1,798 +0,0 @@
-[
-{
-  "name":"com.sun.management.HotSpotDiagnosticMXBean",
-  "methods":[{"name":"getVMOption","parameterTypes":["java.lang.String"] }]
-},
-{
-  "name":"com.sun.management.VMOption",
-  "methods":[{"name":"getValue","parameterTypes":[] }]
-},
-{
-  "name":"java.lang.String",
-  "methods":[{"name":"isEmpty","parameterTypes":[] }]
-},
-{
-  "name":"java.lang.Thread",
-  "methods":[{"name":"getContextClassLoader","parameterTypes":[] }]
-},
-{
-  "name":"java.lang.Throwable",
-  "methods":[
-    {"name":"addSuppressed","parameterTypes":["java.lang.Throwable"] }, 
-    {"name":"getSuppressed","parameterTypes":[] }
-  ]
-},
-{
-  "name":"java.security.KeyFactory",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"java.security.KeyPairGenerator",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"java.security.MessageDigest",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"java.security.Signature",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"javax.crypto.Cipher",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"javax.crypto.KeyAgreement",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"javax.crypto.Mac",
-  "methods":[{"name":"getInstance","parameterTypes":["java.lang.String","java.lang.String"] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.data.FileDataStore",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.fs.local.LocalFileSystem",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.query.lucene.DefaultRedoLogFactory",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.query.lucene.SearchIndex",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.query.lucene.directory.FSDirectoryManager",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.security.simple.SimpleAccessManager",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.security.simple.SimpleLoginModule",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.jackrabbit.core.security.simple.SimpleSecurityManager",
-  "allPublicMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.log4j.Appender"
-},
-{
-  "name":"org.apache.log4j.Category"
-},
-{
-  "name":"org.apache.log4j.CategoryKey"
-},
-{
-  "name":"org.apache.log4j.ConsoleAppender",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.log4j.Layout"
-},
-{
-  "name":"org.apache.log4j.Logger"
-},
-{
-  "name":"org.apache.log4j.PatternLayout",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.log4j.helpers.Loader"
-},
-{
-  "name":"org.apache.log4j.spi.OptionHandler"
-},
-{
-  "name":"org.apache.lucene.analysis.tokenattributes.CharTermAttributeImpl",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.lucene.analysis.tokenattributes.OffsetAttributeImpl",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.lucene.analysis.tokenattributes.PayloadAttributeImpl",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.lucene.analysis.tokenattributes.PositionIncrementAttributeImpl",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.lucene.analysis.tokenattributes.TypeAttributeImpl",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.lucene.index.DirectoryReader",
-  "methods":[
-    {"name":"doOpenIfChanged","parameterTypes":[] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["org.apache.lucene.index.IndexCommit"] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["org.apache.lucene.index.IndexWriter","boolean"] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["boolean"] }
-  ]
-},
-{
-  "name":"org.apache.lucene.index.IndexReader",
-  "methods":[
-    {"name":"doOpenIfChanged","parameterTypes":[] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["org.apache.lucene.index.IndexCommit"] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["org.apache.lucene.index.IndexWriter","boolean"] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["boolean"] }, 
-    {"name":"reopen","parameterTypes":[] }, 
-    {"name":"reopen","parameterTypes":["org.apache.lucene.index.IndexCommit"] }, 
-    {"name":"reopen","parameterTypes":["org.apache.lucene.index.IndexWriter","boolean"] }, 
-    {"name":"reopen","parameterTypes":["boolean"] }
-  ]
-},
-{
-  "name":"org.apache.lucene.index.MultiReader",
-  "methods":[
-    {"name":"doOpenIfChanged","parameterTypes":[] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["boolean"] }
-  ]
-},
-{
-  "name":"org.apache.lucene.index.SegmentReader",
-  "methods":[
-    {"name":"doOpenIfChanged","parameterTypes":[] }, 
-    {"name":"doOpenIfChanged","parameterTypes":["boolean"] }
-  ]
-},
-{
-  "name":"org.apache.lucene.search.Similarity",
-  "methods":[
-    {"name":"idfExplain","parameterTypes":["org.apache.lucene.index.Term","org.apache.lucene.search.Searcher"] }, 
-    {"name":"idfExplain","parameterTypes":["org.apache.lucene.index.Term","org.apache.lucene.search.Searcher","int"] }
-  ]
-},
-{
-  "name":"org.apache.lucene.util.RamUsageEstimator$DummyOneFieldObject",
-  "fields":[{"name":"base"}]
-},
-{
-  "name":"org.apache.lucene.util.RamUsageEstimator$DummyTwoLongObject",
-  "fields":[
-    {"name":"dummy1"}, 
-    {"name":"dummy2"}
-  ]
-},
-{
-  "name":"org.apache.sshd.common.SshConstants",
-  "allPublicFields":true
-},
-{
-  "name":"org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.sshd.common.util.security.bouncycastle.BouncyCastleSecurityProviderRegistrar",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.sshd.common.util.security.eddsa.EdDSASecurityProviderRegistrar",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.detect.CompositeDetector",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true
-},
-{
-  "name":"org.apache.tika.detect.DefaultDetector",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":["org.apache.tika.mime.MimeTypes","org.apache.tika.config.ServiceLoader","java.util.Collection"] }]
-},
-{
-  "name":"org.apache.tika.detect.OverrideDetector",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.AbstractParser",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true
-},
-{
-  "name":"org.apache.tika.parser.CompositeParser",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true
-},
-{
-  "name":"org.apache.tika.parser.DefaultParser",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":["org.apache.tika.mime.MediaTypeRegistry","org.apache.tika.config.ServiceLoader","java.util.Collection","org.apache.tika.detect.EncodingDetector"] }]
-},
-{
-  "name":"org.apache.tika.parser.EmptyParser",
-  "allDeclaredFields":true,
-  "allDeclaredMethods":true,
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.apple.AppleSingleFileParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.asm.ClassParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.audio.AudioParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.audio.MidiParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.chm.ChmParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.crypto.Pkcs7Parser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.crypto.TSDParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.csv.TextAndCSVParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.dbf.DBFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.dif.DIFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.executable.ExecutableParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.external.CompositeExternalParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.font.AdobeFontMetricParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.font.TrueTypeParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.gdal.GDALParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.geo.topic.GeoParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.grib.GribParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.hdf.HDFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.html.HtmlEncodingDetector",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.hwp.HwpV5Parser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.BPGParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.ICNSParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.ImageParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.PSDParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.TiffParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.image.WebPParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.iptc.IptcAnpaParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.isatab.ISArchiveParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.jdbc.SQLite3Parser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.journal.JournalParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.jpeg.JpegParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.mbox.MboxParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.MSOwnerFileParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.OldExcelParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.TNEFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.ooxml.xwpf.ml2006.Word2006MLParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.xml.SpreadsheetMLParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.microsoft.xml.WordMLParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.mp3.Mp3Parser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.netcdf.NetCDFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.odf.OpenDocumentParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.rtf.RTFParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.txt.Icu4jEncodingDetector",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.txt.UniversalEncodingDetector",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.video.FLVParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.wordperfect.QuattroProParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.wordperfect.WordPerfectParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.xml.DcXMLParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.apache.tika.parser.xml.FictionBookParser",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"org.bouncycastle.jce.provider.BouncyCastleProvider",
-  "methods":[{"name":"<init>","parameterTypes":[] }]
-},
-{
-  "name":"sun.misc.Unsafe",
-  "fields":[{"name":"theUnsafe"}],
-  "methods":[
-    {"name":"addressSize","parameterTypes":[] }, 
-    {"name":"arrayBaseOffset","parameterTypes":["java.lang.Class"] }, 
-    {"name":"arrayIndexScale","parameterTypes":["java.lang.Class"] }, 
-    {"name":"objectFieldOffset","parameterTypes":["java.lang.reflect.Field"] }
-  ]
-}
-]
diff --git a/dist/argeo-cli/native-image/resource-config.json b/dist/argeo-cli/native-image/resource-config.json
deleted file mode 100644 (file)
index 1717dd7..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "resources":[
-    {"pattern":"\\QMETA-INF/services/org.apache.tika.detect.Detector\\E"}, 
-    {"pattern":"\\QMETA-INF/services/org.apache.tika.detect.EncodingDetector\\E"}, 
-    {"pattern":"\\QMETA-INF/services/org.apache.tika.parser.Parser\\E"}, 
-    {"pattern":"\\Qlog4j.properties\\E"}, 
-    {"pattern":"\\Qmozilla/public-suffix-list.txt\\E"}, 
-    {"pattern":"\\Qorg/apache/http/client/version.properties\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/core/config/deprecated-classes.properties\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/core/config/repository-1.6.dtd\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/core/query/lucene/tika-config.xml\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/core/repository.properties\\E"}, 
-    {"pattern":"\\Qorg/apache/jackrabbit/webdav/statuscode.properties\\E"}, 
-    {"pattern":"\\Qorg/apache/sshd/sshd-version.properties\\E"}, 
-    {"pattern":"\\Qorg/apache/tika/mime/tika-mimetypes.xml\\E"}, 
-    {"pattern":"\\Qorg/apache/tika/parser/external/tika-external-parsers.xml\\E"}, 
-    {"pattern":"\\Qorg/apache/tika/parser/geo/topic/GeoTopicConfig.properties\\E"}, 
-    {"pattern":"\\Qorg/argeo/cli/jcr/repository-localfs.xml\\E"}, 
-    {"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"}
-  ],
-  "bundles":[]
-}
diff --git a/dist/argeo-cli/pom.xml b/dist/argeo-cli/pom.xml
deleted file mode 100644 (file)
index d04ba56..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-<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.1-SNAPSHOT</version>
-               <artifactId>dist</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>argeo-cli</artifactId>
-       <packaging>pom</packaging>
-       <name>Argeo Command Line</name>
-       <properties>
-               <graalvm.version>20.3.0</graalvm.version>
-       </properties>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.client</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.dep.cms.node</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-       <profiles>
-               <profile>
-                       <id>dist</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.apache.maven.plugins</groupId>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <configuration>
-                                                       <finalName>argeo-cli-${version.released}</finalName>
-                                                       <appendAssemblyId>false</appendAssemblyId>
-                                                       <descriptors>
-                                                               <descriptor>assembly/argeo-cli.xml</descriptor>
-                                                       </descriptors>
-                                               </configuration>
-                                               <executions>
-                                                       <execution>
-                                                               <id>assembly-base</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-node</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-cli${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/etc/argeo-cli</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>wheel</groupname>
-                                                                                       <filemode>640</filemode>
-                                                                                       <configuration>noreplace</configuration>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>base/etc/argeo-cli</location>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/bin</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>755</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/bin</location>
-                                                                                                       <includes>
-                                                                                                               <include>argeo</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-client</require>
-                                                                               <!-- do not explicitely require java -->
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>native-image</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.graalvm.nativeimage</groupId>
-                                               <artifactId>native-image-maven-plugin</artifactId>
-                                               <version>${graalvm.version}</version>
-                                               <executions>
-                                                       <execution>
-                                                               <goals>
-                                                                       <goal>native-image</goal>
-                                                               </goals>
-                                                               <phase>package</phase>
-                                                       </execution>
-                                               </executions>
-                                               <configuration>
-                                                       <imageName>argeo</imageName>
-                                                       <mainClass>org.argeo.cms.cli.ArgeoCli</mainClass>
-                                                       <buildArgs>
-                                                               --initialize-at-build-time=org.apache.lucene.util.AttributeImpl,org.apache.lucene.util.VirtualMethod,org.apache.lucene.util.WeakIdentityMap
-                                                               -H:IncludeResourceBundles=sun.security.util.Resources
-                                                               --no-fallback
-                                                               --no-server
-                                                               --allow-incomplete-classpath
-                                                               --enable-all-security-services
-                                                               -H:EnableURLProtocols=http,https
-                                                               -H:ConfigurationFileDirectories=${basedir}/native-image
-                                                               -H:ReflectionConfigurationFiles=${basedir}/native-image/reflect-config.json
-                                                               -H:ResourceConfigurationFiles=${basedir}/native-image/resource-config.json
-                                                               -H:JNIConfigurationFiles=${basedir}/native-image/jni-config.json
-                                                               -H:DynamicProxyConfigurationFiles=${basedir}/native-image/proxy-config.json
-                                                       </buildArgs>
-                                                       <skip>false</skip>
-                                               </configuration>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               <!--
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.graalvm.sdk</groupId>
-                                       <artifactId>graal-sdk</artifactId>
-                                       <version>${graalvm.version}</version>
-                                       <scope>provided</scope>
-                               </dependency>
-                       </dependencies>
-                       -->
-               </profile>
-       </profiles>
-</project>
diff --git a/dist/argeo-cli/rpm/usr/bin/argeo b/dist/argeo-cli/rpm/usr/bin/argeo
deleted file mode 100755 (executable)
index bae7d86..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-JVM=java
-
-APP=argeo-cli
-CONF_DIR=/etc/$APP
-
-# Overwrite variables
-if [ -f $CONF_DIR/settings.sh ];then
-       . $CONF_DIR/settings.sh
-fi
-
-CLASSPATH=
-for i in /usr/local/share/osgi/*/; do
-       CLASSPATH=$CLASSPATH:"$i*";
-done;
-for i in /usr/local/lib/osgi/*/; do
-       CLASSPATH=$CLASSPATH:"$i*";
-done;
-for i in /usr/share/osgi/*/; do
-       CLASSPATH=$CLASSPATH:"$i*";
-done;
-for i in /usr/lib/osgi/*/; do
-       CLASSPATH=$CLASSPATH:"$i*";
-done;
-
-$JVM \
-       -Dlog4j.configuration="file:$CONF_DIR/log4j.properties" \
-       $JAVA_OPTS -cp $CLASSPATH \
-       org.argeo.cms.cli.ArgeoCli $*
\ No newline at end of file
diff --git a/dist/argeo-node/assembly/cms-e4-rap.xml b/dist/argeo-node/assembly/cms-e4-rap.xml
deleted file mode 100644 (file)
index 877d6df..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-<assembly
-       xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
-       <id>dist</id>
-       <baseDirectory></baseDirectory>
-       <formats>
-               <format>dir</format>
-       </formats>
-       <fileSets>
-               <fileSet>
-                       <directory>base/bin</directory>
-                       <outputDirectory>bin</outputDirectory>
-                       <fileMode>0755</fileMode>
-                       <includes>
-                               <include>**</include>
-                       </includes>
-               </fileSet>
-               <fileSet>
-                       <directory>base/etc</directory>
-                       <outputDirectory>etc</outputDirectory>
-                       <fileMode>0644</fileMode>
-                       <includes>
-                               <include>**</include>
-                       </includes>
-               </fileSet>
-               <fileSet>
-                       <directory>base/share</directory>
-                       <outputDirectory>share</outputDirectory>
-                       <fileMode>0644</fileMode>
-                       <includes>
-                               <include>**</include>
-                       </includes>
-               </fileSet>
-       </fileSets>
-       <dependencySets>
-               <dependencySet>
-                       <unpack>false</unpack>
-                       <outputFileNameMapping>${artifact.groupId}/${artifact.artifactId}-${artifact.version}.${artifact.extension}</outputFileNameMapping>
-                       <outputDirectory>share/osgi</outputDirectory>
-                       <useTransitiveDependencies>true</useTransitiveDependencies>
-                       <useTransitiveFiltering>true</useTransitiveFiltering>
-                       <scope>compile</scope>
-                       <excludes>
-                               <exclude>org.argeo.tp:argeo-tp</exclude>
-                       </excludes>
-               </dependencySet>
-               <dependencySet>
-                       <useStrictFiltering>true</useStrictFiltering>
-                       <unpack>true</unpack>
-                       <outputDirectory></outputDirectory>
-                       <includes>
-                               <include>org.argeo.commons:osgi-boot:zip:*:*</include>
-                       </includes>
-               </dependencySet>
-       </dependencySets>
-</assembly>
\ No newline at end of file
diff --git a/dist/argeo-node/base/bin/argeo-cms b/dist/argeo-node/base/bin/argeo-cms
deleted file mode 100755 (executable)
index bf3afa2..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/bin/sh
-APP=argeo
-
-JVM=java
-
-BIN_DIR=`dirname "$0"`
-BASE_DIR="$(cd "$(dirname "$0")/.."; pwd -P)"
-#BASE_DIR=$BIN_DIR/..
-
-# Directories and files
-CONF_DIR=$BASE_DIR/etc/$APP
-CONF_DIRS=$CONF_DIR/conf.d
-BASE_CONFIG_INI=$BASE_DIR/share/$APP/config.ini
-
-EXEC_DIR=.
-DATA_DIR=$EXEC_DIR/data
-CONF_RW=$EXEC_DIR/state
-CONFIG_INI=$CONF_RW/config.ini
-
-A2_SOURCES=a2://$BASE_DIR/share/osgi
-OSGI_INSTALL_AREA=$BASE_DIR/share/osgi/boot
-OSGI_FRAMEWORK=$OSGI_INSTALL_AREA/org.eclipse.osgi.jar
-
-# Overwrite variables
-if [ -f $CONF_DIR/settings.sh ];then
-       . $CONF_DIR/settings.sh
-fi
-
-RETVAL=0
-
-start() {
-       mkdir -p $CONF_RW
-       mkdir -p $DATA_DIR
-
-    # Merge config files
-    printf "## Equinox configuration - Generated by argeo-cms ##\n\n" > $CONFIG_INI
-    cat $BASE_CONFIG_INI >> $CONFIG_INI
-    printf "\n##\n## $CONF_DIR/$APP.ini\n##\n\n" >> $CONFIG_INI
-    cat $CONF_DIR/$APP.ini >> $CONFIG_INI
-    for file in `ls -v $CONF_DIRS/*.ini`; do
-            printf "\n##\n## $file\n##\n\n" >> $CONFIG_INI
-            cat $file >> $CONFIG_INI
-    done;
-
-       cd $EXEC_DIR
-       $JVM \
-               -Dlog4j.configuration="file:$CONF_DIR/log4j.properties" \
-               $JAVA_OPTS -jar $OSGI_FRAMEWORK \
-               -Dargeo.osgi.sources=$A2_SOURCES \
-               -configuration "$CONF_RW" \
-               -data "$DATA_DIR"
-}
-
-reload() {
-       echo Not yet implemented
-}
-
-stop() {
-       if [ -f $PID_FILE ];then
-               PID=`cat $PID_FILE`
-               kill -0 $PID &> /dev/null
-               PID_EXISTS=$?
-               if [ $PID_EXISTS -ne 0 ]; then
-                       echo Dead $APP process with pid $PID, removing $PID_FILE
-                       rm -f $PID_FILE
-                       RETVAL=1
-                       return $RETVAL
-               fi
-       else
-               echo $APP is not running
-               RETVAL=1
-               return $RETVAL
-       fi
-       
-       # notifies application by removing the shutdown file
-#      rm -f $SHUTDOWN_FILE
-       kill $PID
-       
-       # wait 10 min for application to shutdown, then kill it
-       TIMEOUT=$((10*60))
-       BEGIN=$(date +%s)
-       while kill -0 $PID &> /dev/null
-       do
-               sleep 1
-               NOW=$(date +%s)
-               DURATION=$(($NOW-$BEGIN))
-               if [ $DURATION -gt $TIMEOUT ]; then
-                       kill -9 $PID
-                       echo Forcibly killed $APP with pid $PID
-                       RETVAL=1
-               fi
-       done
-       
-       # remove pid file
-       rm -f $PID_FILE
-       return $RETVAL
-}
-
-status() {
-       if [ -f $PID_FILE ];then
-               PID=`cat $PID_FILE`
-       else
-               echo $APP is not running
-               return $RETVAL
-       fi
-       kill -0 $PID &> /dev/null
-       PID_EXISTS=$?
-       if [ $PID_EXISTS -eq 0 ]; then
-               echo $APP is running with pid $PID ...
-       else
-               echo No $APP process with pid $PID, removing $PID_FILE
-               rm -f $PID_FILE
-       fi
-       return $RETVAL
-}
-
-# main
-case "$1" in
-  start)
-        start
-        ;;
-  reload)
-        reload
-        ;;
-  stop)
-        stop
-        ;;
-  status)
-       status
-        ;;
-  *)
-        start
-        ;;
-esac
\ No newline at end of file
diff --git a/dist/argeo-node/base/bin/argeo-cms.jsh b/dist/argeo-node/base/bin/argeo-cms.jsh
deleted file mode 100644 (file)
index b221240..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-// Run from base directory with:\r
-// ./bin/a2sh --startup ./share/argeo/cms.jsh ./bin/argeo-cms.jsh\r
-\r
-osgi.setHttpPort(7080);\r
-osgi.conf("argeo.node.useradmin.uris", "os:///");\r
-osgi.setClean(true);\r
-\r
-// LAUNCH\r
-osgi.launch();
\ No newline at end of file
diff --git a/dist/argeo-node/base/etc/argeo.d/config-template.ini b/dist/argeo-node/base/etc/argeo.d/config-template.ini
deleted file mode 100644 (file)
index df880c6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#argeo.osgi.start.6.apps=bundle1.to.start,bundle2.to.start
-
-#org.osgi.service.http.port=8080
-#org.eclipse.equinox.http.jetty.http.host=[IP address to listen to]
-#osgi.console=2323
-
-# Use centralised standard CMS config
-osgi.configuration.cascaded=true
-osgi.sharedConfiguration.area=/usr/share/argeo
-osgi.sharedConfiguration.area.readOnly=true
diff --git a/dist/argeo-node/base/etc/argeo.d/jvm.args b/dist/argeo-node/base/etc/argeo.d/jvm.args
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dist/argeo-node/base/etc/argeo.d/jvm.args.debug b/dist/argeo-node/base/etc/argeo.d/jvm.args.debug
deleted file mode 100644 (file)
index 4e6b1dc..0000000
+++ /dev/null
@@ -1 +0,0 @@
--agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:8000
\ No newline at end of file
diff --git a/dist/argeo-node/base/etc/argeo.d/log4j.properties b/dist/argeo-node/base/etc/argeo.d/log4j.properties
deleted file mode 100644 (file)
index cef8f33..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-log4j.rootLogger=WARN, console
-
-## Levels
-log4j.logger.org.argeo=DEBUG
-log4j.logger.org.djapps.on=DEBUG
-
-## Appenders
-# default appender
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%d{yyyyMMdd HH:mm:ss} %-5p %m [%t] %c%n
-
-## File appender 
-log4j.appender.file=org.apache.log4j.RollingFileAppender
-log4j.appender.file.file=/var/log/argeo.d/<app>/node.log
-log4j.appender.file.MaxFileSize=20MB
-log4j.appender.file.MaxBackupIndex=8
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %m [%t] %p %n
\ No newline at end of file
diff --git a/dist/argeo-node/base/etc/argeo/argeo.ini b/dist/argeo-node/base/etc/argeo/argeo.ini
deleted file mode 100644 (file)
index a4cbdcc..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-## HTTP server
-org.osgi.service.http.port=8080
-
-## System management
-osgi.console=2323
-
-## Standalone
-#argeo.node.useradmin.uris=dc=example,dc=com.ldif
-#argeo.node.repo.type=h2
-
-## Deployed
-#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost/dc=example,dc=com
-#argeo.node.repo.type=postgresql_ds
-#argeo.node.repo.dburl=jdbc:postgresql://localhost/argeo
-#argeo.node.repo.dbuser=argeo
-#argeo.node.repo.dbpassword=argeo
-
-## Complex user configuration examples
-#argeo.node.useradmin.uris="dc=example,dc=com.ldif dc=example,dc=org.ldif"
-#argeo.node.useradmin.uris="ldap://uid=admin,ou=system:secret@localhost:10389\
-#/dc=example,dc=com?userBase=ou=users&groupBase=ou=groups dc=example,dc=org.ldif"
-
-# Legacy
-#osgi.clean=true
-#java.security.manager=
-#java.security.policy=file:/usr/share/argeo/all.policy
-
-# Eclipse 3
-#argeo.osgi.start.4.eclipse3=\
-#org.eclipse.equinox.http.registry,\
-#org.eclipse.gemini.blueprint.extender
-#org.eclipse.rap.workbenchAutostart=false
diff --git a/dist/argeo-node/base/etc/argeo/conf.d/app-template.txt b/dist/argeo-node/base/etc/argeo/conf.d/app-template.txt
deleted file mode 100644 (file)
index 89d582d..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-# Rename to <my app>.ini
-
-# Backend
-#argeo.osgi.start.5.apps=org.argeo.suite.apps
-
-# UI
-#argeo.osgi.start.6.apps=org.argeo.suite.apps.web,org.argeo.suite.e4.rap
diff --git a/dist/argeo-node/base/etc/argeo/log4j.properties b/dist/argeo-node/base/etc/argeo/log4j.properties
deleted file mode 100644 (file)
index d93b234..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-log4j.rootLogger=WARN, console
-
-log4j.logger.org.argeo=DEBUG
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%-5p %m%n
-
-log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.file.File=/var/log/argeo/argeo.csv
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{ISO8601};"%m";%c;%p%n
-log4j.appender.file.bufferedIO=true
-log4j.appender.file.immediateFlush=false
diff --git a/dist/argeo-node/base/etc/argeo/settings.sh b/dist/argeo-node/base/etc/argeo/settings.sh
deleted file mode 100644 (file)
index b20d4d8..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-export LANG=en_US.utf8
-JAVA_OPTS="-showversion -Xmx128m"
-
-# JMX
-#JAVA_OPTS="-showversion -Xmx512m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-
-# Development
-#JAVA_OPTS="-ea -agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n -showversion -Xmx512m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-
-# JMX over server tunnel
-#JAVA_OPTS="-showversion -Xmx2048m -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.rmi.port=7084 -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
-# and then: ssh root@remote-host -L 7084:127.0.0.1:7084 -N
\ No newline at end of file
diff --git a/dist/argeo-node/base/share/argeo/SETUP.txt b/dist/argeo-node/base/share/argeo/SETUP.txt
deleted file mode 100644 (file)
index 41afce2..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-
-# 389 Directory Server
-setup-ds.pl --silent --file=argeo-slapd.inf
-
-# PostgreSQL
-postgresql-setup initdb
-systemctl start postgresql
-sudo -u postgres psql < argeo-pgsql-setup.sql
diff --git a/dist/argeo-node/base/share/argeo/all.policy b/dist/argeo-node/base/share/argeo/all.policy
deleted file mode 100644 (file)
index facb613..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-};
\ No newline at end of file
diff --git a/dist/argeo-node/base/share/argeo/argeo-pgsql-setup.sql b/dist/argeo-node/base/share/argeo/argeo-pgsql-setup.sql
deleted file mode 100644 (file)
index 886f60a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-CREATE USER argeo WITH PASSWORD 'argeo';
-CREATE DATABASE argeo WITH OWNER argeo;
diff --git a/dist/argeo-node/base/share/argeo/argeo-slapd-setup.inf b/dist/argeo-node/base/share/argeo/argeo-slapd-setup.inf
deleted file mode 100644 (file)
index cb142b5..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-[general]
-[slapd]
-instance_name = argeo
-root_dn = cn=Directory Manager
-root_password = argeoargeo
-
-[backend-userroot]
-suffix = dc=example,dc=com
\ No newline at end of file
diff --git a/dist/argeo-node/base/share/argeo/cms.jsh b/dist/argeo-node/base/share/argeo/cms.jsh
deleted file mode 100755 (executable)
index 27dd138..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import java.nio.file.*;
-import org.argeo.osgi.boot.*;
-
-OsgiBuilder osgi = new OsgiBuilder();
-
-// default bundles
-osgi.start(2, "org.eclipse.equinox.http.servlet");
-osgi.start(2, "org.eclipse.equinox.metatype");
-osgi.start(2, "org.eclipse.equinox.cm");
-osgi.start(2, "org.eclipse.equinox.ds");
-osgi.start(2, "org.eclipse.rap.rwt.osgi");
-osgi.start(3, "org.argeo.cms");
-osgi.start(4, "org.argeo.cms.e4.rap");
-
-// specific properties
-osgi.conf("org.eclipse.rap.workbenchAutostart", "false");
-osgi.conf("org.eclipse.equinox.http.jetty.autostart", "false");
-osgi.conf("org.osgi.framework.bootdelegation", "com.sun.jndi.ldap,"
-               + "com.sun.jndi.ldap.sasl," + "com.sun.security.jgss,"
-               + "com.sun.jndi.dns," + "com.sun.nio.file," + "com.sun.nio.sctp");
-
-String homeUri = Paths.get(System.getProperty("user.home")).toUri().toString();
-String execDirUri = Paths.get(System.getProperty("user.dir")).toUri().toString();
-
-osgi.conf("osgi.configuration.area", execDirUri + "/state");
-osgi.conf("osgi.instance.area", execDirUri + "/data");
-System.setProperty("log4j.configuration", execDirUri + "etc/argeo/log4j.properties");
diff --git a/dist/argeo-node/base/share/argeo/config.ini b/dist/argeo-node/base/share/argeo/config.ini
deleted file mode 100644 (file)
index e5cc050..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-# Only Argeo OSGi Boot is explicitly started
-osgi.bundles=org.argeo.osgi.boot@start
-
-# Only clean state is currently fully supported
-osgi.clean=true
-
-# Required standard bundles to start
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.http.servlet,\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
-org.eclipse.rap.rwt.osgi
-
-# Required CMS bundles to start
-argeo.osgi.start.3.node=\
-org.argeo.cms
-
-# Extension managers
-argeo.osgi.start.4.node=\
-org.argeo.cms.e4.rap
-
-# Packages provided by the OpenJDK JVM
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
-com.sun.jndi.ldap.sasl,\
-com.sun.security.jgss,\
-com.sun.jndi.dns,\
-com.sun.nio.file,\
-com.sun.nio.sctp
-
-# Required properties
-eclipse.ignoreApp=true
-osgi.noShutdown=true
-org.eclipse.equinox.http.jetty.autostart=false
-
-# Disable some warnings
-nashorn.option.no.deprecation.warning=true
diff --git a/dist/argeo-node/base/share/argeo/jvm.args b/dist/argeo-node/base/share/argeo/jvm.args
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/dist/argeo-node/pom.xml b/dist/argeo-node/pom.xml
deleted file mode 100644 (file)
index 9a6d616..0000000
+++ /dev/null
@@ -1,197 +0,0 @@
-<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.1-SNAPSHOT</version>
-               <artifactId>dist</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>argeo-node</artifactId>
-       <packaging>pom</packaging>
-       <name>Argeo Node</name>
-       <profiles>
-               <profile>
-                       <id>dist</id>
-                       <dependencies>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.client</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.node</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>org.argeo.dep.cms.e4.rap</artifactId>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                               <dependency>
-                                       <groupId>org.argeo.commons</groupId>
-                                       <artifactId>osgi-boot</artifactId>
-                                       <type>zip</type>
-                                       <version>2.1-SNAPSHOT</version>
-                               </dependency>
-                       </dependencies>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.apache.maven.plugins</groupId>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <configuration>
-                                                       <finalName>argeo-node-${version.released}</finalName>
-                                                       <appendAssemblyId>false</appendAssemblyId>
-                                                       <descriptors>
-                                                               <descriptor>assembly/cms-e4-rap.xml</descriptor>
-                                                       </descriptors>
-                                               </configuration>
-                                               <executions>
-                                                       <execution>
-                                                               <id>assembly-base</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-node</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-node${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/etc/argeo</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>640</filemode>
-                                                                                       <configuration>noreplace</configuration>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>base/etc/argeo</location>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/etc/argeo.d</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <configuration>noreplace</configuration>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>base/etc/argeo.d</location>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/etc/argeo/conf.d</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>640</filemode>
-                                                                                       <configuration>noreplace</configuration>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>base/etc/argeo/conf.d</location>
-                                                                                                       <includes>
-                                                                                                               <include>*.ini</include>
-                                                                                                               <include>*.txt</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/argeo</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>base/share/argeo</location>
-                                                                                                       <includes>
-                                                                                                               <include>**</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/lib/systemd/system</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/lib/systemd/system</location>
-                                                                                                       <includes>
-                                                                                                               <include>*.service</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/lib/systemd/user</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/lib/systemd/user</location>
-                                                                                                       <includes>
-                                                                                                               <include>*.service</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/sbin</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>755</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/sbin</location>
-                                                                                                       <includes>
-                                                                                                               <include>argeoctl</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-cms-node${argeo.rpm.suffix}</require>
-                                                                               <require>argeo-osgi-boot${argeo.rpm.suffix}</require>
-                                                                               <!-- do not explicitely require java -->
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
diff --git a/dist/argeo-node/rpm/usr/lib/systemd/system/argeo.service b/dist/argeo-node/rpm/usr/lib/systemd/system/argeo.service
deleted file mode 100644 (file)
index 1cdb7c2..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-[Unit]
-Description=Argeo default node
-After=network.target
-Wants=postgresql.service
-
-[Service]
-Type=simple
-ExecStart=/usr/sbin/argeoctl start
-ExecReload=/usr/sbin/argeoctl reload
-SuccessExitStatus=143
-
-[Install]
-WantedBy=multi-user.target
diff --git a/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service b/dist/argeo-node/rpm/usr/lib/systemd/system/argeo@.service
deleted file mode 100644 (file)
index c631825..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-[Unit]
-Description=Argeo node %I
-After=network.target
-Wants=postgresql.service
-
-[Service]
-Type=simple
-StateDirectory=argeo.d/%I
-LogsDirectory=argeo.d/%I
-ConfigurationDirectory=argeo.d/%I
-CacheDirectory=argeo.d/%I
-WorkingDirectory=/var/lib/argeo.d/%I
-
-ExecStart=/usr/lib/jvm/jre-11/bin/java \
--Dosgi.configuration.cascaded=true \
--Dosgi.sharedConfiguration.area=/etc/argeo.d/%I \
--Dosgi.sharedConfiguration.area.readOnly=true \
--Dargeo.node.repo.indexesBase=/var/cache/argeo.d/%I/indexes \
--Dorg.osgi.framework.bootdelegation=com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp \
--Declipse.ignoreApp=true \
--Dosgi.noShutdown=true \
--Dorg.eclipse.equinox.http.jetty.autostart=false \
--Dosgi.bundles=org.argeo.osgi.boot@start \
-@/usr/share/argeo/jvm.args \
-@/etc/argeo.d/jvm.args \
-@/etc/argeo.d/%I/jvm.args \
-@/usr/share/osgi/boot/framework.args \
--configuration /var/lib/argeo.d/%I/state \
--data /var/lib/argeo.d/%I/data
-# Exit codes of the JVM when SIGTERM or SIGINT have been caught:
-SuccessExitStatus=143 130
-
-[Install]
-WantedBy=multi-user.target
diff --git a/dist/argeo-node/rpm/usr/lib/systemd/user/argeo@.service b/dist/argeo-node/rpm/usr/lib/systemd/user/argeo@.service
deleted file mode 100644 (file)
index 198d508..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-[Unit]
-Description=Argeo user node %I for %u
-
-[Service]
-Type=simple
-ExecStart=/usr/sbin/argeoctl start %I
-ExecReload=/usr/sbin/argeoctl reload %I
diff --git a/dist/argeo-node/rpm/usr/sbin/argeoctl b/dist/argeo-node/rpm/usr/sbin/argeoctl
deleted file mode 100755 (executable)
index c355308..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/bin/sh
-APP=argeo
-
-if [ -z "$2" ]; then
-# Default node
-echo Argeo default node
-CONF_DIR=/etc/$APP
-EXEC_DIR=/var/lib/$APP
-
-else
-# Instance
-INSTANCE=$2
-echo Argeo instance $INSTANCE
-  if [ -z "$INSTANCE_DIR" ]; then
-  INSTANCE_DIR=$HOME/.local/share/$APP.d/$INSTANCE
-  fi
-  if [ -z "$CONF_DIR" ]; then
-  CONF_DIR=$HOME/.config/$APP.d/$INSTANCE
-  fi  
-EXEC_DIR=$INSTANCE_DIR
-# Make sure minimal files are available
-  if [ ! -f $CONF_DIR/$APP.ini ]; then
-  cp /etc/$APP/$APP.ini $CONF_DIR
-  fi
-  if [ ! -f $CONF_DIR/log4j.properties ]; then
-  cp /etc/$APP/log4j.properties $CONF_DIR
-  fi
-fi
-
-# Java
-if [ -z "$JVM" ]; then
-JVM=java
-fi
-
-# Directories and files
-
-BASE_POLICY_ALL=/usr/share/$APP/all.policy
-BASE_CONFIG_INI=/usr/share/$APP/config.ini
-
-CONF_DIRS=$CONF_DIR/conf.d
-DATA_DIR=$EXEC_DIR/data
-CONF_RW=$EXEC_DIR/state
-CONFIG_INI=$CONF_RW/config.ini
-
-# A2 sources can be overridden in *.ini files
-A2_SOURCES=a2:///
-OSGI_INSTALL_AREA=/usr/share/osgi/boot
-OSGI_FRAMEWORK=$OSGI_INSTALL_AREA/org.eclipse.osgi.jar
-
-# Overwrite variables
-if [ -f $CONF_DIR/settings.sh ];then
-  . $CONF_DIR/settings.sh
-fi
-
-RETVAL=0
-
-## START ##
-start() {
-mkdir -p $CONF_RW
-mkdir -p $DATA_DIR
-
-# Merge config files
-printf "## Equinox configuration - Generated by /usr/sbin/argeoctl ##\n\n" > $CONFIG_INI
-cat $BASE_CONFIG_INI >> $CONFIG_INI
-printf "\n##\n## $CONF_DIR/$APP.ini\n##\n\n" >> $CONFIG_INI
-cat $CONF_DIR/$APP.ini >> $CONFIG_INI
-# Concatenate additional .ini files
-if [ -d "$CONF_DIRS" ]; then
-for file in `ls -v $CONF_DIRS/*.ini`; do
-  printf "\n##\n## $file\n##\n\n" >> $CONFIG_INI
-  cat $file >> $CONFIG_INI
-done;
-fi
-
-cd $EXEC_DIR
-$JVM \
-  -Dlog4j.configuration="file:$CONF_DIR/log4j.properties" \
-  $JAVA_OPTS -jar $OSGI_FRAMEWORK \
-  -Dargeo.osgi.sources=$A2_SOURCES \
-  -configuration "$CONF_RW" \
-  -data "$DATA_DIR"
-}
-
-## RELOAD ##
-reload() {
-echo Not yet implemented
-}
-
-# main
-case "$1" in
-start)
-  start
-  ;;
-reload)
-  reload
-  ;;
-*)
-  echo $"Usage: $0 {start|reload}"
-  exit 1
-esac
\ No newline at end of file
diff --git a/dist/containers/argeo2-all b/dist/containers/argeo2-all
deleted file mode 100755 (executable)
index 4ad00fa..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-./argeo2-java
-./argeo2-tp
-./argeo2-node
-./argeo2-builder
diff --git a/dist/containers/argeo2-builder b/dist/containers/argeo2-builder
deleted file mode 100755 (executable)
index 055522b..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/buildah-metadata"
-container=$(buildah from argeo/argeo2-java:openjdk-v$VERSION_JAVA-$ARCH)
-
-source "$(dirname "$0")/buildah-common"
-
-buildah config --label release="1" $container
-buildah config --label version="$VERSION_MAVEN" $container
-
-# Metadata
-buildah config --label name="argeo2-builder" $container
-buildah config --label description="Argeo 2 Builder" $container
-
-# Utilities
-#buildah run $container -- microdnf -y install git
-# Java 11
-#buildah run $container -- microdnf -y install java-11-openjdk-devel
-# Maven
-#buildah run $container -- microdnf -y install maven
-#buildah copy $container maven.conf /etc/java/maven.conf
-
-buildah run $container -- microdnf -y install tar gzip
-buildah copy $container https://archive.apache.org/dist/maven/maven-3/$VERSION_MAVEN/binaries/apache-maven-$VERSION_MAVEN-bin.tar.gz /opt
-buildah run $container -- tar -C /opt -xzf /opt/apache-maven-$VERSION_MAVEN-bin.tar.gz
-buildah run $container -- rm -f /opt/apache-maven-$VERSION_MAVEN-bin.tar.gz
-buildah run $container -- microdnf -y remove tar gzip
-
-buildah run $container -- ln -s /opt/apache-maven-$VERSION_MAVEN/bin/mvn /usr/local/bin/mvn
-# Maven script requires which
-buildah run $container -- microdnf -y install which
-# Clean microdnf
-buildah run $container -- microdnf clean all
-
-buildah run $container -- mkdir -p /srv/javafactory/
-
-# Working dir
-buildah run $container -- mkdir -p /root/build/
-buildah config --workingdir /root/build/ $container
-
-# Perform a build of argeo-commons
-buildah copy $container ../.. /root/build
-buildah run $container -- mvn clean install
-#buildah run $container -- mvn dependency:go-offline
-
-# Clean up build directories
-buildah run $container -- rm -rf /root/.m2/repository/org/argeo/commons
-buildah run $container -- rm -rf /root/build
-buildah run $container -- mkdir -p /root/build/
-
-# Configuration
-buildah config --entrypoint '["mvn","clean","install"]' $container
-
-buildah commit --rm --format docker $container argeo/argeo2-builder:maven-v$VERSION_MAVEN-$ARCH
-buildah tag argeo/argeo2-builder:maven-v$VERSION_MAVEN-$ARCH argeo/argeo2-builder:$ARCH
-
-buildah push argeo/argeo2-builder:maven-v$VERSION_MAVEN-$ARCH docker://argeo/argeo2-builder:maven-v$VERSION_MAVEN-$ARCH
-buildah push argeo/argeo2-builder:$ARCH docker://argeo/argeo2-builder:$ARCH
diff --git a/dist/containers/argeo2-java b/dist/containers/argeo2-java
deleted file mode 100755 (executable)
index 170872f..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/buildah-metadata"
-buildah pull ubi8/ubi-minimal
-container=$(buildah from ubi8/ubi-minimal)
-
-source "$(dirname "$0")/buildah-common"
-
-buildah config --label release="1" $container
-buildah config --label version="$VERSION_JAVA" $container
-
-# Metadata
-buildah config --label name="argeo2-java" $container
-buildah config --label description="OpenJDK 11 headless on Red Hat UBI 8" $container
-buildah config --label url=https://hub.docker.com/repository/docker/argeo/argeo2-java $container
-
-# Java 11
-buildah run $container -- microdnf install java-11-openjdk-headless
-buildah run $container -- microdnf clean all
-
-# Configuration
-buildah config --entrypoint '["java"]' $container
-
-buildah commit --rm --format docker $container argeo/argeo2-java:openjdk-v$VERSION_JAVA-$ARCH
-buildah tag argeo/argeo2-java:openjdk-v$VERSION_JAVA-$ARCH argeo/argeo2-java:$ARCH
-
-buildah push argeo/argeo2-java:openjdk-v$VERSION_JAVA-$ARCH docker://argeo/argeo2-java:openjdk-v$VERSION_JAVA-$ARCH
-buildah push argeo/argeo2-java:$ARCH docker://argeo/argeo2-java:$ARCH
diff --git a/dist/containers/argeo2-lists b/dist/containers/argeo2-lists
deleted file mode 100755 (executable)
index 1c29810..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/sh
-
-# Multi-architecture manifests
-buildah rmi argeo/argeo2-java:latest
-buildah manifest create argeo/argeo2-java:latest docker://argeo/argeo2-java:x86_64 docker://argeo/argeo2-java:aarch64
-buildah manifest push argeo/argeo2-java:latest docker://argeo/argeo2-java:latest
-
-buildah rmi argeo/argeo2-tp:latest
-buildah manifest create argeo/argeo2-tp:latest docker://argeo/argeo2-tp:x86_64 docker://argeo/argeo2-tp:aarch64
-buildah manifest push argeo/argeo2-tp:latest docker://argeo/argeo2-tp:latest
-
-buildah rmi argeo/argeo2-node:latest
-buildah manifest create argeo/argeo2-node:latest docker://argeo/argeo2-node:x86_64 docker://argeo/argeo2-node:aarch64
-buildah manifest push argeo/argeo2-node:latest docker://argeo/argeo2-node:latest
-
-buildah rmi argeo/argeo2-builder:latest
-buildah manifest create argeo/argeo2-builder:latest docker://argeo/argeo2-builder:x86_64 docker://argeo/argeo2-builder:aarch64
-buildah manifest push argeo/argeo2-builder:latest docker://argeo/argeo2-builder:latest
-
diff --git a/dist/containers/argeo2-node b/dist/containers/argeo2-node
deleted file mode 100755 (executable)
index 8578ab9..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/buildah-metadata"
-container=$(buildah from argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH)
-
-# Override version
-#buildah copy $container argeo2-rpmfactory.repo /etc/yum.repos.d/
-#buildah copy $container /srv/rpmfactory/argeo-osgi-2/el7/ /srv/rpmfactory/argeo-osgi-2/el7/
-
-source "$(dirname "$0")/buildah-common"
-
-buildah config --label release="1" $container
-buildah config --label version="$VERSION_ARGEO_COMMONS" $container
-
-# Metadata
-buildah config --label name="argeo2-node" $container
-buildah config --label description="Argeo 2 Node" $container
-buildah config --label url=https://hub.docker.com/repository/docker/argeo/argeo2-node $container
-
-# Argeo Node
-buildah run $container -- microdnf install osgi-boot argeo-cms-e4-rap argeo-node
-buildah run $container -- microdnf clean all
-
-#buildah run $container -- rm -rf /srv/rpmfactory/argeo-osgi-2
-
-# Configuration
-buildah config --entrypoint '["/usr/sbin/argeoctl","start"]' $container
-buildah config --port 8080 $container
-
-buildah commit --rm --format docker $container argeo/argeo2-node:$VERSION_ARGEO_COMMONS-$ARCH
-buildah tag argeo/argeo2-node:$VERSION_ARGEO_COMMONS-$ARCH argeo/argeo2-node:$ARCH
-
-buildah push argeo/argeo2-node:$VERSION_ARGEO_COMMONS-$ARCH docker://argeo/argeo2-node:$VERSION_ARGEO_COMMONS-$ARCH
-buildah push argeo/argeo2-node:$ARCH docker://argeo/argeo2-node:$ARCH
diff --git a/dist/containers/argeo2-node-snapshots b/dist/containers/argeo2-node-snapshots
deleted file mode 100755 (executable)
index 54422b4..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/buildah-metadata"
-container=$(buildah from argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH)
-
-# Override version
-buildah copy $container argeo2-snapshots.repo /etc/yum.repos.d/
-
-source "$(dirname "$0")/buildah-common"
-
-#buildah config --label release="1" $container
-#buildah config --label version="$VERSION_ARGEO_COMMONS" $container
-
-# Metadata
-buildah config --label name="argeo2-node" $container
-buildah config --label description="Argeo 2 Node" $container
-buildah config --label url=https://hub.docker.com/repository/docker/argeo/argeo2-node $container
-
-# Argeo Node
-buildah run $container -- microdnf install osgi-boot argeo-cms-e4-rap argeo-node
-buildah run $container -- microdnf clean all
-
-# Override settings
-buildah copy $container dev-settings.sh /etc/argeo/settings.sh
-
-# Configuration
-buildah config --entrypoint '["/usr/sbin/argeoctl","start"]' $container
-buildah config --port 8080 $container
-
-buildah commit --rm --format docker $container argeo/argeo2-node:snapshots-$ARCH
-
-buildah push argeo/argeo2-node:snapshots-$ARCH docker://argeo/argeo2-node:snapshots-$ARCH
diff --git a/dist/containers/argeo2-rpmfactory.repo b/dist/containers/argeo2-rpmfactory.repo
deleted file mode 100644 (file)
index 00445b6..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[argeo2-rpmfactory]
-baseurl=file:///srv/rpmfactory/argeo-osgi-2/el7/
-gpgcheck=0
-enabled=1
diff --git a/dist/containers/argeo2-snapshots.repo b/dist/containers/argeo2-snapshots.repo
deleted file mode 100644 (file)
index 39ccba5..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-[argeo-osgi-staging]
-name=Argeo 2 OSGi (staging)
-baseurl=http://snapshots.argeo.org/rpm/testing/argeo-osgi-2/el7/
-gpgcheck=no
diff --git a/dist/containers/argeo2-tp b/dist/containers/argeo2-tp
deleted file mode 100755 (executable)
index a033ab0..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-
-source "$(dirname "$0")/buildah-metadata"
-container=$(buildah from argeo/argeo2-java:$ARCH)
-
-source "$(dirname "$0")/buildah-common"
-
-buildah config --label release="1" $container
-buildah config --label version="$VERSION_ARGEO_TP" $container
-
-# Metadata
-buildah config --label name="argeo2-tp" $container
-buildah config --label description="Argeo 2 OSGi Third Parties" $container
-buildah config --label url=https://hub.docker.com/repository/docker/argeo/argeo2-tp $container
-
-# Argeo
-buildah run $container -- rpm -Uvh http://repo.argeo.org/rpm/argeo2-release-latest-7.noarch.rpm
-# Argeo Third Parties
-buildah run $container -- microdnf install argeo-cms-e4-rap-tp osgi-boot-equinox
-buildah run $container -- microdnf clean all
-
-# Configuration
-buildah config --entrypoint '["java","-Dosgi.bundles=org.argeo.osgi.boot.jar@start","-Dosgi.configuration=/var/lib/argeo/state","-Dosgi.data=/var/lib/argeo/data","-jar","/usr/share/osgi/boot/org.eclipse.osgi.jar","-console","2323"]' $container
-buildah config --workingdir /var/lib/argeo $container
-buildah config --volume /var/lib/argeo $container
-
-buildah commit --rm --format docker $container argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH
-buildah tag argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH argeo/argeo2-tp:$ARCH
-
-buildah push argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH docker://argeo/argeo2-tp:$VERSION_ARGEO_TP-$ARCH
-buildah push argeo/argeo2-tp:$ARCH docker://argeo/argeo2-tp:$ARCH
diff --git a/dist/containers/buildah-common b/dist/containers/buildah-common
deleted file mode 100755 (executable)
index c912032..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/sh
-
-# Metadata
-buildah config --label maintainer="Mathieu Baudier, mbaudier at argeo.org" $container
-buildah config --created-by "Mathieu Baudier, mbaudier at argeo.org" $container
-buildah config --author "Argeo GmbH, http://www.argeo.org, contact at argeo.org" $container
-buildah config --label vendor="Argeo GmbH" $container
-
-# Remove Red Hat labels
-buildah config --label authoritative-source-url= $container
-buildah config --label com.redhat.build-host= $container
-buildah config --label com.redhat.component= $container
-buildah config --label com.redhat.license_terms= $container
-buildah config --label io.k8s.description= $container
-buildah config --label io.k8s.display-name= $container
-buildah config --label io.openshift.expose-services= $container
-buildah config --label io.openshift.tags= $container
-buildah config --label vcs-ref= $container
-buildah config --label vcs-type= $container
-buildah config --label summary= $container
-
diff --git a/dist/containers/buildah-login b/dist/containers/buildah-login
deleted file mode 100755 (executable)
index 171a869..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-echo Login to Red Hat registry registry.redhat.io
-podman login registry.redhat.io
-
-echo Login to Docker Hub registry docker.io
-podman login docker.io
-
diff --git a/dist/containers/buildah-metadata b/dist/containers/buildah-metadata
deleted file mode 100644 (file)
index 3908bb4..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-VERSION_ARGEO_COMMONS=2.1
-VERSION_ARGEO_TP=2.1
-
-VERSION_MAVEN=3.5
-VERSION_JAVA=11
-
-ARCH=`arch`
-echo Building for architecture $ARCH
diff --git a/dist/containers/dev-settings.sh b/dist/containers/dev-settings.sh
deleted file mode 100644 (file)
index 2983934..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-export LANG=en_US.utf8
-JAVA_OPTS="-ea -agentlib:jdwp=transport=dt_socket,server=y,address=*:8000,suspend=n -showversion -Xmx512m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7084 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
diff --git a/dist/containers/filtered/buildah-metadata b/dist/containers/filtered/buildah-metadata
deleted file mode 100644 (file)
index d0e0466..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#VERSION_ARGEO_COMMONS=2.1.84
-#VERSION_ARGEO_TP=${version.argeo-tp}
-
-#VERSION_MAVEN=3.5.4
-#VERSION_JAVA=11.0.5
-
-#ARCH=`arch`
-#echo Building for architecture $ARCH
diff --git a/dist/containers/maven.conf b/dist/containers/maven.conf
deleted file mode 100644 (file)
index e96cfc7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-JAVA_HOME=/usr/lib/jvm/java-11-openjdk
diff --git a/dist/containers/pom.xml b/dist/containers/pom.xml
deleted file mode 100644 (file)
index cc32425..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<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.1-SNAPSHOT</version>
-               <artifactId>dist</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>containers</artifactId>
-       <packaging>pom</packaging>
-       <name>Argeo OCI Containers</name>
-<!--   <build> -->
-<!--           <plugins> -->
-<!--                   <plugin> -->
-<!--                           <artifactId>maven-resources-plugin</artifactId> -->
-<!--                           <executions> -->
-<!--                                   <execution> -->
-<!--                                           <id>copy-resources</id> -->
-<!--                                           <phase>validate</phase> -->
-<!--                                           <goals> -->
-<!--                                                   <goal>copy-resources</goal> -->
-<!--                                           </goals> -->
-<!--                                           <configuration> -->
-<!--                                                   <outputDirectory>${basedir}</outputDirectory> -->
-<!--                                                   <resources> -->
-<!--                                                           <resource> -->
-<!--                                                                   <directory>filtered</directory> -->
-<!--                                                                   <filtering>true</filtering> -->
-<!--                                                           </resource> -->
-<!--                                                   </resources> -->
-<!--                                           </configuration> -->
-<!--                                   </execution> -->
-<!--                           </executions> -->
-<!--                   </plugin> -->
-<!--           </plugins> -->
-<!--   </build> -->
-</project>
diff --git a/dist/osgi-boot/assembly/osgi-boot.xml b/dist/osgi-boot/assembly/osgi-boot.xml
deleted file mode 100644 (file)
index adad22b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<assembly
-       xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
-       <id>dist</id>
-       <baseDirectory></baseDirectory>
-       <formats>
-               <format>zip</format>
-       </formats>
-       <fileSets>
-               <fileSet>
-                       <directory>base/bin</directory>
-                       <outputDirectory>bin</outputDirectory>
-                       <fileMode>0755</fileMode>
-                       <includes>
-                               <include>*</include>
-                       </includes>
-                       <excludes>
-                               <exclude>offline.sh</exclude>
-                       </excludes>
-               </fileSet>
-       </fileSets>
-       <dependencySets>
-               <dependencySet>
-                       <unpack>false</unpack>
-                       <outputFileNameMapping>${artifact.artifactId}.${artifact.extension}</outputFileNameMapping>
-                       <outputDirectory>share/osgi/boot</outputDirectory>
-                       <includes>
-                               <include>org.argeo.tp.equinox:org.eclipse.osgi</include>
-                               <include>org.argeo.commons:org.argeo.osgi.boot</include>
-                       </includes>
-               </dependencySet>
-       </dependencySets>
-</assembly>
\ No newline at end of file
diff --git a/dist/osgi-boot/base/bin/a2sh b/dist/osgi-boot/base/bin/a2sh
deleted file mode 100755 (executable)
index dce5463..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-BIN_DIR=`dirname $0`
-EQUINOX=$BIN_DIR/../share/osgi/boot/org.eclipse.osgi.jar
-OSGI_BOOT=$BIN_DIR/../share/osgi/boot/org.argeo.osgi.boot.jar
-
-/usr/bin/jshell --class-path "$EQUINOX:$OSGI_BOOT" $*
diff --git a/dist/osgi-boot/pom.xml b/dist/osgi-boot/pom.xml
deleted file mode 100644 (file)
index 575b4b4..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-<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.1-SNAPSHOT</version>
-               <artifactId>dist</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>osgi-boot</artifactId>
-       <packaging>pom</packaging>
-       <name>Commons Deployable OSGi Boot</name>
-       <!-- <properties> -->
-       <!-- <version.equinox>3.10.1.v20140909-1633</version.equinox> -->
-       <!-- </properties> -->
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.tp</groupId>
-                       <artifactId>argeo-tp</artifactId>
-                       <version>${version.argeo-tp}</version>
-               </dependency>
-
-               <!-- OSGi Boot (and Equinox) -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.osgi.boot</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-       <profiles>
-               <profile>
-                       <id>dist</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.apache.maven.plugins</groupId>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <configuration>
-                                                       <finalName>osgi-boot-${version.released}</finalName>
-                                                       <appendAssemblyId>false</appendAssemblyId>
-                                                       <descriptors>
-                                                               <descriptor>assembly/osgi-boot.xml</descriptor>
-                                                       </descriptors>
-                                               </configuration>
-                                               <executions>
-                                                       <execution>
-                                                               <id>assembly-base</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>single</goal>
-                                                               </goals>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-osgi-boot</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-osgi-boot${argeo.rpm.suffix}</name>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/bin</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>755</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/bin</location>
-                                                                                                       <includes>
-                                                                                                               <include>*</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi/boot</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <dependency>
-                                                                                               <stripVersion>true</stripVersion>
-                                                                                               <includes>
-                                                                                                       <include>org.argeo.commons:org.argeo.osgi.boot</include>
-                                                                                               </includes>
-                                                                                       </dependency>
-                                                                               </mapping>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi/boot</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <configuration>noreplace</configuration>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <sources>
-                                                                                               <source>
-                                                                                                       <location>rpm/usr/share/osgi/boot</location>
-                                                                                                       <includes>
-                                                                                                               <include>*.args</include>
-                                                                                                       </includes>
-                                                                                               </source>
-                                                                                       </sources>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                                       <requires>
-                                                                               <require>argeo-osgi-boot-equinox${argeo.rpm.suffix}</require>
-                                                                       </requires>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-               <profile>
-                       <id>rpmbuild-tp</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <groupId>org.codehaus.mojo</groupId>
-                                               <artifactId>rpm-maven-plugin</artifactId>
-                                               <executions>
-                                                       <execution>
-                                                               <id>rpm-osgi-boot-equinox</id>
-                                                               <phase>package</phase>
-                                                               <goals>
-                                                                       <goal>rpm</goal>
-                                                               </goals>
-                                                               <configuration>
-                                                                       <name>argeo-osgi-boot-equinox${argeo.rpm.suffix}</name>
-                                                                       <projversion>${version.argeo-tp}</projversion>
-                                                                       <release>${argeo.rpm.release.tp}</release>
-                                                                       <mappings>
-                                                                               <mapping>
-                                                                                       <directory>/usr/share/osgi/boot</directory>
-                                                                                       <username>root</username>
-                                                                                       <groupname>root</groupname>
-                                                                                       <filemode>644</filemode>
-                                                                                       <directoryIncluded>false</directoryIncluded>
-                                                                                       <dependency>
-                                                                                               <stripVersion>true</stripVersion>
-                                                                                               <includes>
-                                                                                                       <include>org.argeo.tp.equinox:org.eclipse.osgi</include>
-                                                                                               </includes>
-                                                                                       </dependency>
-                                                                               </mapping>
-                                                                       </mappings>
-                                                               </configuration>
-                                                       </execution>
-                                               </executions>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
diff --git a/dist/osgi-boot/rpm/usr/bin/a2sh b/dist/osgi-boot/rpm/usr/bin/a2sh
deleted file mode 100755 (executable)
index 07a27eb..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-export A2_HOME=$HOME/.a2
-if [ -d "$A2_HOME/share/osgi/boot" ]; then
-       PREFIX=$A2_HOME
-else
-       PREFIX=/usr
-fi
-
-EQUINOX=$PREFIX/share/osgi/boot/org.eclipse.osgi.jar
-OSGI_BOOT=$PREFIX/share/osgi/boot/org.argeo.osgi.boot.jar
-
-/usr/bin/jshell --class-path "$EQUINOX:$OSGI_BOOT" $*
diff --git a/dist/osgi-boot/rpm/usr/share/osgi/boot/framework.args b/dist/osgi-boot/rpm/usr/share/osgi/boot/framework.args
deleted file mode 100644 (file)
index 1c1d2fa..0000000
+++ /dev/null
@@ -1 +0,0 @@
--jar /usr/share/osgi/boot/org.eclipse.osgi.jar
\ No newline at end of file
diff --git a/dist/pom.xml b/dist/pom.xml
deleted file mode 100644 (file)
index f6b9917..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>dist</artifactId>
-       <name>Commons Deployable Distributions</name>
-       <packaging>pom</packaging>
-       <modules>
-               <module>osgi-boot</module>
-               <!-- <module>argeo-cli</module> -->
-               <module>argeo-node</module>
-               <module>containers</module>
-       </modules>
-       <build>
-               <plugins>
-                       <plugin>
-                               <groupId>org.codehaus.mojo</groupId>
-                               <artifactId>properties-maven-plugin</artifactId>
-                               <configuration>
-                                       <quiet>true</quiet>
-                                       <files>
-                                               <file>../../cnf/${version.context}.bnd</file>
-                                       </files>
-                               </configuration>
-                       </plugin>
-               </plugins>
-       </build>
-</project>
\ 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..ea95043
--- /dev/null
@@ -0,0 +1,17 @@
+Service-Component: OSGI-INF/homeRepository.xml,\
+OSGI-INF/userAdminWrapper.xml,\
+OSGI-INF/defaultCallbackHandler.xml
+Bundle-ActivationPolicy: lazy
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.swt.widgets;version="0.0.0",\
+org.eclipse.e4.ui.model.application.ui,\
+org.eclipse.e4.ui.model.application,\
+javax.jcr.nodetype,\
+org.argeo.cms,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.argeo.cms.swt.auth,\
+org.apache.jackrabbit.*;version="[2,3)",\
+javax.servlet.*;version="[3,5)",\
+*
diff --git a/eclipse/org.argeo.cms.e4/build.properties b/eclipse/org.argeo.cms.e4/build.properties
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/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..7c537ba
--- /dev/null
@@ -0,0 +1,12 @@
+Import-Package:\
+org.osgi.service.http;version=0.0.0,\
+org.osgi.service.http.whiteboard;version=0.0.0,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.argeo.cms.osgi,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component:\
+OSGI-INF/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/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..54c8804
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.cms.servlet;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+public class ServletHttpRequest implements RemoteAuthRequest {
+       private final HttpServletRequest request;
+
+       public ServletHttpRequest(HttpServletRequest request) {
+               Objects.requireNonNull(request);
+               this.request = request;
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               HttpSession httpSession = request.getSession(false);
+               if (httpSession == null)
+                       return null;
+               return new ServletHttpSession(httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               return new ServletHttpSession(request.getSession(true));
+       }
+
+       @Override
+       public Locale getLocale() {
+               return request.getLocale();
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return request.getAttribute(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               request.setAttribute(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               return request.getHeader(key);
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               return request.getRemoteAddr();
+       }
+
+       @Override
+       public int getLocalPort() {
+               return request.getLocalPort();
+       }
+
+       @Override
+       public int getRemotePort() {
+               return request.getRemotePort();
+       }
+}
diff --git a/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..29f820a
--- /dev/null
@@ -0,0 +1,8 @@
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.core.commands.common,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Bundle-ActivationPolicy: lazy
+                               
\ No newline at end of file
diff --git a/eclipse/org.argeo.cms.swt/build.properties b/eclipse/org.argeo.cms.swt/build.properties
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/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..701de28
--- /dev/null
@@ -0,0 +1,256 @@
+package org.argeo.cms.swt;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsStyle;
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsView;
+import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowData;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Widget;
+
+/** SWT utilities. */
+public class CmsSwtUtils {
+
+       /** Singleton. */
+       private CmsSwtUtils() {
+       }
+
+       public static CmsTheme getCmsTheme(Composite parent) {
+               CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName());
+               if (theme == null) {
+                       // find parent shell
+                       Shell topShell = parent.getShell();
+                       while (topShell.getParent() != null)
+                               topShell = (Shell) topShell.getParent();
+                       theme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+                       parent.setData(CmsTheme.class.getName(), theme);
+               }
+               return theme;
+       }
+
+       public static void registerCmsTheme(Shell shell, CmsTheme theme) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsTheme.class.getName()) != null) {
+                       CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName());
+                       throw new IllegalArgumentException(
+                                       "Theme " + registeredTheme.getThemeId() + " already registered in this shell");
+               }
+               topShell.setData(CmsTheme.class.getName(), theme);
+       }
+
+       public static CmsView getCmsView(Control parent) {
+               // find parent shell
+               Shell topShell = parent.getShell();
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               return (CmsView) topShell.getData(CmsView.class.getName());
+       }
+
+       public static void registerCmsView(Shell shell, CmsView view) {
+               // find parent shell
+               Shell topShell = shell;
+               while (topShell.getParent() != null)
+                       topShell = (Shell) topShell.getParent();
+               // check if already set
+               if (topShell.getData(CmsView.class.getName()) != null) {
+                       CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName());
+                       throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell");
+               }
+               shell.setData(CmsView.class.getName(), view);
+       }
+
+       /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */
+       public static void sendEventOnSelect(Control control, String topic, Map<String, Object> properties) {
+               SelectionListener listener = (Selected) (e) -> {
+                       getCmsView(control.getParent()).sendEvent(topic, properties);
+               };
+               if (control instanceof Button) {
+                       ((Button) control).addSelectionListener(listener);
+               } else
+                       throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported.");
+       }
+
+       /**
+        * Convenience method to sends an event via
+        * {@link CmsView#sendEvent(String, Map)}.
+        */
+       public static void sendEventOnSelect(Control control, String topic, String key, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(key, value);
+               sendEventOnSelect(control, topic, properties);
+       }
+
+       /*
+        * GRID LAYOUT
+        */
+       /** A {@link GridLayout} without any spacing and one column. */
+       public static GridLayout noSpaceGridLayout() {
+               return noSpaceGridLayout(new GridLayout());
+       }
+
+       /**
+        * A {@link GridLayout} without any spacing and multiple columns of unequal
+        * width.
+        */
+       public static GridLayout noSpaceGridLayout(int columns) {
+               return noSpaceGridLayout(new GridLayout(columns, false));
+       }
+
+       /** @return the same layout, with spaces removed. */
+       public static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static GridData fillAll() {
+               return new GridData(SWT.FILL, SWT.FILL, true, true);
+       }
+
+       public static GridData fillWidth() {
+               return grabWidth(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, true, false);
+       }
+
+       public static GridData fillHeight() {
+               return grabHeight(SWT.FILL, SWT.FILL);
+       }
+
+       public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) {
+               return new GridData(horizontalAlignment, horizontalAlignment, false, true);
+       }
+
+       /*
+        * ROW LAYOUT
+        */
+       /** @return the same layout, with margins removed. */
+       public static RowLayout noMarginsRowLayout(RowLayout rowLayout) {
+               rowLayout.marginTop = 0;
+               rowLayout.marginBottom = 0;
+               rowLayout.marginLeft = 0;
+               rowLayout.marginRight = 0;
+               return rowLayout;
+       }
+
+       public static RowLayout noMarginsRowLayout(int type) {
+               return noMarginsRowLayout(new RowLayout(type));
+       }
+
+       public static RowData rowData16px() {
+               return new RowData(16, 16);
+       }
+
+       /*
+        * FORM LAYOUT
+        */
+       public static FormData coverAll() {
+               FormData fdLabel = new FormData();
+               fdLabel.top = new FormAttachment(0, 0);
+               fdLabel.left = new FormAttachment(0, 0);
+               fdLabel.right = new FormAttachment(100, 0);
+               fdLabel.bottom = new FormAttachment(100, 0);
+               return fdLabel;
+       }
+
+       /*
+        * STYLING
+        */
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, String style) {
+               if (style == null)
+                       return widget;// does nothing
+               EclipseUiSpecificUtils.setStyleData(widget, style);
+               if (widget instanceof Control) {
+                       CmsView cmsView = getCmsView((Control) widget);
+                       if (cmsView != null)
+                               cmsView.applyStyles(widget);
+               }
+               return widget;
+       }
+
+       /** Style widget */
+       public static <T extends Widget> T style(T widget, CmsStyle style) {
+               return style(widget, style.style());
+       }
+
+       /** Enable markups on widget */
+       public static <T extends Widget> T markup(T widget) {
+               EclipseUiSpecificUtils.setMarkupData(widget);
+               return widget;
+       }
+
+       /** Disable markup validation. */
+       public static <T extends Widget> T disableMarkupValidation(T widget) {
+               EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget);
+               return widget;
+       }
+
+       /**
+        * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}.
+        * 
+        * @param widget the widget to style and to use in order to display text
+        * @param txt    the object to display via its <code>toString()</code> method.
+        *               This argument should not be null, but if it is null and
+        *               assertions are disabled "<null>" is displayed instead; if
+        *               assertions are enabled the call will fail.
+        * 
+        * @see markup
+        */
+       public static <T extends Widget> T text(T widget, Object txt) {
+               assert txt != null;
+               String str = txt != null ? txt.toString() : "<null>";
+               markup(widget);
+               if (widget instanceof Label)
+                       ((Label) widget).setText(str);
+               else if (widget instanceof Button)
+                       ((Button) widget).setText(str);
+               else if (widget instanceof Text)
+                       ((Text) widget).setText(str);
+               else
+                       throw new IllegalArgumentException("Unsupported widget type " + widget.getClass());
+               return widget;
+       }
+
+       /** A {@link Label} with markup activated. */
+       public static Label lbl(Composite parent, Object txt) {
+               return text(new Label(parent, SWT.NONE), txt);
+       }
+
+       /** A read-only {@link Text} whose content can be copy/pasted. */
+       public static Text txt(Composite parent, Object txt) {
+               return text(new Text(parent, SWT.NONE), txt);
+       }
+
+       /** Dispose all children of a Composite */
+       public static void clear(Composite composite) {
+               if (composite.isDisposed())
+                       return;
+               for (Control child : composite.getChildren())
+                       child.dispose();
+       }
+}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java
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..43e5739
--- /dev/null
@@ -0,0 +1,337 @@
+package org.argeo.cms.swt.auth;
+
+import static org.argeo.cms.CmsMsg.password;
+import static org.argeo.cms.CmsMsg.username;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class CmsLogin implements CmsStyles, CallbackHandler {
+       private final static CmsLog log = CmsLog.getLog(CmsLogin.class);
+
+       private Composite parent;
+       private Text usernameT, passwordT;
+       private Composite credentialsBlock;
+       private final SelectionListener loginSelectionListener;
+
+       private final Locale defaultLocale;
+       private LocaleChoice localeChoice = null;
+
+       private final CmsView cmsView;
+
+       // optional subject to be set explicitly
+       private Subject subject = null;
+
+       private CmsContext cmsContext;
+
+       public CmsLogin(CmsView cmsView, CmsContext cmsContext) {
+               this.cmsView = cmsView;
+               this.cmsContext = cmsContext;
+               if (this.cmsContext != null) {
+                       defaultLocale = this.cmsContext.getDefaultLocale();
+                       List<Locale> locales = this.cmsContext.getLocales();
+                       if (locales != null)
+                               localeChoice = new LocaleChoice(locales, defaultLocale);
+               } else {
+                       defaultLocale = Locale.getDefault();
+               }
+               loginSelectionListener = new SelectionListener() {
+                       private static final long serialVersionUID = -8832133363830973578L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               login();
+                       }
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                       }
+               };
+       }
+
+       protected boolean isAnonymous() {
+               return cmsView.isAnonymous();
+       }
+
+       public final void createUi(Composite parent) {
+               this.parent = parent;
+               createContents(parent);
+       }
+
+       protected void createContents(Composite parent) {
+               defaultCreateContents(parent);
+       }
+
+       public final void defaultCreateContents(Composite parent) {
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               Composite credentialsBlock = createCredentialsBlock(parent);
+               if (parent instanceof Shell) {
+                       credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+               }
+       }
+
+       public final Composite createCredentialsBlock(Composite parent) {
+               if (isAnonymous()) {
+                       return anonymousUi(parent);
+               } else {
+                       return userUi(parent);
+               }
+       }
+
+       public Composite getCredentialsBlock() {
+               return credentialsBlock;
+       }
+
+       protected Composite userUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+
+               specificUserUi(credentialsBlock);
+
+               Label l = new Label(credentialsBlock, SWT.NONE);
+               CmsSwtUtils.style(l, CMS_USER_MENU_ITEM);
+               l.setText(CmsMsg.logout.lead(locale));
+               GridData lData = CmsSwtUtils.fillWidth();
+               lData.widthHint = 120;
+               l.setLayoutData(lData);
+
+               l.addMouseListener(new MouseAdapter() {
+                       private static final long serialVersionUID = 6444395812777413116L;
+
+                       public void mouseDown(MouseEvent e) {
+                               logout();
+                       }
+               });
+               return credentialsBlock;
+       }
+
+       /** To be overridden */
+       protected void specificUserUi(Composite parent) {
+
+       }
+
+       protected Composite anonymousUi(Composite parent) {
+               Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale();
+               // We need a composite for the traversal
+               credentialsBlock = new Composite(parent, SWT.NONE);
+               credentialsBlock.setLayout(new GridLayout());
+               // credentialsBlock.setLayoutData(CmsUiUtils.fillAll());
+               CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
+
+               Integer textWidth = 120;
+               if (parent instanceof Shell)
+                       CmsSwtUtils.style(parent, CMS_USER_MENU);
+               // new Label(this, SWT.NONE).setText(CmsMsg.username.lead());
+               usernameT = new Text(credentialsBlock, SWT.BORDER);
+               usernameT.setMessage(username.lead(locale));
+               CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
+               GridData gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               usernameT.setLayoutData(gd);
+
+               // new Label(this, SWT.NONE).setText(CmsMsg.password.lead());
+               passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD);
+               passwordT.setMessage(password.lead(locale));
+               CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
+               gd = CmsSwtUtils.fillWidth();
+               gd.widthHint = textWidth;
+               passwordT.setLayoutData(gd);
+
+               TraverseListener tl = new TraverseListener() {
+                       private static final long serialVersionUID = -1158892811534971856L;
+
+                       public void keyTraversed(TraverseEvent e) {
+                               if (e.detail == SWT.TRAVERSE_RETURN)
+                                       login();
+                       }
+               };
+               credentialsBlock.addTraverseListener(tl);
+               usernameT.addTraverseListener(tl);
+               passwordT.addTraverseListener(tl);
+               parent.setTabList(new Control[] { credentialsBlock });
+               credentialsBlock.setTabList(new Control[] { usernameT, passwordT });
+
+               // Button
+               Button loginButton = new Button(credentialsBlock, SWT.PUSH);
+               loginButton.setText(CmsMsg.login.lead(locale));
+               loginButton.setLayoutData(CmsSwtUtils.fillWidth());
+               loginButton.addSelectionListener(loginSelectionListener);
+
+               extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener);
+               if (localeChoice != null)
+                       createLocalesBlock(credentialsBlock);
+               return credentialsBlock;
+       }
+
+       /**
+        * To be overridden in order to provide custom login button and other links.
+        */
+       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
+                       SelectionListener loginSelectionListener) {
+
+       }
+
+       protected void updateLocale(Locale selectedLocale) {
+               // save already entered values
+               String usernameStr = usernameT.getText();
+               char[] pwd = passwordT.getTextChars();
+
+               for (Control child : parent.getChildren())
+                       child.dispose();
+               createContents(parent);
+               if (parent.getParent() != null)
+                       parent.getParent().layout(true, true);
+               else
+                       parent.layout();
+               usernameT.setText(usernameStr);
+               passwordT.setTextChars(pwd);
+       }
+
+       protected Composite createLocalesBlock(final Composite parent) {
+               Composite c = new Composite(parent, SWT.NONE);
+               CmsSwtUtils.style(c, CMS_USER_MENU_ITEM);
+               c.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               c.setLayoutData(CmsSwtUtils.fillAll());
+
+               SelectionListener selectionListener = new SelectionAdapter() {
+                       private static final long serialVersionUID = 4891637813567806762L;
+
+                       public void widgetSelected(SelectionEvent event) {
+                               Button button = (Button) event.widget;
+                               if (button.getSelection()) {
+                                       localeChoice.setSelectedIndex((Integer) event.widget.getData());
+                                       updateLocale(localeChoice.getSelectedLocale());
+                               }
+                       };
+               };
+
+               List<Locale> locales = localeChoice.getLocales();
+               for (Integer i = 0; i < locales.size(); i++) {
+                       Locale locale = locales.get(i);
+                       Button button = new Button(c, SWT.RADIO);
+                       CmsSwtUtils.style(button, CMS_USER_MENU_ITEM);
+                       button.setData(i);
+                       button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")");
+                       // button.addListener(SWT.Selection, listener);
+                       button.addSelectionListener(selectionListener);
+                       if (i == localeChoice.getSelectedIndex())
+                               button.setSelection(true);
+               }
+               return c;
+       }
+
+       protected boolean login() {
+               // TODO use CmsVie in order to retrieve subject?
+               // Subject subject = cmsView.getLoginContext().getSubject();
+               // LoginContext loginContext = cmsView.getLoginContext();
+               try {
+                       //
+                       // LOGIN
+                       //
+                       // loginContext.logout();
+                       LoginContext loginContext;
+                       if (subject == null)
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this);
+                       else
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this);
+                       loginContext.login();
+                       cmsView.authChange(loginContext);
+                       return true;
+               } catch (LoginException e) {
+                       if (log.isTraceEnabled())
+                               log.warn("Login failed: " + e.getMessage(), e);
+                       else
+                               log.warn("Login failed: " + e.getMessage());
+
+                       try {
+                               Thread.sleep(3000);
+                       } catch (InterruptedException e2) {
+                               // silent
+                       }
+                       // ErrorFeedback.show("Login failed", e);
+                       return false;
+               }
+               // catch (LoginException e) {
+               // log.error("Cannot login", e);
+               // return false;
+               // }
+       }
+
+       protected void logout() {
+               cmsView.logout();
+               cmsView.navigateTo("~");
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks) {
+                       if (callback instanceof NameCallback && usernameT != null)
+                               ((NameCallback) callback).setName(usernameT.getText());
+                       else if (callback instanceof PasswordCallback && passwordT != null)
+                               ((PasswordCallback) callback).setPassword(passwordT.getTextChars());
+                       else if (callback instanceof RemoteAuthCallback) {
+                               ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+                               ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+                       } else if (callback instanceof LanguageCallback) {
+                               Locale toUse = null;
+                               if (localeChoice != null)
+                                       toUse = localeChoice.getSelectedLocale();
+                               else if (defaultLocale != null)
+                                       toUse = defaultLocale;
+
+                               if (toUse != null) {
+                                       ((LanguageCallback) callback).setLocale(toUse);
+                                       UiContext.setLocale(toUse);
+                               }
+
+                       }
+               }
+       }
+
+       public void setSubject(Subject subject) {
+               this.subject = subject;
+       }
+
+}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java
new file mode 100644 (file)
index 0000000..a4d7c07
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.swt.auth;
+
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsView;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class CmsLoginShell extends CmsLogin {
+       private final Shell shell;
+
+       public CmsLoginShell(CmsView cmsView, CmsContext cmsContext) {
+               super(cmsView, cmsContext);
+               shell = createShell();
+//             createUi(shell);
+       }
+
+       /** To be overridden. */
+       protected Shell createShell() {
+               Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM);
+               shell.setMaximized(true);
+               return shell;
+       }
+
+       /** To be overridden. */
+       public void open() {
+               CmsSwtUtils.style(shell, CMS_USER_MENU);
+               shell.open();
+       }
+
+       @Override
+       protected boolean login() {
+               boolean success = false;
+               try {
+                       success = super.login();
+                       return success;
+               } finally {
+                       if (success)
+                               closeShell();
+                       else {
+                               for (Control child : shell.getChildren())
+                                       child.dispose();
+                               createUi(shell);
+                               shell.layout();
+                               // TODO error message
+                       }
+               }
+       }
+
+       @Override
+       protected void logout() {
+               closeShell();
+               super.logout();
+       }
+
+       protected void closeShell() {
+               if (!shell.isDisposed()) {
+                       shell.close();
+                       shell.dispose();
+               }
+       }
+
+       public Shell getShell() {
+               return shell;
+       }
+
+       public void createUi() {
+               createUi(shell);
+       }
+}
diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java
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/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..065f0d1
--- /dev/null
@@ -0,0 +1,40 @@
+Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator
+
+Provide-Capability:\
+cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\
+cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\
+cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\
+osgi.service;objectClass="javax.jcr.Repository"
+
+Import-Package:\
+org.argeo.cms.servlet,\
+javax.jcr.security,\
+org.h2;resolution:=optional;version="[1,3)",\
+org.postgresql;version="[42,43)";resolution:=optional,\
+org.apache.commons.httpclient.cookie;resolution:=optional,\
+org.osgi.framework.namespace;version=0.0.0,\
+org.osgi.*;version=0.0.0,\
+org.osgi.service.http.whiteboard,\
+org.apache.jackrabbit.api.stats;version="[1,4)",\
+org.apache.jackrabbit.api;version="[1,4)",\
+org.apache.jackrabbit.commons;version="[1,4)",\
+org.apache.jackrabbit.spi;version="[1,4)",\
+org.apache.jackrabbit.spi2dav;version="[1,4)",\
+org.apache.jackrabbit.spi2davex;version="[1,4)",\
+org.apache.jackrabbit.webdav.jcr;version="[1,4)",\
+org.apache.jackrabbit.webdav.server;version="[1,4)",\
+org.apache.jackrabbit.webdav.simple;version="[1,4)",\
+org.apache.jackrabbit.*;version="[1,4)",\
+junit.*;resolution:=optional,\
+javax.servlet.*;version="[3,5)",\
+*
+
+Service-Component:\
+OSGI-INF/repositoryContextsFactory.xml,\
+OSGI-INF/jcrRepositoryFactory.xml,\
+OSGI-INF/jcrFsProvider.xml,\
+OSGI-INF/jcrDeployment.xml,\
+OSGI-INF/jcrServletContext.xml,\
+OSGI-INF/dataServletContext.xml,\
+OSGI-INF/filesServletContext.xml,\
+OSGI-INF/filesServlet.xml
diff --git a/jcr/org.argeo.cms.jcr/build.properties b/jcr/org.argeo.cms.jcr/build.properties
new file mode 100644 (file)
index 0000000..2b99dd7
--- /dev/null
@@ -0,0 +1,10 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
+additional.bundles = \
+org.apache.jackrabbit.data, \
+org.apache.jackrabbit.spi.commons,\
+
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java
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..4a28dca
--- /dev/null
@@ -0,0 +1,281 @@
+package org.argeo.cms.jcr;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.AuthPermission;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+
+/** Utilities related to Argeo model in JCR */
+public class CmsJcrUtils {
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#CN} in order to simplify it and protect against future
+        * API changes.
+        */
+       public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(CmsConstants.CN, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias,
+                                       e);
+               }
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+        * future API changes.
+        */
+       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) {
+               return getRepositoryByUri(repositoryFactory, uri, null);
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against
+        * future API changes.
+        */
+       public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(CmsConstants.LABELED_URI, uri);
+                       if (alias != null)
+                               parameters.put(CmsConstants.CN, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e);
+               }
+       }
+
+       /**
+        * Returns the home node of the user or null if none was found.
+        * 
+        * @param session  the session to use in order to perform the search, this can
+        *                 be a session with a different user ID than the one searched,
+        *                 typically when a system or admin session is used.
+        * @param username the username of the user
+        */
+       public static Node getUserHome(Session session, String username) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for user " + username, e);
+//             }
+
+               try {
+                       checkUserWorkspace(session, username);
+                       String homePath = getHomePath(username);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/home/" + username;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Cannot find home for user " + username, e);
+               }
+       }
+
+       private static String getHomePath(String username) {
+               LdapName dn;
+               try {
+                       dn = new LdapName(username);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Invalid name " + username, e);
+               }
+               String userId = dn.getRdn(dn.size() - 1).getValue().toString();
+               return '/' + userId;
+       }
+
+       private static void checkUserWorkspace(Session session, String username) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username);
+       }
+
+       /**
+        * Returns the home node of the user or null if none was found.
+        * 
+        * @param session   the session to use in order to perform the search, this can
+        *                  be a session with a different user ID than the one searched,
+        *                  typically when a system or admin session is used.
+        * @param groupname the name of the group
+        */
+       public static Node getGroupHome(Session session, String groupname) {
+//             try {
+//                     QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory();
+//                     Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel");
+//                     DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN);
+//                     StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn));
+//                     Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop);
+//                     Query query = qomf.createQuery(sel, constraint, null, null);
+//                     return querySingleNode(query);
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot find home for group " + cn, e);
+//             }
+
+               try {
+                       checkGroupWorkspace(session, groupname);
+                       String homePath = getGroupPath(groupname);
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       // legacy
+                       homePath = "/groups/" + groupname;
+                       if (session.itemExists(homePath))
+                               return session.getNode(homePath);
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new RuntimeException("Cannot find home for group " + groupname, e);
+               }
+
+       }
+
+       private static String getGroupPath(String groupname) {
+               String cn;
+               try {
+                       LdapName dn = new LdapName(groupname);
+                       cn = dn.getRdn(dn.size() - 1).getValue().toString();
+               } catch (InvalidNameException e) {
+                       cn = groupname;
+               }
+               return '/' + cn;
+       }
+
+       private static void checkGroupWorkspace(Session session, String groupname) {
+               String workspaceName = session.getWorkspace().getName();
+               if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName))
+                       throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname);
+       }
+
+       /**
+        * Queries one single node.
+        * 
+        * @return one single node or null if none was found
+        * @throws ArgeoJcrException if more than one node was found
+        */
+//     private static Node querySingleNode(Query query) {
+//             NodeIterator nodeIterator;
+//             try {
+//                     QueryResult queryResult = query.execute();
+//                     nodeIterator = queryResult.getNodes();
+//             } catch (RepositoryException e) {
+//                     throw new RuntimeException("Cannot execute query " + query, e);
+//             }
+//             Node node;
+//             if (nodeIterator.hasNext())
+//                     node = nodeIterator.nextNode();
+//             else
+//                     return null;
+//
+//             if (nodeIterator.hasNext())
+//                     throw new RuntimeException("Query returned more than one node.");
+//             return node;
+//     }
+
+       /** Returns the home node of the session user or null if none was found. */
+       public static Node getUserHome(Session session) {
+               String userID = session.getUserID();
+               return getUserHome(session, userID);
+       }
+
+       /** Whether this node is the home of the user of the underlying session. */
+       public static boolean isUserHome(Node node) {
+               try {
+                       String userID = node.getSession().getUserID();
+                       return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID);
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       /**
+        * Translate the path to this node into a path containing the name of the
+        * repository and the name of the workspace.
+        */
+       public static String getDataPath(String cn, Node node) {
+               assert node != null;
+               StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA);
+               try {
+                       return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName())
+                                       .append(node.getPath()).toString();
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e);
+               }
+       }
+
+       /**
+        * Translate the path to this node into a path containing the name of the
+        * repository and the name of the workspace.
+        */
+       public static String getDataPath(Node node) {
+               return getDataPath(CmsConstants.NODE, node);
+       }
+
+       /**
+        * Open a JCR session with full read/write rights on the data, as
+        * {@link CmsConstants#ROLE_USER_ADMIN}, using the
+        * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security hardened
+        * deployement, use {@link AuthPermission} on this login context.
+        */
+       public static Session openDataAdminSession(Repository repository, String workspaceName) {
+               LoginContext loginContext;
+               try {
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN);
+                       loginContext.login();
+               } catch (LoginException e1) {
+                       throw new RuntimeException("Could not login as data admin", e1);
+               } finally {
+               }
+
+               ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(CmsJcrUtils.class.getClassLoader());
+                       return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+
+                               @Override
+                               public Session run() {
+                                       try {
+                                               return repository.login(workspaceName);
+                                       } catch (NoSuchWorkspaceException e) {
+                                               throw new IllegalArgumentException("No workspace " + workspaceName + " available", e);
+                                       } catch (RepositoryException e) {
+                                               throw new RuntimeException("Cannot open data admin session", e);
+                                       }
+                               }
+
+                       });
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCl);
+               }
+       }
+
+       /** Singleton. */
+       private CmsJcrUtils() {
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java
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..16f5979
--- /dev/null
@@ -0,0 +1,476 @@
+package org.argeo.cms.jcr.internal;
+
+import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
+import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.callback.CallbackHandler;
+import javax.servlet.Servlet;
+
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoNames;
+import org.argeo.cms.internal.jcr.JcrInitUtils;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet;
+import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet;
+import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils;
+import org.argeo.cms.osgi.DataModelNamespace;
+import org.argeo.cms.security.CryptoKeyring;
+import org.argeo.cms.security.Keyring;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.LangUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** Implementation of a CMS deployment. */
+public class CmsJcrDeployment {
+       private final CmsLog log = CmsLog.getLog(getClass());
+       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+       private DataModels dataModels;
+       private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG;
+
+       private boolean argeoDataModelExtensionsAvailable = false;
+
+       // Readiness
+       private boolean nodeAvailable = false;
+
+       CmsDeployment cmsDeployment;
+
+       public void start() {
+               dataModels = new DataModels(bc);
+
+               ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
+               repoContextSt.open();
+               //KernelUtils.asyncOpen(repoContextSt);
+
+//             nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class);
+
+               JcrInitUtils.addToDeployment(cmsDeployment);
+
+       }
+
+       public void stop() {
+//             if (nodeHttp != null)
+//                     nodeHttp.destroy();
+
+               try {
+                       for (ServiceReference<JackrabbitLocalRepository> sr : bc
+                                       .getServiceReferences(JackrabbitLocalRepository.class, null)) {
+                               bc.getService(sr).destroy();
+                       }
+               } catch (InvalidSyntaxException e1) {
+                       log.error("Cannot clean repositories", e1);
+               }
+
+       }
+
+       public void setCmsDeployment(CmsDeployment cmsDeployment) {
+               this.cmsDeployment = cmsDeployment;
+       }
+
+       /**
+        * Checks whether the deployment is available according to expectations, and
+        * mark it as available.
+        */
+//     private synchronized void checkReadiness() {
+//             if (isAvailable())
+//                     return;
+//             if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) {
+//                     String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA);
+//                     String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA);
+//                     availableSince = System.currentTimeMillis();
+//                     long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+//                     String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
+//                     log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+//                     if (log.isDebugEnabled()) {
+//                             log.debug("## state: " + state);
+//                             if (data != null)
+//                                     log.debug("## data: " + data);
+//                     }
+//                     long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince();
+//                     long initDuration = System.currentTimeMillis() - begin;
+//                     if (log.isTraceEnabled())
+//                             log.trace("Kernel initialization took " + initDuration + "ms");
+//                     tributeToFreeSoftware(initDuration);
+//             }
+//     }
+
+       private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
+//             if (availableSince != null) {
+//                     throw new IllegalStateException("Deployment is already available");
+//             }
+
+               // home
+               prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
+
+               // init from backup
+//             if (deployConfig.isFirstInit()) {
+//                     Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
+//                     if (Files.exists(restorePath)) {
+//                             if (log.isDebugEnabled())
+//                                     log.debug("Found backup " + restorePath + ", restoring it...");
+//                             LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
+//                             KernelUtils.doAsDataAdmin(logicalRestore);
+//                             log.info("Restored backup from " + restorePath);
+//                     }
+//             }
+
+               // init from repository
+               Collection<ServiceReference<Repository>> initRepositorySr;
+               try {
+                       initRepositorySr = bc.getServiceReferences(Repository.class,
+                                       "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")");
+               } catch (InvalidSyntaxException e1) {
+                       throw new IllegalArgumentException(e1);
+               }
+               Iterator<ServiceReference<Repository>> it = initRepositorySr.iterator();
+               while (it.hasNext()) {
+                       ServiceReference<Repository> sr = it.next();
+                       Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name());
+                       Repository initRepository = bc.getService(sr);
+                       if (log.isDebugEnabled())
+                               log.debug("Found init repository " + labeledUri + ", copying it...");
+                       initFromRepository(deployedNodeRepository, initRepository);
+                       log.info("Node repository initialised from " + labeledUri);
+               }
+       }
+
+       /** Init from a (typically remote) repository. */
+       private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) {
+               Session initSession = null;
+               try {
+                       initSession = initRepository.login();
+                       workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) {
+                               if ("security".equals(workspaceName))
+                                       continue workspaces;
+                               if (log.isDebugEnabled())
+                                       log.debug("Copying workspace " + workspaceName + " from init repository...");
+                               long begin = System.currentTimeMillis();
+                               Session targetSession = null;
+                               Session sourceSession = null;
+                               try {
+                                       try {
+                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+                                       } catch (IllegalArgumentException e) {// no such workspace
+                                               Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null);
+                                               try {
+                                                       adminSession.getWorkspace().createWorkspace(workspaceName);
+                                               } finally {
+                                                       Jcr.logout(adminSession);
+                                               }
+                                               targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
+                                       }
+                                       sourceSession = initRepository.login(workspaceName);
+//                                     JcrUtils.copyWorkspaceXml(sourceSession, targetSession);
+                                       // TODO deal with referenceable nodes
+                                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
+                                       targetSession.save();
+                                       long duration = System.currentTimeMillis() - begin;
+                                       if (log.isDebugEnabled())
+                                               log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000)
+                                                               + " s");
+                               } catch (Exception e) {
+                                       log.error("Cannot copy workspace " + workspaceName + " from init repository.", e);
+                               } finally {
+                                       Jcr.logout(sourceSession);
+                                       Jcr.logout(targetSession);
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               } finally {
+                       Jcr.logout(initSession);
+               }
+       }
+
+       private void prepareHomeRepository(RepositoryImpl deployedRepository) {
+               Session adminSession = KernelUtils.openAdminSession(deployedRepository);
+               try {
+                       argeoDataModelExtensionsAvailable = Arrays
+                                       .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs())
+                                       .contains(ArgeoNames.ARGEO_NAMESPACE);
+               } catch (RepositoryException e) {
+                       log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e);
+                       argeoDataModelExtensionsAvailable = false;
+               } finally {
+                       JcrUtils.logoutQuietly(adminSession);
+               }
+
+               // Publish home with the highest service ranking
+               Hashtable<String, Object> regProps = new Hashtable<>();
+               regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY);
+               regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+               Repository egoRepository = new EgoRepository(deployedRepository, false);
+               bc.registerService(Repository.class, egoRepository, regProps);
+               registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository);
+
+               // Keyring only if Argeo extensions are available
+               if (argeoDataModelExtensionsAvailable) {
+                       new ServiceTracker<CallbackHandler, CallbackHandler>(bc, CallbackHandler.class, null) {
+
+                               @Override
+                               public CallbackHandler addingService(ServiceReference<CallbackHandler> reference) {
+                                       NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository);
+                                       CallbackHandler callbackHandler = bc.getService(reference);
+                                       nodeKeyring.setDefaultCallbackHandler(callbackHandler);
+                                       bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class),
+                                                       nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID));
+                                       return callbackHandler;
+                               }
+
+                       }.open();
+               }
+       }
+
+       /** Session is logged out. */
+       private void prepareDataModel(String cn, Repository repository, List<String> publishAsLocalRepo) {
+               Session adminSession = KernelUtils.openAdminSession(repository);
+               try {
+                       Set<String> processed = new HashSet<String>();
+                       bundles: for (Bundle bundle : bc.getBundles()) {
+                               BundleWiring wiring = bundle.adapt(BundleWiring.class);
+                               if (wiring == null)
+                                       continue bundles;
+                               if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models
+                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+                               else {
+                                       List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+                                       for (BundleCapability capability : capabilities) {
+                                               String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME);
+                                               if (dataModelName.equals(cn))// process only own data model
+                                                       processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo);
+                                       }
+                               }
+                       }
+               } finally {
+                       JcrUtils.logoutQuietly(adminSession);
+               }
+       }
+
+       private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set<String> processed,
+                       boolean importListedAbstractModels, List<String> publishAsLocalRepo) {
+               // recursively process requirements first
+               List<BundleWire> requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
+               for (BundleWire wire : requiredWires) {
+                       processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo);
+               }
+
+               List<BundleCapability> capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
+               capabilities: for (BundleCapability capability : capabilities) {
+                       if (!importListedAbstractModels
+                                       && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) {
+                               continue capabilities;
+                       }
+                       boolean publish = registerDataModelCapability(cn, adminSession, capability, processed);
+                       if (publish)
+                               publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME));
+               }
+       }
+
+       private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability,
+                       Set<String> processed) {
+               Map<String, Object> attrs = capability.getAttributes();
+               String name = (String) attrs.get(DataModelNamespace.NAME);
+               if (processed.contains(name)) {
+                       if (log.isTraceEnabled())
+                               log.trace("Data model " + name + " has already been processed");
+                       return false;
+               }
+
+               // CND
+               String path = (String) attrs.get(DataModelNamespace.CND);
+               if (path != null) {
+                       File dataModel = bc.getBundle().getDataFile("dataModels/" + path);
+                       if (!dataModel.exists()) {
+                               URL url = capability.getRevision().getBundle().getResource(path);
+                               if (url == null)
+                                       throw new IllegalArgumentException("No data model '" + name + "' found under path " + path);
+                               try (Reader reader = new InputStreamReader(url.openStream())) {
+                                       CndImporter.registerNodeTypes(reader, adminSession, true);
+                                       processed.add(name);
+                                       dataModel.getParentFile().mkdirs();
+                                       dataModel.createNewFile();
+                                       if (log.isDebugEnabled())
+                                               log.debug("Registered CND " + url);
+                               } catch (Exception e) {
+                                       log.error("Cannot import CND " + url, e);
+                               }
+                       }
+               }
+
+               if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)))
+                       return false;
+               // Non abstract
+               boolean isStandalone = isStandalone(name);
+               boolean publishLocalRepo;
+               if (isStandalone && name.equals(cn))// includes the node itself
+                       publishLocalRepo = true;
+               else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY))
+                       publishLocalRepo = true;
+               else
+                       publishLocalRepo = false;
+
+               return publishLocalRepo;
+       }
+
+       boolean isStandalone(String dataModelName) {
+               return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
+       }
+
+       private void publishLocalRepo(String dataModelName, Repository repository) {
+               Hashtable<String, Object> properties = new Hashtable<>();
+               properties.put(CmsConstants.CN, dataModelName);
+               LocalRepository localRepository;
+               String[] classes;
+               if (repository instanceof RepositoryImpl) {
+                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
+                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
+                                       JackrabbitLocalRepository.class.getName() };
+               } else {
+                       localRepository = new LocalRepository(repository, dataModelName);
+                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
+               }
+               bc.registerService(classes, localRepository, properties);
+
+               // TODO make it configurable
+               registerRepositoryServlets(dataModelName, localRepository);
+               if (log.isTraceEnabled())
+                       log.trace("Published data model " + dataModelName);
+       }
+
+//     @Override
+//     public synchronized Long getAvailableSince() {
+//             return availableSince;
+//     }
+//
+//     public synchronized boolean isAvailable() {
+//             return availableSince != null;
+//     }
+
+       protected void registerRepositoryServlets(String alias, Repository repository) {
+               registerRemotingServlet(alias, repository);
+               registerWebdavServlet(alias, repository);
+       }
+
+       protected void registerWebdavServlet(String alias, Repository repository) {
+               CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
+               Hashtable<String, String> ip = new Hashtable<>();
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+                               "/" + alias);
+
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")");
+               bc.registerService(Servlet.class, webdavServlet, ip);
+       }
+
+       protected void registerRemotingServlet(String alias, Repository repository) {
+               CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository);
+               Hashtable<String, String> ip = new Hashtable<>();
+               ip.put(CmsConstants.CN, alias);
+               // Properties ip = new Properties();
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
+                               "/" + alias);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER,
+                               "Negotiate");
+
+               // Looks like a bug in Jackrabbit remoting init
+               Path tmpDir;
+               try {
+                       tmpDir = Files.createTempDirectory("remoting_" + alias);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot create temp directory for remoting servlet", e);
+               }
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY,
+                               "remoting_" + alias);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG,
+                               JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS);
+               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
+
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
+               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
+                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")");
+               bc.registerService(Servlet.class, remotingServlet, ip);
+       }
+
+       private class RepositoryContextStc extends ServiceTracker<RepositoryContext, RepositoryContext> {
+
+               public RepositoryContextStc() {
+                       super(bc, RepositoryContext.class, null);
+               }
+
+               @Override
+               public RepositoryContext addingService(ServiceReference<RepositoryContext> reference) {
+                       RepositoryContext repoContext = bc.getService(reference);
+                       String cn = (String) reference.getProperty(CmsConstants.CN);
+                       if (cn != null) {
+                               List<String> publishAsLocalRepo = new ArrayList<>();
+                               if (cn.equals(CmsConstants.NODE_REPOSITORY)) {
+//                                     JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
+                                       prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
+                                       // TODO separate home repository
+                                       prepareHomeRepository(repoContext.getRepository());
+                                       registerRepositoryServlets(cn, repoContext.getRepository());
+                                       nodeAvailable = true;
+//                                     checkReadiness();
+                               } else {
+                                       prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo);
+                               }
+                               // Publish all at once, so that bundles with multiple CNDs are consistent
+                               for (String dataModelName : publishAsLocalRepo)
+                                       publishLocalRepo(dataModelName, repoContext.getRepository());
+                       }
+                       return repoContext;
+               }
+
+               @Override
+               public void modifiedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
+               @Override
+               public void removedService(ServiceReference<RepositoryContext> reference, RepositoryContext service) {
+               }
+
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java
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..4e067ee
--- /dev/null
@@ -0,0 +1,175 @@
+package org.argeo.cms.jcr.internal.servlet;
+
+import java.io.Serializable;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.jackrabbit.server.SessionProvider;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.jcr.JcrUtils;
+
+/**
+ * Implements an open session in view patter: a new JCR session is created for
+ * each request
+ */
+public class CmsSessionProvider implements SessionProvider, Serializable {
+       private static final long serialVersionUID = -1358136599534938466L;
+
+       private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class);
+
+       private final String alias;
+
+       private LinkedHashMap<Session, CmsDataSession> cmsSessions = new LinkedHashMap<>();
+
+       public CmsSessionProvider(String alias) {
+               this.alias = alias;
+       }
+
+       public Session getSession(HttpServletRequest request, Repository rep, String workspace)
+                       throws javax.jcr.LoginException, ServletException, RepositoryException {
+
+               // a client is scanning parent URLs.
+//             if (workspace == null)
+//                     return null;
+
+               CmsSession cmsSession = RemoteAuthUtils.getCmsSession(new ServletHttpRequest(request));
+               // CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request);
+               if (log.isTraceEnabled()) {
+                       log.trace("Get JCR session from " + cmsSession);
+               }
+               if (cmsSession == null)
+                       throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
+               CmsDataSession cmsDataSession = new CmsDataSession(cmsSession);
+               Session session = cmsDataSession.getDataSession(alias, workspace, rep);
+               cmsSessions.put(session, cmsDataSession);
+               return session;
+       }
+
+       public void releaseSession(Session session) {
+//             JcrUtils.logoutQuietly(session);
+               if (cmsSessions.containsKey(session)) {
+                       CmsDataSession cmsDataSession = cmsSessions.get(session);
+                       cmsDataSession.releaseDataSession(alias, session);
+               } else {
+                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       static class CmsDataSession {
+               private CmsSession cmsSession;
+
+               private Map<String, Session> dataSessions = new HashMap<>();
+               private Set<String> dataSessionsInUse = new HashSet<>();
+               private Set<Session> additionalDataSessions = new HashSet<>();
+
+               private CmsDataSession(CmsSession cmsSession) {
+                       this.cmsSession = cmsSession;
+                       cmsSession.addOnCloseCallback((sess) -> close());
+               }
+
+               public Session newDataSession(String cn, String workspace, Repository repository) {
+                       checkValid();
+                       return login(repository, workspace);
+               }
+
+               public synchronized Session getDataSession(String cn, String workspace, Repository repository) {
+                       checkValid();
+                       // FIXME make it more robust
+                       if (workspace == null)
+                               workspace = CmsConstants.SYS_WORKSPACE;
+                       String path = cn + '/' + workspace;
+                       if (dataSessionsInUse.contains(path)) {
+                               try {
+                                       wait(1000);
+                                       if (dataSessionsInUse.contains(path)) {
+                                               Session session = login(repository, workspace);
+                                               additionalDataSessions.add(session);
+                                               if (log.isTraceEnabled())
+                                                       log.trace("Additional data session " + path + " for " + cmsSession.getUserDn());
+                                               return session;
+                                       }
+                               } catch (InterruptedException e) {
+                                       // silent
+                               }
+                       }
+
+                       Session session = null;
+                       if (dataSessions.containsKey(path)) {
+                               session = dataSessions.get(path);
+                       } else {
+                               session = login(repository, workspace);
+                               dataSessions.put(path, session);
+                               if (log.isTraceEnabled())
+                                       log.trace("New data session " + path + " for " + cmsSession.getUserDn());
+                       }
+                       dataSessionsInUse.add(path);
+                       return session;
+               }
+
+               private Session login(Repository repository, String workspace) {
+                       try {
+                               return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction<Session>() {
+                                       @Override
+                                       public Session run() throws Exception {
+                                               return repository.login(workspace);
+                                       }
+                               });
+                       } catch (PrivilegedActionException e) {
+                               throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e);
+                       }
+               }
+
+               public synchronized void releaseDataSession(String cn, Session session) {
+                       if (additionalDataSessions.contains(session)) {
+                               JcrUtils.logoutQuietly(session);
+                               additionalDataSessions.remove(session);
+                               if (log.isTraceEnabled())
+                                       log.trace("Remove additional data session " + session);
+                               return;
+                       }
+                       String path = cn + '/' + session.getWorkspace().getName();
+                       if (!dataSessionsInUse.contains(path))
+                               log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn());
+                       dataSessionsInUse.remove(path);
+                       Session registeredSession = dataSessions.get(path);
+                       if (session != registeredSession)
+                               log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn());
+                       if (log.isTraceEnabled())
+                               log.trace("Released data session " + session + " for " + path);
+                       notifyAll();
+               }
+
+               private void checkValid() {
+                       if (!cmsSession.isValid())
+                               throw new IllegalStateException(
+                                               "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd());
+               }
+
+               protected void close() {
+                       synchronized (this) {
+                               // TODO check data session in use ?
+                               for (String path : dataSessions.keySet())
+                                       JcrUtils.logoutQuietly(dataSessions.get(path));
+                               for (Session session : additionalDataSessions)
+                                       JcrUtils.logoutQuietly(session);
+                       }
+               }
+       }
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java
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..7d86af2
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.jackrabbit.client;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+
+/**
+ * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying
+ * {@link HttpClientContext}.
+ */
+public class ClientDavexRepositoryService extends RepositoryServiceImpl {
+
+       public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig)
+                       throws RepositoryException {
+               super(jcrServerURI, batchReadConfig);
+       }
+       
+       
+
+//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize, ConnectionOptions connectionOptions)
+//                     throws RepositoryException {
+//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, connectionOptions);
+//             // TODO Auto-generated constructor stub
+//     }
+
+
+
+//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections)
+//                     throws RepositoryException {
+//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections);
+//     }
+//
+//     public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName,
+//                     BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException {
+//             super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize);
+//     }
+
+       @Override
+       protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+               HttpClientContext result = HttpClientContext.create();
+               result.setAuthCache(new NonSerialBasicAuthCache());
+               return result;
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java
new file mode 100644 (file)
index 0000000..2af0835
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.jackrabbit.client;
+
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+
+/**
+ * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a
+ * {@link ClientDavexRepositoryService}.
+ */
+public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory {
+       @Override
+       public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
+               // retrieve the repository uri
+               String uri;
+               if (parameters == null) {
+                       uri = System.getProperty(PARAM_REPOSITORY_URI);
+               } else {
+                       Object repoUri = parameters.get(PARAM_REPOSITORY_URI);
+                       uri = (repoUri == null) ? null : repoUri.toString();
+               }
+               if (uri == null) {
+                       uri = DEFAULT_REPOSITORY_URI;
+               }
+
+               // load other optional configuration parameters
+               BatchReadConfig brc = null;
+               int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
+               int maximumHttpConnections = 0;
+
+               // since JCR-4120 the default workspace name is no longer set to 'default'
+               // note: if running with JCR Server < 1.5 a default workspace name must
+               // therefore be configured
+               String workspaceNameDefault = null;
+
+               if (parameters != null) {
+                       // batchRead config
+                       Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
+                       if (param != null && param instanceof BatchReadConfig) {
+                               brc = (BatchReadConfig) param;
+                       }
+
+                       // itemCache size config
+                       param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE);
+                       if (param != null) {
+                               try {
+                                       itemInfoCacheSize = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // ignore, use default
+                               }
+                       }
+
+                       // max connections config
+                       param = parameters.get(PARAM_MAX_CONNECTIONS);
+                       if (param != null) {
+                               try {
+                                       maximumHttpConnections = Integer.parseInt(param.toString());
+                               } catch (NumberFormatException e) {
+                                       // using default
+                               }
+                       }
+
+                       param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
+                       if (param != null) {
+                               workspaceNameDefault = param.toString();
+                       }
+               }
+
+               // FIXME adapt to changes in Jackrabbit
+//             if (maximumHttpConnections > 0) {
+//                     return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize,
+//                                     maximumHttpConnections);
+//             } else {
+//                     return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize);
+//             }
+               return null;
+       }
+
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java
new file mode 100644 (file)
index 0000000..3a122f1
--- /dev/null
@@ -0,0 +1,127 @@
+package org.argeo.jackrabbit.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+import javax.jcr.Session;
+
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory;
+import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.RepositoryServiceFactory;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
+import org.apache.jackrabbit.spi2davex.BatchReadConfig;
+import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl;
+import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory;
+import org.argeo.jcr.JcrUtils;
+
+/** Minimal client to test JCR DAVEX connectivity. */
+public class JackrabbitClient {
+       final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+       final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri";
+       final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+
+       public static void main(String[] args) {
+               String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0];
+               String workspace = args.length < 2 ? "home" : args[1];
+
+               Repository repository = null;
+               Session session = null;
+
+               URI uri;
+               try {
+                       uri = new URI(repoUri);
+               } catch (URISyntaxException e1) {
+                       throw new IllegalArgumentException(e1);
+               }
+
+               if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) {
+
+                       RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() {
+                               @SuppressWarnings("rawtypes")
+                               public Repository getRepository(Map parameters) throws RepositoryException {
+                                       RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() {
+
+                                               @Override
+                                               public RepositoryService createRepositoryService(Map<?, ?> parameters)
+                                                               throws RepositoryException {
+                                                       Object uri = parameters.get(JACKRABBIT_DAVEX_URI);
+                                                       Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE);
+                                                       BatchReadConfig brc = null;
+                                                       // FIXME adapt to change in Jackrabbit
+//                                                     return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc,
+//                                                                     ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) {
+//
+//                                                             @Override
+//                                                             protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException {
+//                                                                     HttpClientContext result = HttpClientContext.create();
+//                                                                     result.setAuthCache(new NonSerialBasicAuthCache());
+//                                                                     return result;
+//                                                             }
+//
+//                                                     };
+                                                       return null;
+                                               }
+                                       };
+                                       return RepositoryImpl.create(
+                                                       new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters));
+                               }
+                       };
+                       Map<String, String> params = new HashMap<String, String>();
+                       params.put(JACKRABBIT_DAVEX_URI, repoUri.toString());
+                       // FIXME make it configurable
+                       params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
+
+                       try {
+                               repository = repositoryFactory.getRepository(params);
+                               if (repository != null)
+                                       session = repository.login(workspace);
+                               else
+                                       throw new IllegalArgumentException("Repository " + repoUri + " not found");
+                       } catch (RepositoryException e) {
+                               e.printStackTrace();
+                       }
+
+               } else {
+                       Path path = Paths.get(uri.getPath());
+               }
+
+               try {
+                       Node rootNode = session.getRootNode();
+                       NodeIterator nit = rootNode.getNodes();
+                       while (nit.hasNext()) {
+                               System.out.println(nit.nextNode().getPath());
+                       }
+
+                       Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir");
+                       System.out.println("Created folder " + newNode.getPath());
+                       Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes());
+                       System.out.println("Created file " + newFile.getPath());
+                       try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) {
+                               System.out.println("Read " + reader.readLine());
+                       } catch (IOException e) {
+                               e.printStackTrace();
+                       }
+                       newNode.getParent().remove();
+                       System.out.println("Removed new nodes");
+               } catch (RepositoryException e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java
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/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..1936f23
--- /dev/null
@@ -0,0 +1,994 @@
+package org.argeo.jcr;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.Row;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+import javax.jcr.version.VersionManager;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Utility class whose purpose is to make using JCR less verbose by
+ * systematically using unchecked exceptions and returning <code>null</code>
+ * when something is not found. This is especially useful when writing user
+ * interfaces (such as with SWT) where listeners and callbacks expect unchecked
+ * exceptions. Loosely inspired by Java's <code>Files</code> singleton.
+ */
+public class Jcr {
+       /**
+        * The name of a node which will be serialized as XML text, as per section 7.3.1
+        * of the JCR 2.0 specifications.
+        */
+       public final static String JCR_XMLTEXT = "jcr:xmltext";
+       /**
+        * The name of a property which will be serialized as XML text, as per section
+        * 7.3.1 of the JCR 2.0 specifications.
+        */
+       public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters";
+       /**
+        * <code>jcr:name</code>, when used in another context than
+        * {@link Property#JCR_NAME}, typically to name a node rather than a property.
+        */
+       public final static String JCR_NAME = "jcr:name";
+       /**
+        * <code>jcr:path</code>, when used in another context than
+        * {@link Property#JCR_PATH}, typically to name a node rather than a property.
+        */
+       public final static String JCR_PATH = "jcr:path";
+       /**
+        * <code>jcr:primaryType</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_PRIMARY_TYPE}.
+        */
+       public final static String JCR_PRIMARY_TYPE = "jcr:primaryType";
+       /**
+        * <code>jcr:mixinTypes</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_MIXIN_TYPES}.
+        */
+       public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes";
+       /**
+        * <code>jcr:uuid</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_UUID}.
+        */
+       public final static String JCR_UUID = "jcr:uuid";
+       /**
+        * <code>jcr:created</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_CREATED}.
+        */
+       public final static String JCR_CREATED = "jcr:created";
+       /**
+        * <code>jcr:createdBy</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_CREATED_BY}.
+        */
+       public final static String JCR_CREATED_BY = "jcr:createdBy";
+       /**
+        * <code>jcr:lastModified</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_LAST_MODIFIED}.
+        */
+       public final static String JCR_LAST_MODIFIED = "jcr:lastModified";
+       /**
+        * <code>jcr:lastModifiedBy</code> with prefix instead of namespace (as in
+        * {@link Property#JCR_LAST_MODIFIED_BY}.
+        */
+       public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy";
+
+       /**
+        * @see Node#isNodeType(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean isNodeType(Node node, String nodeTypeName) {
+               try {
+                       return node.isNodeType(nodeTypeName);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e);
+               }
+       }
+
+       /**
+        * @see Node#hasNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean hasNodes(Node node) {
+               try {
+                       return node.hasNodes();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get whether " + node + " has children.", e);
+               }
+       }
+
+       /**
+        * @see Node#getParent()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getParent(Node node) {
+               try {
+                       return isRoot(node) ? null : node.getParent();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get parent of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getParent()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getParentPath(Node node) {
+               return getPath(getParent(node));
+       }
+
+       /**
+        * Whether this node is the root node.
+        * 
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static boolean isRoot(Node node) {
+               try {
+                       return node.getDepth() == 0;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get depth of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getPath()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getPath(Node node) {
+               try {
+                       return node.getPath();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get path of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getSession()
+        * @see Session#getWorkspace()
+        * @see Workspace#getName()
+        */
+       public static String getWorkspaceName(Node node) {
+               return session(node).getWorkspace().getName();
+       }
+
+       /**
+        * @see Node#getIdentifier()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getIdentifier(Node node) {
+               try {
+                       return node.getIdentifier();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get identifier of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getName()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getName(Node node) {
+               try {
+                       return node.getName();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get name of " + node, e);
+               }
+       }
+
+       /**
+        * Returns the node name with its current index (useful for re-ordering).
+        * 
+        * @see Node#getName()
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String getIndexedName(Node node) {
+               try {
+                       return node.getName() + "[" + node.getIndex() + "]";
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get name of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getProperty(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Property getProperty(Node node, String property) {
+               try {
+                       if (node.hasProperty(property))
+                               return node.getProperty(property);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get property " + property + " of " + node, e);
+               }
+       }
+
+       /**
+        * @see Node#getIndex()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static int getIndex(Node node) {
+               try {
+                       return node.getIndex();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get index of " + node, e);
+               }
+       }
+
+       /**
+        * If node has mixin {@link NodeType#MIX_TITLE}, return
+        * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}.
+        */
+       public static String getTitle(Node node) {
+               if (Jcr.isNodeType(node, NodeType.MIX_TITLE))
+                       return get(node, Property.JCR_TITLE);
+               else
+                       return Jcr.getName(node);
+       }
+
+       /** Accesses a {@link NodeIterator} as an {@link Iterable}. */
+       @SuppressWarnings("unchecked")
+       public static Iterable<Node> iterate(NodeIterator nodeIterator) {
+               return new Iterable<Node>() {
+
+                       @Override
+                       public Iterator<Node> iterator() {
+                               return nodeIterator;
+                       }
+               };
+       }
+
+       /**
+        * @return the children as an {@link Iterable} for use in for-each llops.
+        * @see Node#getNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Iterable<Node> nodes(Node node) {
+               try {
+                       return iterate(node.getNodes());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get children of " + node, e);
+               }
+       }
+
+       /**
+        * @return the children as a (possibly empty) {@link List}.
+        * @see Node#getNodes()
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static List<Node> getNodes(Node node) {
+               List<Node> nodes = new ArrayList<>();
+               try {
+                       if (node.hasNodes()) {
+                               NodeIterator nit = node.getNodes();
+                               while (nit.hasNext())
+                                       nodes.add(nit.nextNode());
+                               return nodes;
+                       } else
+                               return nodes;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get children of " + node, e);
+               }
+       }
+
+       /**
+        * @return the child or <code>null</node> if not found
+        * @see Node#getNode(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNode(Node node, String child) {
+               try {
+                       if (node.hasNode(child))
+                               return node.getNode(child);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get child of " + node, e);
+               }
+       }
+
+       /**
+        * @return the node at this path or <code>null</node> if not found
+        * @see Session#getNode(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNode(Session session, String path) {
+               try {
+                       if (session.nodeExists(path))
+                               return session.getNode(path);
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get node " + path, e);
+               }
+       }
+
+       /**
+        * Add a node to this parent, setting its primary type and its mixins.
+        * 
+        * @param parent      the parent node
+        * @param name        the name of the node, if <code>null</code>, the primary
+        *                    type will be used (typically for XML structures)
+        * @param primaryType the primary type, if <code>null</code>
+        *                    {@link NodeType#NT_UNSTRUCTURED} will be used.
+        * @param mixins      the mixins
+        * @return the created node
+        * @see Node#addNode(String, String)
+        * @see Node#addMixin(String)
+        */
+       public static Node addNode(Node parent, String name, String primaryType, String... mixins) {
+               if (name == null && primaryType == null)
+                       throw new IllegalArgumentException("Both node name and primary type cannot be null");
+               try {
+                       Node newNode = parent.addNode(name == null ? primaryType : name,
+                                       primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType);
+                       for (String mixin : mixins) {
+                               newNode.addMixin(mixin);
+                       }
+                       return newNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
+               }
+       }
+
+       /**
+        * Add an {@link NodeType#NT_BASE} node to this parent.
+        * 
+        * @param parent the parent node
+        * @param name   the name of the node, cannot be <code>null</code>
+        * @return the created node
+        * 
+        * @see Node#addNode(String)
+        */
+       public static Node addNode(Node parent, String name) {
+               if (name == null)
+                       throw new IllegalArgumentException("Node name cannot be null");
+               try {
+                       Node newNode = parent.addNode(name);
+                       return newNode;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add node " + name + " to " + parent, e);
+               }
+       }
+
+       /**
+        * Add mixins to a node.
+        * 
+        * @param node   the node
+        * @param mixins the mixins
+        * @return the created node
+        * @see Node#addMixin(String)
+        */
+       public static void addMixin(Node node, String... mixins) {
+               try {
+                       for (String mixin : mixins) {
+                               node.addMixin(mixin);
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e);
+               }
+       }
+
+       /**
+        * Removes this node.
+        * 
+        * @see Node#remove()
+        */
+       public static void remove(Node node) {
+               try {
+                       node.remove();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot remove node " + node, e);
+               }
+       }
+
+       /**
+        * @return the node with htis id or <code>null</node> if not found
+        * @see Session#getNodeByIdentifier(String)
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Node getNodeById(Session session, String id) {
+               try {
+                       return session.getNodeByIdentifier(id);
+               } catch (ItemNotFoundException e) {
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get node with id " + id, e);
+               }
+       }
+
+       /**
+        * Set a property to the given value, or remove it if the value is
+        * <code>null</code>.
+        * 
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static void set(Node node, String property, Object value) {
+               try {
+                       if (!node.hasProperty(property)) {
+                               if (value != null) {
+                                       if (value instanceof List) {// multiple
+                                               List<?> lst = (List<?>) value;
+                                               String[] values = new String[lst.size()];
+                                               for (int i = 0; i < lst.size(); i++) {
+                                                       values[i] = lst.get(i).toString();
+                                               }
+                                               node.setProperty(property, values);
+                                       } else {
+                                               node.setProperty(property, value.toString());
+                                       }
+                               }
+                               return;
+                       }
+                       Property prop = node.getProperty(property);
+                       if (value == null) {
+                               prop.remove();
+                               return;
+                       }
+
+                       // multiple
+                       if (value instanceof List) {
+                               List<?> lst = (List<?>) value;
+                               String[] values = new String[lst.size()];
+                               // TODO better cast?
+                               for (int i = 0; i < lst.size(); i++) {
+                                       values[i] = lst.get(i).toString();
+                               }
+                               if (!prop.isMultiple())
+                                       prop.remove();
+                               node.setProperty(property, values);
+                               return;
+                       }
+
+                       // single
+                       if (prop.isMultiple()) {
+                               prop.remove();
+                               node.setProperty(property, value.toString());
+                               return;
+                       }
+
+                       if (value instanceof String)
+                               prop.setValue((String) value);
+                       else if (value instanceof Long)
+                               prop.setValue((Long) value);
+                       else if (value instanceof Integer)
+                               prop.setValue(((Integer) value).longValue());
+                       else if (value instanceof Double)
+                               prop.setValue((Double) value);
+                       else if (value instanceof Float)
+                               prop.setValue(((Float) value).doubleValue());
+                       else if (value instanceof Calendar)
+                               prop.setValue((Calendar) value);
+                       else if (value instanceof BigDecimal)
+                               prop.setValue((BigDecimal) value);
+                       else if (value instanceof Boolean)
+                               prop.setValue((Boolean) value);
+                       else if (value instanceof byte[])
+                               JcrUtils.setBinaryAsBytes(prop, (byte[]) value);
+                       else if (value instanceof Instant) {
+                               Instant instant = (Instant) value;
+                               GregorianCalendar calendar = new GregorianCalendar();
+                               calendar.setTime(Date.from(instant));
+                               prop.setValue(calendar);
+                       } else // try with toString()
+                               prop.setValue(value.toString());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e);
+               }
+       }
+
+       /**
+        * Get property as {@link String}.
+        * 
+        * @return the value of
+        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
+        *         <code>null</code> if the property does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String get(Node node, String property) {
+               return get(node, property, null);
+       }
+
+       /**
+        * Get property as a {@link String}. If the property is multiple it returns the
+        * first value.
+        * 
+        * @return the value of
+        *         {@link Node#getProperty(String)}.{@link Property#getString()} or
+        *         <code>defaultValue</code> if the property does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static String get(Node node, String property, String defaultValue) {
+               try {
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               if (!p.isMultiple())
+                                       return p.getString();
+                               else {
+                                       Value[] values = p.getValues();
+                                       if (values.length == 0)
+                                               return defaultValue;
+                                       else
+                                               return values[0].getString();
+                               }
+                       } else
+                               return defaultValue;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get property as a {@link Value}.
+        * 
+        * @return {@link Node#getProperty(String)} or <code>null</code> if the property
+        *         does not exist.
+        * @throws JcrException caused by {@link RepositoryException}
+        */
+       public static Value getValue(Node node, String property) {
+               try {
+                       if (node.hasProperty(property))
+                               return node.getProperty(property).getValue();
+                       else
+                               return null;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get property doing a best effort to cast it as the target object.
+        * 
+        * @return the value of {@link Node#getProperty(String)} or
+        *         <code>defaultValue</code> if the property does not exist.
+        * @throws IllegalArgumentException if the value could not be cast
+        * @throws JcrException             in case of unexpected
+        *                                  {@link RepositoryException}
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> T getAs(Node node, String property, T defaultValue) {
+               try {
+                       // TODO deal with multiple
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               try {
+                                       if (p.isMultiple()) {
+                                               throw new UnsupportedOperationException("Multiple values properties are not supported");
+                                       }
+                                       Value value = p.getValue();
+                                       return (T) get(value);
+                               } catch (ClassCastException e) {
+                                       throw new IllegalArgumentException(
+                                                       "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e);
+                               }
+                       } else {
+                               return defaultValue;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve property " + property + " from " + node, e);
+               }
+       }
+
+       public static <T> T getAs(Node node, String property, Class<T> clss) {
+               if (String.class.isAssignableFrom(clss)) {
+                       return (T) get(node, property);
+               } else if (Long.class.isAssignableFrom(clss)) {
+                       return (T) get(node, property);
+               } else {
+                       throw new IllegalArgumentException("Unsupported format " + clss);
+               }
+       }
+
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        * 
+        * @return the value of {@link Node#getProperty(String)}.
+        * @throws IllegalArgumentException if the value could not be cast
+        * @throws JcrException             in case of unexpected
+        *                                  {@link RepositoryException}
+        */
+       public static <T> List<T> getMultiple(Node node, String property) {
+               try {
+                       if (node.hasProperty(property)) {
+                               Property p = node.getProperty(property);
+                               return getMultiple(p);
+                       } else {
+                               return null;
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e);
+               }
+       }
+
+       /**
+        * Get a multiple property as a list, doing a best effort to cast it as the
+        * target list.
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> List<T> getMultiple(Property p) {
+               try {
+                       List<T> res = new ArrayList<>();
+                       if (!p.isMultiple()) {
+                               res.add((T) get(p.getValue()));
+                               return res;
+                       }
+                       Value[] values = p.getValues();
+                       for (Value value : values) {
+                               res.add((T) get(value));
+                       }
+                       return res;
+               } catch (ClassCastException | RepositoryException e) {
+                       throw new IllegalArgumentException("Cannot get property " + p, e);
+               }
+       }
+
+       /** Cast a {@link Value} to a standard Java object. */
+       public static Object get(Value value) {
+               Binary binary = null;
+               try {
+                       switch (value.getType()) {
+                       case PropertyType.STRING:
+                               return value.getString();
+                       case PropertyType.DOUBLE:
+                               return (Double) value.getDouble();
+                       case PropertyType.LONG:
+                               return (Long) value.getLong();
+                       case PropertyType.BOOLEAN:
+                               return (Boolean) value.getBoolean();
+                       case PropertyType.DATE:
+                               return value.getDate();
+                       case PropertyType.BINARY:
+                               binary = value.getBinary();
+                               byte[] arr = null;
+                               try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) {
+                                       IOUtils.copy(in, out);
+                                       arr = out.toByteArray();
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot read binary from " + value, e);
+                               }
+                               return arr;
+                       default:
+                               return value.getString();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot cast value from " + value, e);
+               } finally {
+                       if (binary != null)
+                               binary.dispose();
+               }
+       }
+
+       /**
+        * Retrieves the {@link Session} related to this node.
+        * 
+        * @deprecated Use {@link #getSession(Node)} instead.
+        */
+       @Deprecated
+       public static Session session(Node node) {
+               return getSession(node);
+       }
+
+       /** Retrieves the {@link Session} related to this node. */
+       public static Session getSession(Node node) {
+               try {
+                       return node.getSession();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve session related to " + node, e);
+               }
+       }
+
+       /** Retrieves the root node related to this session. */
+       public static Node getRootNode(Session session) {
+               try {
+                       return session.getRootNode();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get root node for " + session, e);
+               }
+       }
+
+       /** Whether this item exists. */
+       public static boolean itemExists(Session session, String path) {
+               try {
+                       return session.itemExists(path);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check whether " + path + " exists", e);
+               }
+       }
+
+       /**
+        * Saves the {@link Session} related to this node. Note that all other unrelated
+        * modifications in this session will also be saved.
+        */
+       public static void save(Node node) {
+               try {
+                       Session session = node.getSession();
+//                     if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+//                             set(node, Property.JCR_LAST_MODIFIED, Instant.now());
+//                             set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID());
+//                     }
+                       if (session.hasPendingChanges())
+                               session.save();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot save session related to " + node + " in workspace "
+                                       + session(node).getWorkspace().getName(), e);
+               }
+       }
+
+       /** Login to a JCR repository. */
+       public static Session login(Repository repository, String workspace) {
+               try {
+                       return repository.login(workspace);
+               } catch (RepositoryException e) {
+                       throw new IllegalArgumentException("Cannot login to repository", e);
+               }
+       }
+
+       /** Safely and silently logs out a session. */
+       public static void logout(Session session) {
+               try {
+                       if (session != null)
+                               if (session.isLive())
+                                       session.logout();
+               } catch (Exception e) {
+                       // silent
+               }
+       }
+
+       /** Safely and silently logs out the underlying session. */
+       public static void logout(Node node) {
+               Jcr.logout(session(node));
+       }
+
+       /*
+        * SECURITY
+        */
+       /**
+        * Add a single privilege to a node.
+        * 
+        * @see Privilege
+        */
+       public static void addPrivilege(Node node, String principal, String privilege) {
+               try {
+                       Session session = node.getSession();
+                       JcrUtils.addPrivilege(session, node.getPath(), principal, privilege);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot add privilege " + privilege + " to " + node, e);
+               }
+       }
+
+       /*
+        * VERSIONING
+        */
+       /** Get checked out status. */
+       public static boolean isCheckedOut(Node node) {
+               try {
+                       return node.isCheckedOut();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot retrieve checked out status of " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkpoint(String) */
+       public static void checkpoint(Node node) {
+               try {
+                       versionManager(node).checkpoint(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check in " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkin(String) */
+       public static void checkin(Node node) {
+               try {
+                       versionManager(node).checkin(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check in " + node, e);
+               }
+       }
+
+       /** @see VersionManager#checkout(String) */
+       public static void checkout(Node node) {
+               try {
+                       versionManager(node).checkout(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot check out " + node, e);
+               }
+       }
+
+       /** Get the {@link VersionManager} related to this node. */
+       public static VersionManager versionManager(Node node) {
+               try {
+                       return node.getSession().getWorkspace().getVersionManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get version manager from " + node, e);
+               }
+       }
+
+       /** Get the {@link VersionHistory} related to this node. */
+       public static VersionHistory getVersionHistory(Node node) {
+               try {
+                       return versionManager(node).getVersionHistory(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get version history from " + node, e);
+               }
+       }
+
+       /**
+        * The linear versions of this version history in reverse order and without the
+        * root version.
+        */
+       public static List<Version> getLinearVersions(VersionHistory versionHistory) {
+               try {
+                       List<Version> lst = new ArrayList<>();
+                       VersionIterator vit = versionHistory.getAllLinearVersions();
+                       while (vit.hasNext())
+                               lst.add(vit.nextVersion());
+                       lst.remove(0);
+                       Collections.reverse(lst);
+                       return lst;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get linear versions from " + versionHistory, e);
+               }
+       }
+
+       /** The frozen node related to this {@link Version}. */
+       public static Node getFrozenNode(Version version) {
+               try {
+                       return version.getFrozenNode();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get frozen node from " + version, e);
+               }
+       }
+
+       /** Get the base {@link Version} related to this node. */
+       public static Version getBaseVersion(Node node) {
+               try {
+                       return versionManager(node).getBaseVersion(node.getPath());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get base version from " + node, e);
+               }
+       }
+
+       /*
+        * FILES
+        */
+       /**
+        * Returns the size of this file.
+        * 
+        * @see NodeType#NT_FILE
+        */
+       public static long getFileSize(Node fileNode) {
+               try {
+                       if (!fileNode.isNodeType(NodeType.NT_FILE))
+                               throw new IllegalArgumentException(fileNode + " must be a file.");
+                       return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary());
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get file size of " + fileNode, e);
+               }
+       }
+
+       /** Returns the size of this {@link Binary}. */
+       public static long getBinarySize(Binary binaryArg) {
+               try {
+                       try (Bin binary = new Bin(binaryArg)) {
+                               return binary.getSize();
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get file size of binary " + binaryArg, e);
+               }
+       }
+
+       // QUERY
+       /** Creates a JCR-SQL2 query using {@link MessageFormat}. */
+       public static Query createQuery(QueryManager qm, String sql, Object... args) {
+               // fix single quotes
+               sql = sql.replaceAll("'", "''");
+               String query = MessageFormat.format(sql, args);
+               try {
+                       return qm.createQuery(query, Query.JCR_SQL2);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot create JCR-SQL2 query from " + query, e);
+               }
+       }
+
+       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+       public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) {
+               Query query = createQuery(qm, sql, args);
+               try {
+                       return query.execute().getNodes();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e);
+               }
+       }
+
+       /** Executes a JCR-SQL2 query using {@link MessageFormat}. */
+       public static NodeIterator executeQuery(Session session, String sql, Object... args) {
+               QueryManager queryManager;
+               try {
+                       queryManager = session.getWorkspace().getQueryManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get query manager from session " + session, e);
+               }
+               return executeQuery(queryManager, sql, args);
+       }
+
+       /**
+        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+        * single node at most.
+        * 
+        * @return the node or <code>null</code> if not found.
+        */
+       public static Node getNode(QueryManager qm, String sql, Object... args) {
+               NodeIterator nit = executeQuery(qm, sql, args);
+               if (nit.hasNext()) {
+                       Node node = nit.nextNode();
+                       if (nit.hasNext())
+                               throw new IllegalStateException(
+                                               "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node.");
+                       return node;
+               } else {
+                       return null;
+               }
+       }
+
+       /**
+        * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a
+        * single node at most.
+        * 
+        * @return the node or <code>null</code> if not found.
+        */
+       public static Node getNode(Session session, String sql, Object... args) {
+               QueryManager queryManager;
+               try {
+                       queryManager = session.getWorkspace().getQueryManager();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get query manager from session " + session, e);
+               }
+               return getNode(queryManager, sql, args);
+       }
+
+       public static Node getRowNode(Row row, String selectorName) {
+               try {
+                       return row.getNode(selectorName);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get node " + selectorName + " from row", e);
+               }
+       }
+
+       /** Singleton. */
+       private Jcr() {
+
+       }
+}
diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java
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/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..c3c609c
--- /dev/null
@@ -0,0 +1,23 @@
+Bundle-Activator: org.argeo.cms.ui.internal.Activator
+Bundle-ActivationPolicy: lazy
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.window,\
+org.eclipse.core.commands,\
+javax.jcr.security,\
+org.eclipse.rap.fileupload;version="[2.1,4)",\
+org.eclipse.rap.rwt;version="[2.1,4)",\
+org.eclipse.rap.rwt.application;version="[2.1,4)",\
+org.eclipse.rap.rwt.client;version="[2.1,4)",\
+org.eclipse.rap.rwt.client.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.service;version="[2.1,4)",\
+org.eclipse.rap.rwt.widgets;version="[2.1,4)",\
+org.osgi.*;version=0.0.0,\
+javax.servlet.*;version="[3,5)",\
+*
+
+## TODO: in order to enable single sourcing, we have introduced dummy RAP packages 
+# in the RCP specific bundle org.argeo.eclipse.ui.rap.
+# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, 
+# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x
+# We enable large RAP version range as a workaround that must be fixed 
\ No newline at end of file
diff --git a/jcr/org.argeo.cms.ui/build.properties b/jcr/org.argeo.cms.ui/build.properties
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/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..008ec2c
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.cms.ui.util;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** The site-related user menu */
+public class UserMenu extends CmsLoginShell {
+       private final Control source;
+       private final Node context;
+
+       public UserMenu(Control source, Node context) {
+               // FIXME pass CMS context
+               super(CmsUiUtils.getCmsView(), null);
+               this.context = context;
+               createUi();
+               if (source == null)
+                       throw new CmsException("Source control cannot be null.");
+               this.source = source;
+               open();
+       }
+
+       @Override
+       protected Shell createShell() {
+               return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+       }
+
+       @Override
+       public void open() {
+               Shell shell = getShell();
+               shell.pack();
+               shell.layout();
+               shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y));
+               shell.addShellListener(new ShellAdapter() {
+                       private static final long serialVersionUID = 5178980294808435833L;
+
+                       @Override
+                       public void shellDeactivated(ShellEvent e) {
+                               closeShell();
+                       }
+               });
+               super.open();
+       }
+
+       protected Node getContext() {
+               return context;
+       }
+
+}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java
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/RowColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/RowColumnLabelProvider.java
new file mode 100644 (file)
index 0000000..70c71ef
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.eclipse.ui.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Row;
+
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+
+/** Simplifies writing JCR-based column label provider. */
+public class RowColumnLabelProvider extends ColumnLabelProvider {
+       private static final long serialVersionUID = -6586692836928505358L;
+
+       protected String getRowText(Row row) throws RepositoryException {
+               return super.getText(row);
+       }
+
+       protected String getRowToolTipText(Row row) throws RepositoryException {
+               return super.getToolTipText(row);
+       }
+
+       protected Image getRowImage(Row row) throws RepositoryException {
+               return super.getImage(row);
+       }
+
+       protected Font getRowFont(Row row) throws RepositoryException {
+               return super.getFont(row);
+       }
+
+       public Color getRowBackground(Row row) throws RepositoryException {
+               return super.getBackground(row);
+       }
+
+       public Color getRowForeground(Row row) throws RepositoryException {
+               return super.getForeground(row);
+       }
+
+       @Override
+       public String getText(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowText((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+       @Override
+       public Image getImage(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowImage((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+       @Override
+       public String getToolTipText(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowToolTipText((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+       @Override
+       public Font getFont(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowFont((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+       @Override
+       public Color getBackground(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowBackground((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+       @Override
+       public Color getForeground(Object element) {
+               try {
+                       if (element instanceof Row)
+                               return getRowForeground((Row) element);
+                       else
+                               throw new IllegalArgumentException("Unsupported element type " + element.getClass());
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Repository exception when accessing " + element, e);
+               }
+       }
+
+}
diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java
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/jni/.cproject b/jni/.cproject
new file mode 100644 (file)
index 0000000..259cf27
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
+       <storageModule moduleId="org.eclipse.cdt.core.settings">
+               <cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591">
+                       <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" moduleId="org.eclipse.cdt.core.settings" name="Linux x86_64">
+                               <externalSettings/>
+                               <extensions>
+                                       <extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+                                       <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                                       <extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+                               </extensions>
+                       </storageModule>
+                       <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+                               <configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" name="Linux x86_64" parent="org.eclipse.cdt.build.core.emptycfg">
+                                       <folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933" name="/" resourcePath="">
+                                               <toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.2019979628" name="Cross GCC">
+                                                       <option id="cdt.managedbuild.option.gnu.cross.prefix.715257330" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
+                                                       <option id="cdt.managedbuild.option.gnu.cross.path.1210265928" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
+                                                       <targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.2070205849" isAbstract="false" osList="all"/>
+                                                       <builder enableAutoBuild="true" id="cdt.managedbuild.builder.gnu.cross.1468217036" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder"/>
+                                               </toolChain>
+                                       </folderInfo>
+                                       <sourceEntries>
+                                               <entry flags="VALUE_WORKSPACE_PATH" kind="sourcePath" name="org_argeo_api_uuid_libuuid"/>
+                                       </sourceEntries>
+                               </configuration>
+                       </storageModule>
+                       <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+               </cconfiguration>
+       </storageModule>
+       <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+               <project id="org.argeo.api.uuid.null.1913821562" name="org.argeo.api.uuid"/>
+       </storageModule>
+       <storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
+       <storageModule moduleId="refreshScope" versionNumber="2">
+               <configuration configurationName="Linux x86_64"/>
+               <configuration configurationName="Default">
+                       <resource resourceType="PROJECT" workspacePath="/Java_org_argeo_api_uuid"/>
+               </configuration>
+       </storageModule>
+       <storageModule moduleId="scannerConfiguration">
+               <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+               <scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1367283591;cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933;cdt.managedbuild.tool.gnu.cross.c.compiler.453851306;cdt.managedbuild.tool.gnu.c.compiler.input.1102448447">
+                       <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+               </scannerConfigBuildInfo>
+               <scannerConfigBuildInfo instanceId="cdt.managedbuild.toolchain.gnu.cross.base.1367283591;cdt.managedbuild.toolchain.gnu.cross.base.1367283591.350590933;cdt.managedbuild.tool.gnu.cross.cpp.compiler.365551368;cdt.managedbuild.tool.gnu.cpp.compiler.input.916135861">
+                       <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+               </scannerConfigBuildInfo>
+       </storageModule>
+       <storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
+       <storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
+               <buildTargets>
+                       <target name="ide" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
+                               <buildCommand>make</buildCommand>
+                               <buildArguments/>
+                               <buildTarget>ide</buildTarget>
+                               <stopOnError>true</stopOnError>
+                               <useDefaultCommand>true</useDefaultCommand>
+                               <runAllBuilders>true</runAllBuilders>
+                       </target>
+               </buildTargets>
+       </storageModule>
+</cproject>
\ No newline at end of file
diff --git a/jni/.project b/jni/.project
new file mode 100644 (file)
index 0000000..492a807
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>jni-commons</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
+                       <triggers>full,incremental,</triggers>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.cdt.core.cnature</nature>
+               <nature>org.eclipse.cdt.core.ccnature</nature>
+               <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
+               <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
+       </natures>
+</projectDescription>
diff --git a/jni/.settings/language.settings.xml b/jni/.settings/language.settings.xml
new file mode 100644 (file)
index 0000000..e30ee16
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project>
+       <configuration id="cdt.managedbuild.toolchain.gnu.cross.base.1367283591" name="Linux x86_64">
+               <extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+                       <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+                       <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+                       <provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="(g?cc)|([gc]\+\+)|(clang)" prefer-non-shared="true"/>
+                       <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+               </extension>
+       </configuration>
+</project>
\ No newline at end of file
diff --git a/jni/.settings/org.eclipse.cdt.core.prefs b/jni/.settings/org.eclipse.cdt.core.prefs
new file mode 100644 (file)
index 0000000..c8ec5df
--- /dev/null
@@ -0,0 +1,6 @@
+doxygen/doxygen_new_line_after_brief=true
+doxygen/doxygen_use_brief_tag=false
+doxygen/doxygen_use_javadoc_tags=true
+doxygen/doxygen_use_pre_tag=false
+doxygen/doxygen_use_structural_commands=false
+eclipse.preferences.version=1
diff --git a/jni/Makefile b/jni/Makefile
new file mode 100644 (file)
index 0000000..de2b84c
--- /dev/null
@@ -0,0 +1,14 @@
+include ../sdk.mk
+
+JNIDIRS = org_argeo_api_uuid_libuuid
+
+.PHONY: clean all
+
+all: 
+       $(foreach dir, $(JNIDIRS), $(MAKE) -C $(dir);)
+       
+clean:
+       rm -rf $(BUILD_DIR) $(SDK_BUILD_BASE)/jni
+
+
+
diff --git a/jni/jni.mk b/jni/jni.mk
new file mode 100644 (file)
index 0000000..40dde44
--- /dev/null
@@ -0,0 +1,58 @@
+TARGET_EXEC := libJava_$(NATIVE_PACKAGE).so
+
+LDFLAGS = -shared -fPIC -Wl,-soname,$(TARGET_EXEC).$(MAJOR).$(MINOR) $(ADDITIONAL_LIBS)
+CFLAGS = -O3 -fPIC
+
+SRC_DIRS := . 
+
+#
+# Generic Argeo
+#
+BUILD_DIR := $(SDK_BUILD_BASE)/jni/$(NATIVE_PACKAGE)
+
+# Every folder in ./src will need to be passed to GCC so that it can find header files
+INC_DIRS := $(shell find $(SRC_DIRS) -type d) $(JAVA_HOME)/include $(JAVA_HOME)/include/linux $(ADDITIONAL_INCLUDES)
+
+
+.PHONY: clean all ide
+all: $(SDK_BUILD_BASE)/jni/$(TARGET_EXEC)
+
+# Find all the C and C++ files we want to compile
+# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
+SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
+
+# String substitution for every C/C++ file.
+# As an example, hello.cpp turns into ./build/hello.cpp.o
+OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
+
+# String substitution (suffix version without %).
+# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
+DEPS := $(OBJS:.o=.d)
+
+# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
+INC_FLAGS := $(addprefix -I,$(INC_DIRS))
+
+# The -MMD and -MP flags together generate Makefiles for us!
+# These files will have .d instead of .o as the output.
+CPPFLAGS := $(INC_FLAGS) -MMD -MP
+
+# The final build step.
+$(SDK_BUILD_BASE)/jni/$(TARGET_EXEC): $(OBJS)
+       $(CC) $(OBJS) -o $@ $(LDFLAGS)
+
+# Build step for C source
+$(BUILD_DIR)/%.c.o: %.c
+       mkdir -p $(dir $@)
+       $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
+
+# Build step for C++ source
+$(BUILD_DIR)/%.cpp.o: %.cpp
+       mkdir -p $(dir $@)
+       $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
+
+# Include the .d makefiles. The - at the front suppresses the errors of missing
+# Makefiles. Initially, all the .d files will be missing, and we don't want those
+# errors to show up.
+-include $(DEPS)
+
+# MAKEFILE_DIR := $(dir $(firstword $(MAKEFILE_LIST)))
diff --git a/jni/org_argeo_api_uuid_libuuid/.gitignore b/jni/org_argeo_api_uuid_libuuid/.gitignore
new file mode 100644 (file)
index 0000000..84c048a
--- /dev/null
@@ -0,0 +1 @@
+/build/
diff --git a/jni/org_argeo_api_uuid_libuuid/Makefile b/jni/org_argeo_api_uuid_libuuid/Makefile
new file mode 100644 (file)
index 0000000..cfeb1db
--- /dev/null
@@ -0,0 +1,8 @@
+NATIVE_PACKAGE := org_argeo_api_uuid_libuuid
+
+ADDITIONAL_INCLUDES = /usr/include/uuid
+ADDITIONAL_LIBS = -luuid
+
+include ../../sdk.mk
+include ../jni.mk
+
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.c
new file mode 100644 (file)
index 0000000..a5aeed0
--- /dev/null
@@ -0,0 +1,8 @@
+#include <jni.h>
+#include <uuid.h>
+#include "org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h"
+
+JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID(
+               JNIEnv *env, jobject uuidFactory, jobject uuidBuf) {
+       uuid_generate_time((*env)->GetDirectBufferAddress(env, uuidBuf));
+}
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_DirectLibuuidFactory.h
new file mode 100644 (file)
index 0000000..5f18bf7
--- /dev/null
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_argeo_api_uuid_libuuid_DirectLibuuidFactory */
+
+#ifndef _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory
+#define _Included_org_argeo_api_uuid_libuuid_DirectLibuuidFactory
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     org_argeo_api_uuid_libuuid_DirectLibuuidFactory
+ * Method:    timeUUID
+ * Signature: (Ljava/nio/ByteBuffer;)V
+ */
+JNIEXPORT void JNICALL Java_org_argeo_api_uuid_libuuid_DirectLibuuidFactory_timeUUID
+  (JNIEnv *, jobject, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.c
new file mode 100644 (file)
index 0000000..f97b1f4
--- /dev/null
@@ -0,0 +1,91 @@
+#include <jni.h>
+#include <uuid.h>
+#include "org_argeo_api_uuid_libuuid_LibuuidFactory.h"
+
+/*
+ * UTILITIES
+ */
+
+static inline jobject fromBytes(JNIEnv *env, uuid_t out) {
+       jlong msb = 0;
+       jlong lsb = 0;
+
+       for (int i = 0; i < 8; i++)
+               msb = (msb << 8) | (out[i] & 0xff);
+       for (int i = 8; i < 16; i++)
+               lsb = (lsb << 8) | (out[i] & 0xff);
+
+       jclass uuidClass = (*env)->FindClass(env, "java/util/UUID");
+       jmethodID uuidConstructor = (*env)->GetMethodID(env, uuidClass, "<init>",
+                       "(JJ)V");
+
+       jobject jUUID = (*env)->AllocObject(env, uuidClass);
+       (*env)->CallVoidMethod(env, jUUID, uuidConstructor, msb, lsb);
+
+       return jUUID;
+}
+
+static inline void toBytes(JNIEnv *env, jobject jUUID, uuid_t result) {
+
+       jclass uuidClass = (*env)->FindClass(env, "java/util/UUID");
+       jmethodID getMostSignificantBits = (*env)->GetMethodID(env, uuidClass,
+                       "getMostSignificantBits", "()J");
+       jmethodID getLeastSignificantBits = (*env)->GetMethodID(env, uuidClass,
+                       "getLeastSignificantBits", "()J");
+
+       jlong msb = (*env)->CallLongMethod(env, jUUID, getMostSignificantBits);
+       jlong lsb = (*env)->CallLongMethod(env, jUUID, getLeastSignificantBits);
+
+       for (int i = 0; i < 8; i++)
+               result[i] = (unsigned char) ((msb >> ((7 - i) * 8)) & 0xff);
+       for (int i = 8; i < 16; i++)
+               result[i] = (unsigned char) ((lsb >> ((15 - i) * 8)) & 0xff);
+}
+
+/*
+ * JNI IMPLEMENTATION
+ */
+
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_timeUUID(
+               JNIEnv *env, jobject uuidFactory) {
+       uuid_t out;
+
+       uuid_generate_time(out);
+       return fromBytes(env, out);
+}
+
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv5(
+               JNIEnv *env, jobject uuidFactory, jobject namespaceUuid,
+               jbyteArray name) {
+       uuid_t ns;
+       uuid_t out;
+
+       toBytes(env, namespaceUuid, ns);
+       jsize length = (*env)->GetArrayLength(env, name);
+       jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0);
+
+       uuid_generate_sha1(out, ns, bytes, length);
+       return fromBytes(env, out);
+}
+
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv3(
+               JNIEnv *env, jobject uuidFactory, jobject namespaceUuid,
+               jbyteArray name) {
+       uuid_t ns;
+       uuid_t out;
+
+       toBytes(env, namespaceUuid, ns);
+       jsize length = (*env)->GetArrayLength(env, name);
+       jbyte *bytes = (*env)->GetByteArrayElements(env, name, 0);
+
+       uuid_generate_md5(out, ns, bytes, length);
+       return fromBytes(env, out);
+}
+
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_randomUUIDStrong(
+               JNIEnv *env, jobject uuidFactory) {
+       uuid_t out;
+
+       uuid_generate_random(out);
+       return fromBytes(env, out);
+}
diff --git a/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h b/jni/org_argeo_api_uuid_libuuid/org_argeo_api_uuid_libuuid_LibuuidFactory.h
new file mode 100644 (file)
index 0000000..ad0ac5e
--- /dev/null
@@ -0,0 +1,45 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_argeo_api_uuid_libuuid_LibuuidFactory */
+
+#ifndef _Included_org_argeo_api_uuid_libuuid_LibuuidFactory
+#define _Included_org_argeo_api_uuid_libuuid_LibuuidFactory
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class:     org_argeo_api_uuid_libuuid_LibuuidFactory
+ * Method:    timeUUID
+ * Signature: ()Ljava/util/UUID;
+ */
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_timeUUID
+  (JNIEnv *, jobject);
+
+/*
+ * Class:     org_argeo_api_uuid_libuuid_LibuuidFactory
+ * Method:    nameUUIDv5
+ * Signature: (Ljava/util/UUID;[B)Ljava/util/UUID;
+ */
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv5
+  (JNIEnv *, jobject, jobject, jbyteArray);
+
+/*
+ * Class:     org_argeo_api_uuid_libuuid_LibuuidFactory
+ * Method:    nameUUIDv3
+ * Signature: (Ljava/util/UUID;[B)Ljava/util/UUID;
+ */
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_nameUUIDv3
+  (JNIEnv *, jobject, jobject, jbyteArray);
+
+/*
+ * Class:     org_argeo_api_uuid_libuuid_LibuuidFactory
+ * Method:    randomUUIDStrong
+ * Signature: ()Ljava/util/UUID;
+ */
+JNIEXPORT jobject JNICALL Java_org_argeo_api_uuid_libuuid_LibuuidFactory_randomUUIDStrong
+  (JNIEnv *, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/org.argeo.api.acr/.classpath b/org.argeo.api.acr/.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/org.argeo.api.acr/.project b/org.argeo.api.acr/.project
new file mode 100644 (file)
index 0000000..d7a785e
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.acr</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.acr/bnd.bnd b/org.argeo.api.acr/bnd.bnd
new file mode 100644 (file)
index 0000000..6e839c0
--- /dev/null
@@ -0,0 +1,4 @@
+Import-Package: \
+javax.security.*
+
+Export-Package: org.argeo.api.acr.*
\ No newline at end of file
diff --git a/org.argeo.api.acr/build.properties b/org.argeo.api.acr/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java b/org.argeo.api.acr/src/org/argeo/api/acr/AttributeFormatter.java
new file mode 100644 (file)
index 0000000..4160033
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.api.acr;
+
+/**
+ * An attribute type MUST consistently parse a string to an object so that
+ * <code>parse(obj.toString()).equals(obj)</code> is verified.
+ * {@link #format(Object)} can be overridden to provide more efficient
+ * implementations but the returned
+ * <code>String<code> MUST be the same, that is <code>format(obj).equals(obj.toString())</code>
+ * is verified.
+ */
+public interface AttributeFormatter<T> {
+       /** Parses a String to a Java object. */
+       T parse(String str) throws IllegalArgumentException;
+
+       /** Default implementation returns {@link Object#toString()} on the argument. */
+       default String format(T obj) {
+               return obj.toString();
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java b/org.argeo.api.acr/src/org/argeo/api/acr/CompositeString.java
new file mode 100644 (file)
index 0000000..b83fe30
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.api.acr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/** A name that can be expressed with various conventions. */
+public class CompositeString {
+       public final static Character UNDERSCORE = Character.valueOf('_');
+       public final static Character SPACE = Character.valueOf(' ');
+       public final static Character DASH = Character.valueOf('-');
+
+       private final String[] parts;
+
+       // optimisation
+       private final int hashCode;
+
+       public CompositeString(String str) {
+               Objects.requireNonNull(str, "String cannot be null");
+               if ("".equals(str.trim()))
+                       throw new IllegalArgumentException("String cannot be empty");
+               if (!str.equals(str.trim()))
+                       throw new IllegalArgumentException("String must be trimmed");
+               this.parts = toParts(str);
+               hashCode = hashCode(this.parts);
+       }
+
+       public String toString(char separator, boolean upperCase) {
+               StringBuilder sb = null;
+               for (String part : parts) {
+                       if (sb == null) {
+                               sb = new StringBuilder();
+                       } else {
+                               sb.append(separator);
+                       }
+                       sb.append(upperCase ? part.toUpperCase() : part);
+               }
+               return sb.toString();
+       }
+
+       public String toStringCaml(boolean firstCharUpperCase) {
+               StringBuilder sb = null;
+               for (String part : parts) {
+                       if (sb == null) {// first
+                               sb = new StringBuilder();
+                               sb.append(firstCharUpperCase ? Character.toUpperCase(part.charAt(0)) : part.charAt(0));
+                       } else {
+                               sb.append(Character.toUpperCase(part.charAt(0)));
+                       }
+
+                       if (part.length() > 1)
+                               sb.append(part.substring(1));
+               }
+               return sb.toString();
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj == null || !(obj instanceof CompositeString))
+                       return false;
+
+               CompositeString other = (CompositeString) obj;
+               return Arrays.equals(parts, other.parts);
+       }
+
+       @Override
+       public String toString() {
+               return toString(DASH, false);
+       }
+
+       public static String[] toParts(String str) {
+               Character separator = null;
+               if (str.indexOf(UNDERSCORE) >= 0) {
+                       checkNo(str, SPACE);
+                       checkNo(str, DASH);
+                       separator = UNDERSCORE;
+               } else if (str.indexOf(DASH) >= 0) {
+                       checkNo(str, SPACE);
+                       checkNo(str, UNDERSCORE);
+                       separator = DASH;
+               } else if (str.indexOf(SPACE) >= 0) {
+                       checkNo(str, DASH);
+                       checkNo(str, UNDERSCORE);
+                       separator = SPACE;
+               }
+
+               List<String> res = new ArrayList<>();
+               if (separator != null) {
+                       StringTokenizer st = new StringTokenizer(str, separator.toString());
+                       while (st.hasMoreTokens()) {
+                               res.add(st.nextToken().toLowerCase());
+                       }
+               } else {
+                       // single
+                       String strLowerCase = str.toLowerCase();
+                       if (str.toUpperCase().equals(str) || strLowerCase.equals(str))
+                               return new String[] { strLowerCase };
+
+                       // CAML
+                       StringBuilder current = null;
+                       for (char c : str.toCharArray()) {
+                               if (Character.isUpperCase(c)) {
+                                       if (current != null)
+                                               res.add(current.toString());
+                                       current = new StringBuilder();
+                               }
+                               if (current == null)// first char is lower case
+                                       current = new StringBuilder();
+                               current.append(Character.toLowerCase(c));
+                       }
+                       res.add(current.toString());
+               }
+               return res.toArray(new String[res.size()]);
+       }
+
+       private static void checkNo(String str, Character c) {
+               if (str.indexOf(c) >= 0) {
+                       throw new IllegalArgumentException("Only one kind of sperator is allowed");
+               }
+       }
+
+       private static int hashCode(String[] parts) {
+               int hashCode = 0;
+               for (String part : parts) {
+                       hashCode = hashCode + part.hashCode();
+               }
+               return hashCode;
+       }
+
+       static boolean smokeTests() {
+               CompositeString plainName = new CompositeString("NAME");
+               assert "name".equals(plainName.toString());
+               assert "NAME".equals(plainName.toString(UNDERSCORE, true));
+               assert "name".equals(plainName.toString(UNDERSCORE, false));
+               assert "name".equals(plainName.toStringCaml(false));
+               assert "Name".equals(plainName.toStringCaml(true));
+
+               CompositeString camlName = new CompositeString("myComplexName");
+
+               assert new CompositeString("my-complex-name").equals(camlName);
+               assert new CompositeString("MY_COMPLEX_NAME").equals(camlName);
+               assert new CompositeString("My complex Name").equals(camlName);
+               assert new CompositeString("MyComplexName").equals(camlName);
+
+               assert "my-complex-name".equals(camlName.toString());
+               assert "MY_COMPLEX_NAME".equals(camlName.toString(UNDERSCORE, true));
+               assert "my_complex_name".equals(camlName.toString(UNDERSCORE, false));
+               assert "myComplexName".equals(camlName.toStringCaml(false));
+               assert "MyComplexName".equals(camlName.toStringCaml(true));
+
+               return CompositeString.class.desiredAssertionStatus();
+       }
+
+       public static void main(String[] args) {
+               System.out.println(smokeTests());
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/Content.java b/org.argeo.api.acr/src/org/argeo/api/acr/Content.java
new file mode 100644 (file)
index 0000000..edcfaea
--- /dev/null
@@ -0,0 +1,118 @@
+package org.argeo.api.acr;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+
+/**
+ * A semi-structured content, with attributes, within a hierarchical structure.
+ */
+public interface Content extends Iterable<Content>, Map<QName, Object> {
+
+       QName getName();
+
+       String getPath();
+
+       Content getParent();
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+
+       <A> Optional<A> get(QName key, Class<A> clss);
+
+       default Object get(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return get(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
+
+       default Object put(String key, Object value) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return put(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX), value);
+       }
+
+       default Object remove(String key) {
+               if (key.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + key + " has a prefix");
+               return remove(new QName(XMLConstants.NULL_NS_URI, key, XMLConstants.DEFAULT_NS_PREFIX));
+       }
+
+       Class<?> getType(QName key);
+
+       boolean isMultiple(QName key);
+
+       <A> Optional<List<A>> getMultiple(QName key, Class<A> clss);
+
+       default <A> List<A> getMultiple(QName key) {
+               Class<A> type;
+               try {
+                       type = (Class<A>) getType(key);
+               } catch (ClassCastException e) {
+                       throw new IllegalArgumentException("Requested type is not the default type");
+               }
+               Optional<List<A>> res = getMultiple(key, type);
+               if (res == null)
+                       return null;
+               else {
+                       if (res.isEmpty())
+                               throw new IllegalStateException("Metadata " + key + " is not availabel as list of type " + type);
+                       return res.get();
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+       Content add(QName name, QName... classes);
+
+       default Content add(String name, QName... classes) {
+               if (name.indexOf(':') >= 0)
+                       throw new IllegalArgumentException("Name " + name + " has a prefix");
+               return add(new QName(XMLConstants.NULL_NS_URI, name, XMLConstants.DEFAULT_NS_PREFIX), classes);
+       }
+
+       void remove();
+
+       /*
+        * DEFAULT METHODS
+        */
+       default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+               throw new IllegalArgumentException("Cannot adapt content " + this + " to " + clss.getName());
+       }
+
+       default <C extends AutoCloseable> C open(Class<C> clss) throws Exception, IllegalArgumentException {
+               throw new IllegalArgumentException("Cannot open content " + this + " as " + clss.getName());
+       }
+
+       /*
+        * CONVENIENCE METHODS
+        */
+//     default String attr(String key) {
+//             return get(key, String.class);
+//     }
+//
+//     default String attr(Object key) {
+//             return key != null ? attr(key.toString()) : attr(null);
+//     }
+//
+//     default <A> A get(Object key, Class<A> clss) {
+//             return key != null ? get(key.toString(), clss) : get(null, clss);
+//     }
+
+       /*
+        * EXPERIMENTAL UNSUPPORTED
+        */
+       default boolean hasText() {
+               return false;
+       }
+
+       default String getText() {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentFeatureUnsupportedException.java
new file mode 100644 (file)
index 0000000..485caa9
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.api.acr;
+
+/** When a feature is not supported by the underlying repository. */
+public class ContentFeatureUnsupportedException extends UnsupportedOperationException {
+       private static final long serialVersionUID = 3193936026343114949L;
+
+       public ContentFeatureUnsupportedException() {
+       }
+
+       public ContentFeatureUnsupportedException(String message) {
+               super(message);
+       }
+
+       public ContentFeatureUnsupportedException(Throwable cause) {
+               super(cause);
+       }
+
+       public ContentFeatureUnsupportedException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java
new file mode 100644 (file)
index 0000000..341a3e2
--- /dev/null
@@ -0,0 +1,165 @@
+package org.argeo.api.acr;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link QName} which MUST have prefix and whose {@link #toString()} method
+ * returns the prefixed form (prefix:localPart).
+ */
+public class ContentName extends QName {
+       private static final long serialVersionUID = 5722920985400306100L;
+       public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+       /**
+        * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
+        * namespace, to be used as a base for the namespaces.
+        * 
+        * @see https://www.w3.org/TR/xml-names/#ns-decl
+        */
+       // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
+       // NOTE : must be declared before default namespaces
+       public final static UUID XMLNS_UUID = UUID.fromString("4b352aad-ba1c-3139-b9d3-41e5816f6088");
+       // uuidgen --md5 --namespace 4b352aad-ba1c-3139-b9d3-41e5816f6088 --name ""
+       public final static UUID NULL_NS_UUID = UUID.fromString("f07726e3-99c8-3178-b758-a86ed41f300d");
+
+       private final static Map<String, UUID> namespaceUuids = Collections.synchronizedMap(new TreeMap<>());
+       private final static Map<String, UUID> nameUuids = Collections.synchronizedMap(new TreeMap<>());
+
+       static {
+               assert NULL_NS_UUID.equals(nameUUIDv3(XMLNS_UUID, XMLConstants.NULL_NS_URI.getBytes(UTF_8)));
+       }
+
+//     private final UUID uuid;
+
+       public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) {
+               super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI));
+       }
+
+       private static String checkPrefix(NamespaceContext nsContext, String namespaceURI) {
+               Objects.requireNonNull(nsContext, "Namespace context cannot be null");
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               String prefix = nsContext.getNamespaceURI(namespaceURI);
+               if (prefix == null)
+                       throw new IllegalStateException("No prefix found for " + namespaceURI + " from context " + nsContext);
+               return prefix;
+       }
+
+       ContentName(String namespaceURI, String localPart, String prefix) {
+               super(namespaceURI, localPart, prefix);
+       }
+
+       public ContentName(String localPart) {
+               super(XMLConstants.NULL_NS_URI, localPart, XMLConstants.DEFAULT_NS_PREFIX);
+       }
+
+       public ContentName(QName qName, NamespaceContext nsContext) {
+               this(qName.getNamespaceURI(), qName.getLocalPart(), nsContext);
+       }
+
+       public String toQNameString() {
+               return super.toString();
+       }
+
+       public String toPrefixedString() {
+               return toPrefixedString(this);
+       }
+
+       /*
+        * OBJECT METHOS
+        */
+
+       @Override
+       public String toString() {
+               return toPrefixedString();
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new ContentName(getNamespaceURI(), getLocalPart(), getPrefix());
+       }
+
+       public static String toPrefixedString(QName name) {
+               String prefix = name.getPrefix();
+               assert prefix != null;
+               return "".equals(prefix) ? name.getLocalPart() : prefix + ":" + name.getLocalPart();
+       }
+//     ContentNamespace getNamespace();
+//
+//     String getName();
+
+       public static UUID namespaceUuid(String namespaceURI) {
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return NULL_NS_UUID;
+               Objects.requireNonNull(namespaceURI, "Namespace URI cannot be null");
+               synchronized (namespaceUuids) {
+                       UUID namespaceUuid = namespaceUuids.get(namespaceURI);
+                       if (namespaceUuid == null) {
+                               namespaceUuid = nameUUIDv3(ContentName.XMLNS_UUID,
+                                               namespaceURI.toString().getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(namespaceURI, namespaceUuid);
+                       }
+                       return namespaceUuid;
+               }
+       }
+
+       public static UUID nameUuid(String namespaceURI, QName name) {
+               return nameUuid(name.getNamespaceURI(), name.getLocalPart());
+       }
+
+       public static UUID nameUuid(String namespaceURI, String name) {
+               Objects.requireNonNull(namespaceURI, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+               synchronized (nameUuids) {
+                       String key = XMLConstants.NULL_NS_URI.equals(namespaceURI) ? name : "{" + namespaceURI + "}" + name;
+                       UUID nameUuid = nameUuids.get(key);
+                       if (nameUuid == null) {
+                               UUID namespaceUuid = namespaceUuid(namespaceURI);
+                               nameUuid = nameUUIDv3(namespaceUuid, name.getBytes(StandardCharsets.UTF_8));
+                               namespaceUuids.put(key, nameUuid);
+                       }
+                       return nameUuid;
+               }
+       }
+
+       /*
+        * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+        */
+       static UUID nameUUIDv3(UUID namespace, byte[] name) {
+               byte[] arr = new byte[name.length + 16];
+               ContentName.copyUuidBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
+               long msb = uuid.getMostSignificantBits();
+               long lsb = uuid.getLeastSignificantBits();
+               assert arr.length >= 16 + offset;
+               for (int i = offset; i < 8 + offset; i++)
+                       arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
+               for (int i = 8 + offset; i < 16 + offset; i++)
+                       arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
+       }
+
+       /*
+        * UTILITIES
+        */
+
+       public static boolean contains(QName[] classes, QName name) {
+               for (QName clss : classes) {
+                       if (clss.equals(name))
+                               return true;
+               }
+               return false;
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNameSupplier.java
new file mode 100644 (file)
index 0000000..e3c721f
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.api.acr;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.function.Supplier;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+public interface ContentNameSupplier extends Supplier<ContentName>, NamespaceContext {
+       String name();
+
+       String getNamespaceURI();
+
+       String getDefaultPrefix();
+
+       @Override
+       default ContentName get() {
+               return toContentName();
+       }
+
+       default ContentName toContentName() {
+               CompositeString cs = new CompositeString(name());
+               String camlName = cs.toStringCaml(false);
+               return new ContentName(getNamespaceURI(), camlName, this);
+       }
+
+//     default String getNamespaceURI() {
+//             return XMLConstants.NULL_NS_URI;
+//     }
+//
+//     default String getDefaultPrefix() {
+//             return XMLConstants.DEFAULT_NS_PREFIX;
+//     }
+
+//     static ContentName toContentName(String namespaceURI, String localName, String prefix) {
+//             CompositeString cs = new CompositeString(localName);
+//             String camlName = cs.toStringCaml(false);
+//             return new ContentName(namespaceURI, camlName, this);
+//     }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (prefix.equals(getDefaultPrefix()))
+                       return getNamespaceURI();
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return getDefaultPrefix();
+               return null;
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> it = getStandardPrefixes(namespaceURI);
+               if (it != null)
+                       return it;
+               if (namespaceURI.equals(getNamespaceURI()))
+                       return Collections.singleton(getDefaultPrefix()).iterator();
+               return Collections.emptyIterator();
+       }
+
+       /*
+        * DEFAULT NAMESPACE CONTEXT OPERATIONS as specified in NamespaceContext
+        */
+       static String getStandardPrefix(String namespaceURI) {
+               if (namespaceURI == null)
+                       throw new IllegalArgumentException("Namespace URI cannot be null");
+               if (XMLConstants.XML_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XML_NS_PREFIX;
+               else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI))
+                       return XMLConstants.XMLNS_ATTRIBUTE;
+               return null;
+       }
+
+       static Iterator<String> getStandardPrefixes(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix == null)
+                       return null;
+               return Collections.singleton(prefix).iterator();
+       }
+
+       static String getStandardNamespaceURI(String prefix) {
+               if (prefix == null)
+                       throw new IllegalArgumentException("Prefix cannot be null");
+               if (XMLConstants.XML_NS_PREFIX.equals(prefix))
+                       return XMLConstants.XML_NS_URI;
+               else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
+                       return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+               return null;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentNotFoundException.java
new file mode 100644 (file)
index 0000000..b86c92c
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.api.acr;
+
+/** When a countent was requested which does not exists, equivalent to HTTP code 404.*/
+public class ContentNotFoundException extends RuntimeException {
+       private static final long serialVersionUID = -8629074900713760886L;
+
+       public ContentNotFoundException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+       public ContentNotFoundException(String message) {
+               super(message);
+       }
+
+       
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentRepository.java
new file mode 100644 (file)
index 0000000..3b2f156
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.api.acr;
+
+import java.util.Locale;
+import java.util.function.Supplier;
+
+/**
+ * A content repository is an actually running implementation of various kind of
+ * content system. It allows a pre-authenticated caller to open a session.
+ */
+public interface ContentRepository extends Supplier<ContentSession> {
+       ContentSession get(Locale locale);
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentResourceException.java
new file mode 100644 (file)
index 0000000..e418c4f
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.api.acr;
+
+/**
+ * When there is a problem the underlying resources, typically IO, network, DB
+ * access, etc.
+ */
+public class ContentResourceException extends IllegalStateException {
+       private static final long serialVersionUID = -2850145213683756996L;
+
+       public ContentResourceException() {
+       }
+
+       public ContentResourceException(String s) {
+               super(s);
+       }
+
+       public ContentResourceException(Throwable cause) {
+               super(cause);
+       }
+
+       public ContentResourceException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentSession.java
new file mode 100644 (file)
index 0000000..215bb9e
--- /dev/null
@@ -0,0 +1,49 @@
+package org.argeo.api.acr;
+
+import java.util.Locale;
+import java.util.Objects;
+
+import javax.security.auth.Subject;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+public interface ContentSession extends NamespaceContext {
+       Subject getSubject();
+
+       Locale getLocale();
+
+       Content get(String path);
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       default ContentName parsePrefixedName(String nameWithPrefix) {
+               Objects.requireNonNull(nameWithPrefix, "Name cannot be null");
+               if (nameWithPrefix.charAt(0) == '{') {
+                       return new ContentName(QName.valueOf(nameWithPrefix), this);
+               }
+               int index = nameWithPrefix.indexOf(':');
+               if (index < 0) {
+                       return new ContentName(nameWithPrefix);
+               }
+               String prefix = nameWithPrefix.substring(0, index);
+               // TODO deal with empty name?
+               String localName = nameWithPrefix.substring(index + 1);
+               String namespaceURI = getNamespaceURI(prefix);
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is unbound.");
+               return new ContentName(namespaceURI, localName, prefix);
+       }
+
+       default String toPrefixedName(QName name) {
+               if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()))
+                       return name.getLocalPart();
+               String prefix = getPrefix(name.getNamespaceURI());
+               if (prefix == null)
+                       throw new IllegalStateException("Namespace " + name.getNamespaceURI() + " is unbound.");
+               return prefix + ":" + name.getLocalPart();
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentStore.java
new file mode 100644 (file)
index 0000000..cda5d91
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.api.acr;
+
+public interface ContentStore {
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentUtils.java
new file mode 100644 (file)
index 0000000..2036c86
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.api.acr;
+
+import java.io.PrintStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import javax.xml.namespace.QName;
+
+public class ContentUtils {
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
+               traverse(content, doIt, 0);
+       }
+
+       public static void traverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth) {
+               doIt.accept(content, currentDepth);
+               int nextDepth = currentDepth + 1;
+               for (Content child : content) {
+                       traverse(child, doIt, nextDepth);
+               }
+       }
+
+       public static void print(Content content, PrintStream out, int depth, boolean printText) {
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < depth; i++) {
+                       sb.append("  ");
+               }
+               String prefix = sb.toString();
+               out.println(prefix + content.getName());
+               for (QName key : content.keySet()) {
+                       out.println(prefix + " " + key + "=" + content.get(key));
+               }
+               if (printText) {
+                       if (content.hasText()) {
+                               out.println("<![CDATA[" + content.getText().trim() + "]]>");
+                       }
+               }
+       }
+
+       public static URI bytesToDataURI(byte[] arr) {
+               String base64Str = Base64.getEncoder().encodeToString(arr);
+               try {
+                       final String PREFIX = "data:application/octet-stream;base64,";
+                       return new URI(PREFIX + base64Str);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e);
+               }
+
+       }
+
+       public static byte[] bytesFromDataURI(URI uri) {
+               if (!"data".equals(uri.getScheme()))
+                       throw new IllegalArgumentException("URI must have 'data' as a scheme");
+               String schemeSpecificPart = uri.getSchemeSpecificPart();
+               int commaIndex = schemeSpecificPart.indexOf(',');
+               String prefix = schemeSpecificPart.substring(0, commaIndex);
+               List<String> info = Arrays.asList(prefix.split(";"));
+               if (!info.contains("base64"))
+                       throw new IllegalArgumentException("URI must specify base64");
+
+               String base64Str = schemeSpecificPart.substring(commaIndex + 1);
+               return Base64.getDecoder().decode(base64Str);
+
+       }
+
+       public static <T> boolean isString(T t) {
+               return t instanceof String;
+       }
+
+       /** Singleton. */
+       private ContentUtils() {
+
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrAttributeType.java
new file mode 100644 (file)
index 0000000..ffa28af
--- /dev/null
@@ -0,0 +1,201 @@
+package org.argeo.api.acr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.UUID;
+
+import javax.xml.XMLConstants;
+
+/**
+ * Minimal standard attribute types that MUST be supported. All related classes
+ * belong to java.base and can be implicitly derived form a given
+ * <code>String<code>.
+ */
+public enum CrAttributeType implements ContentNameSupplier {
+       BOOLEAN(Boolean.class, new BooleanFormatter()), //
+       INTEGER(Integer.class, new IntegerFormatter()), //
+       LONG(Long.class, new LongFormatter()), //
+       DOUBLE(Double.class, new DoubleFormatter()), //
+       // we do not support short and float, like recent additions to Java
+       // (e.g. optional primitives)
+       DATE_TIME(Instant.class, new InstantFormatter()), //
+       UUID(UUID.class, new UuidFormatter()), //
+       ANY_URI(URI.class, new UriFormatter()), //
+       STRING(String.class, new StringFormatter()), //
+       ;
+
+       private final Class<?> clss;
+       private final AttributeFormatter<?> formatter;
+
+       private <T> CrAttributeType(Class<T> clss, AttributeFormatter<T> formatter) {
+               this.clss = clss;
+               this.formatter = formatter;
+       }
+
+       public Class<?> getClss() {
+               return clss;
+       }
+
+       public AttributeFormatter<?> getFormatter() {
+               return formatter;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               if (equals(UUID))
+                       return CrName.CR_DEFAULT_PREFIX;
+               else
+                       return "xs";
+       }
+
+       @Override
+       public String getNamespaceURI() {
+               if (equals(UUID))
+                       return CrName.CR_NAMESPACE_URI;
+               else
+                       return XMLConstants.W3C_XML_SCHEMA_NS_URI;
+       }
+
+       public static Object parse(String str) {
+               if (str == null)
+                       throw new IllegalArgumentException("String cannot be null");
+               // order IS important
+               try {
+                       if (str.length() == 4 || str.length() == 5)
+                               return BOOLEAN.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return INTEGER.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return LONG.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DOUBLE.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       return DATE_TIME.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       if (str.length() == 36)
+                               return UUID.getFormatter().parse(str);
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+               try {
+                       java.net.URI uri = (java.net.URI) ANY_URI.getFormatter().parse(str);
+                       if (uri.getScheme() != null)
+                               return uri;
+                       String path = uri.getPath();
+                       if (path.indexOf('/') >= 0)
+                               return uri;
+                       // if it is not clearly a path, we will consider it as a string
+                       // because their is no way to distinguish between 'any_string'
+                       // and 'any_file_name'.
+                       // Note that providing ./any_file_name would result in an equivalent URI
+               } catch (IllegalArgumentException e) {
+                       // silent
+               }
+
+               // default
+               return STRING.getFormatter().parse(str);
+       }
+
+       static class BooleanFormatter implements AttributeFormatter<Boolean> {
+
+               /**
+                * @param str must be exactly equals to either 'true' or 'false' (different
+                *            contract than {@link Boolean#parseBoolean(String)}.
+                */
+               @Override
+               public Boolean parse(String str) throws IllegalArgumentException {
+                       if ("true".equals(str))
+                               return Boolean.TRUE;
+                       if ("false".equals(str))
+                               return Boolean.FALSE;
+                       throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str);
+               }
+       }
+
+       static class IntegerFormatter implements AttributeFormatter<Integer> {
+               @Override
+               public Integer parse(String str) throws NumberFormatException {
+                       return Integer.parseInt(str);
+               }
+       }
+
+       static class LongFormatter implements AttributeFormatter<Long> {
+               @Override
+               public Long parse(String str) throws NumberFormatException {
+                       return Long.parseLong(str);
+               }
+       }
+
+       static class DoubleFormatter implements AttributeFormatter<Double> {
+
+               @Override
+               public Double parse(String str) throws NumberFormatException {
+                       return Double.parseDouble(str);
+               }
+       }
+
+       static class InstantFormatter implements AttributeFormatter<Instant> {
+
+               @Override
+               public Instant parse(String str) throws IllegalArgumentException {
+                       try {
+                               return Instant.parse(str);
+                       } catch (DateTimeParseException e) {
+                               throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e);
+                       }
+               }
+       }
+
+       static class UuidFormatter implements AttributeFormatter<UUID> {
+
+               @Override
+               public UUID parse(String str) throws IllegalArgumentException {
+                       return java.util.UUID.fromString(str);
+               }
+       }
+
+       static class UriFormatter implements AttributeFormatter<URI> {
+
+               @Override
+               public URI parse(String str) throws IllegalArgumentException {
+                       try {
+                               return new URI(str);
+                       } catch (URISyntaxException e) {
+                               throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e);
+                       }
+               }
+
+       }
+
+       static class StringFormatter implements AttributeFormatter<String> {
+
+               @Override
+               public String parse(String str) {
+                       return str;
+               }
+
+               @Override
+               public String format(String obj) {
+                       return obj;
+               }
+
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java
new file mode 100644 (file)
index 0000000..099be9f
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.api.acr;
+
+/** Standard names. */
+public enum CrName implements ContentNameSupplier {
+
+       /*
+        * TYPES
+        */
+       COLLECTION, // a collection type
+
+       /*
+        * ATTRIBUTES
+        */
+       UUID, // the UUID of a content
+
+       /*
+        * ATTRIBUTES FROM FILE SEMANTICS
+        */
+       CREATION_TIME, //
+       LAST_MODIFIED_TIME, //
+       SIZE, //
+       FILE_KEY, //
+       OWNER, //
+       GROUP, //
+       PERMISSIONS, //
+
+       /*
+        * CONTENT NAMES
+        */
+       ROOT,
+
+       //
+       ;
+
+       public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr";
+       public final static String CR_DEFAULT_PREFIX = "cr";
+       private final ContentName value;
+
+       CrName() {
+               value = toContentName();
+       }
+
+       @Override
+       public ContentName get() {
+               return value;
+       }
+
+       @Override
+       public String getNamespaceURI() {
+               return CR_NAMESPACE_URI;
+       }
+
+       @Override
+       public String getDefaultPrefix() {
+               return CR_DEFAULT_PREFIX;
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java
new file mode 100644 (file)
index 0000000..fa95daf
--- /dev/null
@@ -0,0 +1,387 @@
+package org.argeo.api.acr.fs;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchEvent.Modifier;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public abstract class AbstractFsPath<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
+       private final FS fs;
+       /** null for non absolute paths */
+       private final ST fileStore;
+
+       private final String[] segments;// null means root
+       private final boolean absolute;
+
+       private final String separator;
+
+       // optim
+       private final int hashCode;
+
+       public AbstractFsPath(FS filesSystem, String path) {
+               if (path == null)
+                       throw new IllegalArgumentException("Path cannot be null");
+               this.fs = filesSystem;
+               this.separator = fs.getSeparator();
+               // TODO deal with both path and separator being empty strings
+               if (path.equals(separator)) {// root
+                       this.segments = null;
+                       this.absolute = true;
+                       this.hashCode = 0;
+                       this.fileStore = fs.getBaseFileStore();
+                       return;
+               } else if (path.equals("")) {// empty path
+                       this.segments = new String[] { "" };
+                       this.absolute = false;
+                       this.hashCode = "".hashCode();
+                       this.fileStore = null;
+                       return;
+               }
+
+               this.absolute = path.startsWith(toStringRoot());
+
+               String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
+                               path.endsWith(separator) ? path.length() - separator.length() : path.length());
+               this.segments = trimmedPath.split(separator);
+               // clean up
+               for (int i = 0; i < this.segments.length; i++) {
+                       this.segments[i] = cleanUpSegment(this.segments[i]);
+               }
+               this.hashCode = this.segments[this.segments.length - 1].hashCode();
+
+               this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
+       }
+
+       protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
+               this.segments = segments;
+               this.absolute = absolute;
+               this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
+               this.separator = filesSystem.getSeparator();
+//             super(path, path == null ? true : absolute, filesSystem.getSeparator());
+//             assert path == null ? absolute == true : true;
+               this.fs = filesSystem;
+//             this.path = path;
+//             this.absolute = path == null ? true : absolute;
+               if (isAbsolute() && fileStore == null)
+                       throw new IllegalArgumentException("Absolute path requires a file store");
+               if (!isAbsolute() && fileStore != null)
+                       throw new IllegalArgumentException("A file store should not be provided for a relative path");
+               this.fileStore = fileStore;
+               assert !(absolute && fileStore == null);
+       }
+
+       protected Path retrieve(String path) {
+               return getFileSystem().getPath(path);
+       }
+
+       @Override
+       public FS getFileSystem() {
+               return fs;
+       }
+
+       public ST getFileStore() {
+               return fileStore;
+       }
+
+       @Override
+       public boolean isAbsolute() {
+               return absolute;
+       }
+
+       @Override
+       public URI toUri() {
+               try {
+                       return new URI(fs.provider().getScheme(), toString(), null);
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot create URI for " + toString(), e);
+               }
+       }
+
+       @Override
+       public Path toAbsolutePath() {
+               if (isAbsolute())
+                       return this;
+               // FIXME it doesn't seem right
+               return newInstance(getSegments(), true);
+       }
+
+       @Override
+       public Path toRealPath(LinkOption... options) throws IOException {
+               return this;
+       }
+
+       @Override
+       public File toFile() {
+               throw new UnsupportedOperationException();
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       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));
+       }
+
+       public final Path resolve(String other) {
+               return resolve(retrieve(other));
+       }
+
+       public boolean startsWith(Path other) {
+               return toString().startsWith(other.toString());
+       }
+
+       public boolean endsWith(Path other) {
+               return toString().endsWith(other.toString());
+       }
+
+       @Override
+       public Path normalize() {
+               // always normalized
+               return this;
+       }
+
+       @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 int compareTo(Path other) {
+               return toString().compareTo(other.toString());
+       }
+
+       public Path resolve(Path other) {
+               AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
+               if (otherPath.isAbsolute())
+                       return other;
+               String[] newPath;
+               if (isRoot()) {
+                       newPath = new String[otherPath.segments.length];
+                       System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
+               } else {
+                       newPath = new String[segments.length + otherPath.segments.length];
+                       System.arraycopy(segments, 0, newPath, 0, segments.length);
+                       System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
+               }
+               if (!absolute)
+                       return newInstance(newPath, absolute);
+               else {
+                       return newInstance(toString(newPath));
+               }
+       }
+
+       public Path relativize(Path other) {
+               if (equals(other))
+                       return newInstance("");
+               if (other.toString().startsWith(this.toString())) {
+                       String p1 = toString();
+                       String p2 = other.toString();
+                       String relative = p2.substring(p1.length(), p2.length());
+                       if (relative.charAt(0) == '/')
+                               relative = relative.substring(1);
+                       return newInstance(relative);
+               }
+               throw new IllegalArgumentException(other + " cannot be relativized against " + this);
+       }
+
+       /*
+        * FACTORIES
+        */
+       protected abstract AbstractFsPath<FS, ST> newInstance(String path);
+
+       protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
+
+       /*
+        * CUSTOMISATIONS
+        */
+       protected String toStringRoot() {
+               return separator;
+       }
+
+       protected String cleanUpSegment(String segment) {
+               return segment;
+       }
+
+       protected boolean isRoot() {
+               return segments == null;
+       }
+
+       protected boolean isEmpty() {
+               return segments.length == 1 && "".equals(segments[0]);
+       }
+
+       /*
+        * PATH OPERATIONS
+        */
+       public AbstractFsPath<FS, ST> getRoot() {
+               return newInstance(toStringRoot());
+       }
+
+       public AbstractFsPath<FS, ST> getParent() {
+               if (isRoot())
+                       return null;
+               // FIXME empty path?
+               if (segments.length == 1)// first level
+                       return newInstance(toStringRoot());
+               String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
+               if (!absolute)
+                       return newInstance(parentPath, absolute);
+               else
+                       return newInstance(toString(parentPath));
+       }
+
+       public AbstractFsPath<FS, ST> getFileName() {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[segments.length - 1]);
+       }
+
+       public int getNameCount() {
+               if (isRoot())
+                       return 0;
+               return segments.length;
+       }
+
+       public AbstractFsPath<FS, ST> getName(int index) {
+               if (isRoot())
+                       return null;
+               return newInstance(segments[index]);
+       }
+
+       public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
+               if (isRoot())
+                       return null;
+               String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
+               return newInstance(parentPath, false);
+       }
+
+       public boolean startsWith(String other) {
+               return toString().startsWith(other);
+       }
+
+       public boolean endsWith(String other) {
+               return toString().endsWith(other);
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected String toString(String[] path) {
+               if (isRoot())
+                       return toStringRoot();
+               StringBuilder sb = new StringBuilder();
+               if (isAbsolute())
+                       sb.append(separator);
+               for (int i = 0; i < path.length; i++) {
+                       if (i != 0)
+                               sb.append(separator);
+                       sb.append(path[i]);
+               }
+               return sb.toString();
+       }
+
+       @Override
+       public String toString() {
+               return toString(segments);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof AbstractFsPath))
+                       return false;
+               AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
+
+               if (isRoot()) {// root
+                       if (other.isRoot())// root
+                               return true;
+                       else
+                               return false;
+               } else {
+                       if (other.isRoot())// root
+                               return false;
+               }
+               // non root
+               if (segments.length != other.segments.length)
+                       return false;
+               for (int i = 0; i < segments.length; i++) {
+                       if (!segments[i].equals(other.segments[i]))
+                               return false;
+               }
+               return true;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return newInstance(toString());
+       }
+
+       /*
+        * GETTERS / SETTERS
+        */
+       protected String[] getSegments() {
+               return segments;
+       }
+
+       protected String getSeparator() {
+               return separator;
+       }
+
+       /*
+        * UNSUPPORTED
+        */
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsStore.java
new file mode 100644 (file)
index 0000000..1294766
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.acr.fs;
+
+import java.nio.file.FileStore;
+
+public abstract class AbstractFsStore extends FileStore {
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java b/org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsSystem.java
new file mode 100644 (file)
index 0000000..3bf10f6
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.acr.fs;
+
+import java.nio.file.FileSystem;
+
+public abstract class AbstractFsSystem<ST extends AbstractFsStore> extends FileSystem {
+       public abstract ST getBaseFileStore();
+
+       public abstract ST getFileStore(String path);
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/AbstractContent.java
new file mode 100644 (file)
index 0000000..6bfc7cd
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.api.acr.spi;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.CrName;
+
+public abstract class AbstractContent extends AbstractMap<QName, Object> implements Content {
+
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+       protected abstract Iterable<QName> keys();
+
+       protected abstract void removeAttr(QName key);
+
+       @Override
+       public Set<Entry<QName, Object>> entrySet() {
+               Set<Entry<QName, Object>> result = new AttrSet();
+               return result;
+       }
+
+       @Override
+       public Class<?> getType(QName key) {
+               return String.class;
+       }
+
+       @Override
+       public boolean isMultiple(QName key) {
+               return false;
+       }
+
+       @Override
+       public <A> Optional<List<A>> getMultiple(QName key, Class<A> clss) {
+               Object value = get(key);
+               if (value == null)
+                       return null;
+               if (value instanceof List) {
+                       try {
+                               List<A> res = (List<A>) value;
+                               return Optional.of(res);
+                       } catch (ClassCastException e) {
+                               List<A> res = new ArrayList<>();
+                               List<?> lst = (List<?>) value;
+                               try {
+                                       for (Object o : lst) {
+                                               A item = (A) o;
+                                               res.add(item);
+                                       }
+                                       return Optional.of(res);
+                               } catch (ClassCastException e1) {
+                                       return Optional.empty();
+                               }
+                       }
+               } else {// singleton
+                       try {
+                               A res = (A) value;
+                               return Optional.of(Collections.singletonList(res));
+                       } catch (ClassCastException e) {
+                               return Optional.empty();
+                       }
+               }
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public String getPath() {
+               List<Content> ancestors = new ArrayList<>();
+               collectAncestors(ancestors, this);
+               StringBuilder path = new StringBuilder();
+               for (Content c : ancestors) {
+                       QName name = c.getName();
+                       // FIXME
+                       if (!CrName.ROOT.get().equals(name))
+                               path.append('/').append(name);
+               }
+               return path.toString();
+       }
+
+       private void collectAncestors(List<Content> ancestors, Content content) {
+               if (content == null)
+                       return;
+               ancestors.add(0, content);
+               collectAncestors(ancestors, content.getParent());
+       }
+
+       /*
+        * UTILITIES
+        */
+       protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+               // check whether clss is Object.class
+               return clss.isAssignableFrom(Object.class);
+       }
+
+       @Override
+       public String toString() {
+               return "content " + getPath();
+       }
+
+       /*
+        * SUB CLASSES
+        */
+
+       class AttrSet extends AbstractSet<Entry<QName, Object>> {
+
+               @Override
+               public Iterator<Entry<QName, Object>> iterator() {
+                       final Iterator<QName> keys = keys().iterator();
+                       Iterator<Entry<QName, Object>> it = new Iterator<Map.Entry<QName, Object>>() {
+
+                               QName key = null;
+
+                               @Override
+                               public boolean hasNext() {
+                                       return keys.hasNext();
+                               }
+
+                               @Override
+                               public Entry<QName, Object> next() {
+                                       key = keys.next();
+                                       // TODO check type
+                                       Optional<?> value = get(key, Object.class);
+                                       assert !value.isEmpty();
+                                       AbstractMap.SimpleEntry<QName, Object> entry = new SimpleEntry<>(key, value.get());
+                                       return entry;
+                               }
+
+                               @Override
+                               public void remove() {
+                                       if (key != null) {
+                                               AbstractContent.this.removeAttr(key);
+                                       } else {
+                                               throw new IllegalStateException("Iteration has not started");
+                                       }
+                               }
+
+                       };
+                       return it;
+               }
+
+               @Override
+               public int size() {
+                       int count = 0;
+                       for (QName key : keys()) {
+                               count++;
+                       }
+                       return count;
+               }
+
+       }
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ContentProvider.java
new file mode 100644 (file)
index 0000000..d83cf49
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.Content;
+
+public interface ContentProvider {
+
+       Content get(ProvidedSession session, String mountPath, String relativePath);
+
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedContent.java
new file mode 100644 (file)
index 0000000..d9fc781
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.Content;
+
+public interface ProvidedContent extends Content {
+       ProvidedSession getSession();
+
+       ContentProvider getProvider();
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedRepository.java
new file mode 100644 (file)
index 0000000..c3052c3
--- /dev/null
@@ -0,0 +1,6 @@
+package org.argeo.api.acr.spi;
+
+import org.argeo.api.acr.ContentRepository;
+
+public interface ProvidedRepository extends ContentRepository {
+}
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java
new file mode 100644 (file)
index 0000000..f90d674
--- /dev/null
@@ -0,0 +1,68 @@
+package org.argeo.api.acr.spi;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+import org.argeo.api.acr.ContentNameSupplier;
+import org.argeo.api.acr.ContentSession;
+
+public interface ProvidedSession extends ContentSession, NamespaceContext {
+       ProvidedRepository getRepository();
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+       /** @return the bound namespace or null if not found */
+       String findNamespace(String prefix);
+
+       // TODO find the default prefix?
+       Set<String> findPrefixes(String namespaceURI);
+
+       /** To be overridden for optimisation, as it will be called a lot */
+       default String findPrefix(String namespaceURI) {
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               if (prefixes.isEmpty())
+                       return null;
+               return prefixes.iterator().next();
+       }
+
+       @Override
+       default String getNamespaceURI(String prefix) {
+               String namespaceURI = ContentNameSupplier.getStandardNamespaceURI(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix))
+                       return XMLConstants.NULL_NS_URI;
+               namespaceURI = findNamespace(prefix);
+               if (namespaceURI != null)
+                       return namespaceURI;
+               return XMLConstants.NULL_NS_URI;
+       }
+
+       @Override
+       default String getPrefix(String namespaceURI) {
+               String prefix = ContentNameSupplier.getStandardPrefix(namespaceURI);
+               if (prefix != null)
+                       return prefix;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return XMLConstants.DEFAULT_NS_PREFIX;
+               return findPrefix(namespaceURI);
+       }
+
+       @Override
+       default Iterator<String> getPrefixes(String namespaceURI) {
+               Iterator<String> standard = ContentNameSupplier.getStandardPrefixes(namespaceURI);
+               if (standard != null)
+                       return standard;
+               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                       return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator();
+               Set<String> prefixes = findPrefixes(namespaceURI);
+               assert prefixes != null;
+               return prefixes.iterator();
+       }
+
+}
diff --git a/org.argeo.api.cms/.classpath b/org.argeo.api.cms/.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/org.argeo.api.cms/.project b/org.argeo.api.cms/.project
new file mode 100644 (file)
index 0000000..17631c8
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.cms</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.cms/META-INF/.gitignore b/org.argeo.api.cms/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/org.argeo.api.cms/bnd.bnd b/org.argeo.api.cms/bnd.bnd
new file mode 100644 (file)
index 0000000..51c4e66
--- /dev/null
@@ -0,0 +1,4 @@
+Import-Package: \
+javax.security.*
+
+Export-Package: org.argeo.api.cms.*
\ No newline at end of file
diff --git a/org.argeo.api.cms/build.properties b/org.argeo.api.cms/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/AnonymousPrincipal.java
new file mode 100644 (file)
index 0000000..63ee348
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.api.cms;
+
+import java.security.Principal;
+
+/** Marker for anonymous users. */
+public final class AnonymousPrincipal implements Principal {
+       private final String name = CmsConstants.ROLE_ANONYMOUS;
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               return this == obj;
+       }
+
+       @Override
+       public String toString() {
+               return name.toString();
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java b/org.argeo.api.cms/src/org/argeo/api/cms/Cms2DSize.java
new file mode 100644 (file)
index 0000000..30b3d81
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.api.cms;
+
+/** A 2D size. */
+public class Cms2DSize {
+       private Integer width;
+       private Integer height;
+
+       public Cms2DSize() {
+
+       }
+
+       public Cms2DSize(Integer width, Integer height) {
+               super();
+               this.width = width;
+               this.height = height;
+       }
+
+       public Integer getWidth() {
+               return width;
+       }
+
+       public void setWidth(Integer width) {
+               this.width = width;
+       }
+
+       public Integer getHeight() {
+               return height;
+       }
+
+       public void setHeight(Integer height) {
+               this.height = height;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsApp.java
new file mode 100644 (file)
index 0000000..761191e
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.api.cms;
+
+import java.util.Set;
+
+/** An extensible user interface base on the CMS backend. */
+public interface CmsApp {
+       /**
+        * If {@link CmsUi#setData(String, Object)} is set with this property, it
+        * indicates a different UI (typically with another theming. The {@link CmsApp}
+        * can use this information, but it doesn't have to be set, in which case a
+        * default UI must be provided. The provided value must belong to the values
+        * returned by {@link CmsApp#getUiNames()}.
+        */
+       final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
+
+       Set<String> getUiNames();
+
+       CmsUi initUi(Object uiParent);
+
+       void refreshUi(CmsUi cmsUi, String state);
+
+       void setState(CmsUi cmsUi, String state);
+
+       CmsTheme getTheme(String uiName);
+
+       boolean allThemesAvailable();
+
+       void addCmsAppListener(CmsAppListener listener);
+
+       void removeCmsAppListener(CmsAppListener listener);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAppListener.java
new file mode 100644 (file)
index 0000000..55fcec5
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.cms;
+
+/** Notifies important events in a CMS App life cycle. */
+public interface CmsAppListener {
+       /** Theming has been updated and should be reloaded. */
+       void themingUpdated();
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsAuth.java
new file mode 100644 (file)
index 0000000..decea35
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.api.cms;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+/** The type of login context to use. */
+public enum CmsAuth {
+       NODE, USER, ANONYMOUS, DATA_ADMIN, SINGLE_USER, KEYRING;
+
+       public String getLoginContextName() {
+               return name();
+       }
+
+       @Override
+       public String toString() {
+               return getLoginContextName();
+       }
+
+       public LoginContext newLoginContext(CallbackHandler callbackHandler) throws LoginException {
+               return new LoginContext(getLoginContextName(), callbackHandler);
+       }
+
+       /*
+        * LOGIN CONTEXTS
+        */
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_NODE = NODE.getLoginContextName();
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_USER = USER.getLoginContextName();
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_ANONYMOUS = ANONYMOUS.getLoginContextName();
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_DATA_ADMIN = DATA_ADMIN.getLoginContextName();
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_SINGLE_USER = SINGLE_USER.getLoginContextName();
+       /** @deprecated Use enum instead. */
+       @Deprecated
+       public static final String LOGIN_CONTEXT_KEYRING = KEYRING.getLoginContextName();
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsConstants.java
new file mode 100644 (file)
index 0000000..8fe9846
--- /dev/null
@@ -0,0 +1,126 @@
+package org.argeo.api.cms;
+
+public interface CmsConstants {
+       /*
+        * DN ATTRIBUTES (RFC 4514)
+        */
+       String CN = "cn";
+       String L = "l";
+       String ST = "st";
+       String O = "o";
+       String OU = "ou";
+       String C = "c";
+       String STREET = "street";
+       String DC = "dc";
+       String UID = "uid";
+
+       /*
+        * STANDARD ATTRIBUTES
+        */
+       String LABELED_URI = "labeledUri";
+
+       /*
+        * COMMON NAMES
+        */
+       String NODE = "node";
+
+       /*
+        * JCR CONVENTIONS
+        */
+       String NODE_REPOSITORY = NODE;
+       String EGO_REPOSITORY = "ego";
+       String SYS_WORKSPACE = "sys";
+       String HOME_WORKSPACE = "home";
+       String SRV_WORKSPACE = "srv";
+       String GUESTS_WORKSPACE = "guests";
+       String PUBLIC_WORKSPACE = "public";
+       String SECURITY_WORKSPACE = "security";
+
+       /*
+        * BASE DNs
+        */
+       String DEPLOY_BASEDN = "ou=deploy,ou=node";
+
+       /*
+        * STANDARD VALUES
+        */
+       String DEFAULT = "default";
+
+       /*
+        * RESERVED ROLES
+        */
+       String ROLES_BASEDN = "ou=roles,ou=node";
+       String TOKENS_BASEDN = "ou=tokens,ou=node";
+       String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
+       String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
+       String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
+       // Special system groups that cannot be edited:
+       // user U anonymous = everyone
+       String ROLE_USER = "cn=user," + ROLES_BASEDN;
+       String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
+       // Account lifecycle
+       String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
+
+       /*
+        * PATHS
+        */
+       String PATH_DATA = "/data";
+       String PATH_JCR = "/jcr";
+       String PATH_FILES = "/files";
+       // String PATH_JCR_PUB = "/pub";
+
+       /*
+        * FILE SYSTEMS
+        */
+       String SCHEME_NODE = NODE;
+
+       /*
+        * KERBEROS
+        */
+       String NODE_SERVICE = NODE;
+
+       /*
+        * INIT FRAMEWORK PROPERTIES
+        */
+       String NODE_INIT = "argeo.node.init";
+       String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
+       String I18N_LOCALES = "argeo.i18n.locales";
+       // Node Security
+       String ROLES_URI = "argeo.node.roles.uri";
+       String TOKENS_URI = "argeo.node.tokens.uri";
+       /** URI to an LDIF file or LDAP server used as initialization or backend */
+       String USERADMIN_URIS = "argeo.node.useradmin.uris";
+       // Transaction manager
+       String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
+       String TRANSACTION_MANAGER_SIMPLE = "simple";
+       String TRANSACTION_MANAGER_BITRONIX = "bitronix";
+       // Node
+       /** Properties configuring the node repository */
+       String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
+       /** Additional standalone repositories, related to data models. */
+       String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
+       // HTTP
+       String HTTP_PORT = "org.osgi.service.http.port";
+       String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
+       /**
+        * The HTTP header used to convey the DN of a client verified by a reverse
+        * proxy. Typically SSL_CLIENT_S_DN for Apache.
+        */
+       String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
+
+       /*
+        * PIDs
+        */
+       String NODE_STATE_PID = "org.argeo.api.state";
+       String NODE_DEPLOYMENT_PID = "org.argeo.api.deployment";
+       String NODE_INSTANCE_PID = "org.argeo.api.instance";
+
+       String NODE_KEYRING_PID = "org.argeo.api.keyring";
+       String NODE_FS_PROVIDER_PID = "org.argeo.api.fsProvider";
+
+       /*
+        * FACTORY PIDs
+        */
+       String NODE_REPOS_FACTORY_PID = "org.argeo.api.repos";
+       String NODE_USER_ADMIN_PID = "org.argeo.api.userAdmin";
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsContext.java
new file mode 100644 (file)
index 0000000..fa26b25
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.api.cms;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A logical view on this CMS instance, independently of a particular launch or
+ * deployment.
+ */
+public interface CmsContext {
+       /**
+        * To be used as an identifier of a workgroup, typically as a value for the
+        * 'businessCategory' attribute in LDAP.
+        */
+       public final static String WORKGROUP = "workgroup";
+
+       Locale getDefaultLocale();
+
+       List<Locale> getLocales();
+
+       Long getAvailableSince();
+
+       
+       /** Mark this group as a workgroup */
+       void createWorkgroup(String groupDn);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsDeployment.java
new file mode 100644 (file)
index 0000000..5893d2e
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.api.cms;
+
+import java.util.Dictionary;
+
+/** A configured node deployment. */
+public interface CmsDeployment {
+
+       void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
+
+       Dictionary<String, Object> getProps(String factoryPid, String cn);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEditable.java
new file mode 100644 (file)
index 0000000..2deca01
--- /dev/null
@@ -0,0 +1,57 @@
+package org.argeo.api.cms;
+
+/** Abstraction of a simple edition life cycle. */
+public interface CmsEditable {
+
+       /** Whether the calling thread can edit, the value is immutable */
+       public Boolean canEdit();
+
+       public Boolean isEditing();
+
+       public void startEditing();
+
+       public void stopEditing();
+
+       public static CmsEditable NON_EDITABLE = new CmsEditable() {
+
+               @Override
+               public void stopEditing() {
+               }
+
+               @Override
+               public void startEditing() {
+               }
+
+               @Override
+               public Boolean isEditing() {
+                       return false;
+               }
+
+               @Override
+               public Boolean canEdit() {
+                       return false;
+               }
+       };
+
+       public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
+
+               @Override
+               public void stopEditing() {
+               }
+
+               @Override
+               public void startEditing() {
+               }
+
+               @Override
+               public Boolean isEditing() {
+                       return true;
+               }
+
+               @Override
+               public Boolean canEdit() {
+                       return true;
+               }
+       };
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsEvent.java
new file mode 100644 (file)
index 0000000..b5dccbe
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.api.cms;
+
+/**
+ * Can be applied to {@link Enum}s in order to define events used by
+ * {@link CmsView#sendEvent(String, java.util.Map)}.
+ */
+public interface CmsEvent {
+       String name();
+
+       default String topic() {
+               return getTopicBase() + "/" + name();
+       }
+
+       default         String getTopicBase() {
+               return "argeo/cms";
+       }
+
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsImageManager.java
new file mode 100644 (file)
index 0000000..8c637b8
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.api.cms;
+
+import java.io.InputStream;
+
+/** Read and write access to images. */
+public interface CmsImageManager<V, M> {
+       /** Load image in control */
+       public Boolean load(M node, V control, Cms2DSize size);
+
+       /** @return (0,0) if not available */
+       public Cms2DSize getImageSize(M node);
+
+       /**
+        * The related &lt;img&gt; tag, with src, width and height set.
+        * 
+        * @return null if not available
+        */
+       public String getImageTag(M node);
+
+       /**
+        * The related &lt;img&gt; tag, with url, width and height set. Caller must
+        * close the tag (or add additional attributes).
+        * 
+        * @return null if not available
+        */
+       public StringBuilder getImageTagBuilder(M node, Cms2DSize size);
+
+       /**
+        * Returns the remotely accessible URL of the image (registering it if
+        * needed) @return null if not available
+        */
+       public String getImageUrl(M node);
+
+//     public Binary getImageBinary(Node node) throws RepositoryException;
+
+//     public Image getSwtImage(Node node) throws RepositoryException;
+
+       /** @return URL */
+       public String uploadImage(M context, M uploadFolder, String fileName, InputStream in, String contentType);
+
+       @Deprecated
+       default String uploadImage(M uploadFolder, String fileName, InputStream in) {
+               System.err.println("Context must be provided to " + CmsImageManager.class.getName());
+               return uploadImage(null, uploadFolder, fileName, in, null);
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsLog.java
new file mode 100644 (file)
index 0000000..3454dfc
--- /dev/null
@@ -0,0 +1,203 @@
+package org.argeo.api.cms;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * A Commons Logging / SLF4J style logging utilities wrapping a standard Java
+ * platform {@link Logger}.
+ */
+public interface CmsLog {
+       Logger getLogger();
+
+       default boolean isDebugEnabled() {
+               return getLogger().isLoggable(Level.DEBUG);
+       }
+
+       default boolean isErrorEnabled() {
+               return getLogger().isLoggable(Level.ERROR);
+       }
+
+       default boolean isInfoEnabled() {
+               return getLogger().isLoggable(Level.INFO);
+       }
+
+       default boolean isTraceEnabled() {
+               return getLogger().isLoggable(Level.TRACE);
+       }
+
+       default boolean isWarnEnabled() {
+               return getLogger().isLoggable(Level.WARNING);
+       }
+
+       /*
+        * TRACE
+        */
+
+       default void trace(String message) {
+               getLogger().log(Level.TRACE, message);
+       }
+
+       default void trace(Supplier<String> message) {
+               getLogger().log(Level.TRACE, message);
+       }
+
+       default void trace(Object message) {
+               getLogger().log(Level.TRACE, Objects.requireNonNull(message));
+       }
+
+       default void trace(String message, Throwable t) {
+               getLogger().log(Level.TRACE, message, t);
+       }
+
+       default void trace(Object message, Throwable t) {
+               trace(Objects.requireNonNull(message).toString(), t);
+       }
+
+       default void trace(String format, Object... arguments) {
+               getLogger().log(Level.TRACE, format, arguments);
+       }
+
+       /*
+        * DEBUG
+        */
+
+       default void debug(String message) {
+               getLogger().log(Level.DEBUG, message);
+       }
+
+       default void debug(Supplier<String> message) {
+               getLogger().log(Level.DEBUG, message);
+       }
+
+       default void debug(Object message) {
+               getLogger().log(Level.DEBUG, message);
+       }
+
+       default void debug(String message, Throwable t) {
+               getLogger().log(Level.DEBUG, message, t);
+       }
+
+       default void debug(Object message, Throwable t) {
+               debug(Objects.requireNonNull(message).toString(), t);
+       }
+
+       default void debug(String format, Object... arguments) {
+               getLogger().log(Level.DEBUG, format, arguments);
+       }
+
+       /*
+        * INFO
+        */
+
+       default void info(String message) {
+               getLogger().log(Level.INFO, message);
+       }
+
+       default void info(Supplier<String> message) {
+               getLogger().log(Level.INFO, message);
+       }
+
+       default void info(Object message) {
+               getLogger().log(Level.INFO, message);
+       }
+
+       default void info(String message, Throwable t) {
+               getLogger().log(Level.INFO, message, t);
+       }
+
+       default void info(Object message, Throwable t) {
+               info(Objects.requireNonNull(message).toString(), t);
+       }
+
+       default void info(String format, Object... arguments) {
+               getLogger().log(Level.INFO, format, arguments);
+       }
+
+       /*
+        * WARN
+        */
+
+       default void warn(String message) {
+               getLogger().log(Level.WARNING, message);
+       }
+
+       default void warn(Supplier<String> message) {
+               getLogger().log(Level.WARNING, message);
+       }
+
+       default void warn(Object message) {
+               getLogger().log(Level.WARNING, message);
+       }
+
+       default void warn(String message, Throwable t) {
+               getLogger().log(Level.WARNING, message, t);
+       }
+
+       default void warn(Object message, Throwable t) {
+               warn(Objects.requireNonNull(message).toString(), t);
+       }
+
+       default void warn(String format, Object... arguments) {
+               getLogger().log(Level.WARNING, format, arguments);
+       }
+
+       /*
+        * ERROR
+        */
+
+       default void error(String message) {
+               getLogger().log(Level.ERROR, message);
+       }
+
+       default void error(Supplier<String> message) {
+               getLogger().log(Level.ERROR, message);
+       }
+
+       default void error(Object message) {
+               getLogger().log(Level.ERROR, message);
+       }
+
+       default void error(String message, Throwable t) {
+               getLogger().log(Level.ERROR, message, t);
+       }
+
+       default void error(Object message, Throwable t) {
+               error(Objects.requireNonNull(message).toString(), t);
+       }
+
+       default void error(String format, Object... arguments) {
+               getLogger().log(Level.ERROR, format, arguments);
+       }
+
+       /*
+        * STATIC UTILITIES
+        */
+
+       static CmsLog getLog(Class<?> clss) {
+               return getLog(Objects.requireNonNull(clss).getName());
+       }
+
+       static CmsLog getLog(String name) {
+               Logger logger = System.getLogger(Objects.requireNonNull(name));
+               return new LoggerWrapper(logger);
+       }
+
+       /** A trivial implementation wrapping a platform logger. */
+       static class LoggerWrapper implements CmsLog {
+               private final Logger logger;
+
+               LoggerWrapper(Logger logger) {
+                       this.logger = logger;
+               }
+
+               @Override
+               public Logger getLogger() {
+                       return logger;
+               }
+
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java
new file mode 100644 (file)
index 0000000..ea9d10b
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.api.cms;
+
+import java.time.ZonedDateTime;
+import java.util.Locale;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+
+/** An authenticated user session. */
+public interface CmsSession {
+       final static String USER_DN = "DN";
+       final static String SESSION_UUID = "entryUUID";
+       final static String SESSION_LOCAL_ID = "uniqueIdentifier";
+
+       UUID getUuid();
+
+       String getUserRole();
+
+       LdapName getUserDn();
+
+       String getLocalId();
+
+       String getDisplayName();
+//     Authorization getAuthorization();
+
+       Subject getSubject();
+
+       boolean isAnonymous();
+
+       ZonedDateTime getCreationTime();
+
+       ZonedDateTime getEnd();
+
+       Locale getLocale();
+
+       boolean isValid();
+
+       void registerView(String uid, Object view);
+
+       void addOnCloseCallback(Consumer<CmsSession> onClose);
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSessionId.java
new file mode 100644 (file)
index 0000000..0e47789
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.api.cms;
+
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+
+/**
+ * The ID of a {@link CmsSession}, which must be available in the private
+ * credentials of an authenticated {@link Subject}.
+ */
+public class CmsSessionId {
+       private final UUID uuid;
+
+       public CmsSessionId(UUID value) {
+               if (value == null)
+                       throw new IllegalArgumentException("Value cannot be null");
+               this.uuid = value;
+       }
+
+       public UUID getUuid() {
+               return uuid;
+       }
+
+       @Override
+       public int hashCode() {
+               return uuid.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               return obj instanceof CmsSessionId && ((CmsSessionId) obj).getUuid().equals(uuid);
+       }
+
+       @Override
+       public String toString() {
+               return "Node Session " + uuid;
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsState.java
new file mode 100644 (file)
index 0000000..ed8698f
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.api.cms;
+
+/** A running node process. */
+public interface CmsState {
+       String getHostname();
+
+       Long getAvailableSince();
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsStyle.java
new file mode 100644 (file)
index 0000000..8444e2f
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.api.cms;
+
+/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
+public interface CmsStyle {
+       String name();
+
+       /** @deprecated use {@link #style()} instead. */
+       @Deprecated
+       default String toStyleClass() {
+               return style();
+       }
+
+       default String style() {
+               String classPrefix = getClassPrefix();
+               return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
+       }
+
+       default String getClassPrefix() {
+               return "";
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsTheme.java
new file mode 100644 (file)
index 0000000..50c3b1f
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.api.cms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+/** A CMS theme which can be applied to web apps as well as desktop apps. */
+public interface CmsTheme {
+       /** Unique ID of this theme. */
+       String getThemeId();
+
+       /**
+        * Load a resource as an input stream, base don its relative path, or
+        * <code>null</code> if not found
+        */
+       InputStream getResourceAsStream(String resourceName) throws IOException;
+
+       /** Relative paths to standard web CSS. */
+       Set<String> getWebCssPaths();
+
+       /** Relative paths to RAP specific CSS. */
+       Set<String> getRapCssPaths();
+
+       /** Relative paths to SWT specific CSS. */
+       Set<String> getSwtCssPaths();
+
+       /** Relative paths to images such as icons. */
+       Set<String> getImagesPaths();
+
+       /** Relative paths to fonts. */
+       Set<String> getFontsPaths();
+
+       /** Tags to be added to the header section of the HTML page. */
+       String getHtmlHeaders();
+
+       /** The HTML body to use. */
+       String getBodyHtml();
+
+       /** The default icon size (typically the smallest). */
+       Integer getDefaultIconSize();
+
+       /** Loads one of the relative path provided by the other methods. */
+       InputStream loadPath(String path) throws IOException;
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsUi.java
new file mode 100644 (file)
index 0000000..fd91c6e
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.api.cms;
+
+public interface CmsUi {
+       Object getData(String key);
+       void setData(String key, Object value);
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsView.java
new file mode 100644 (file)
index 0000000..c7ca1e9
--- /dev/null
@@ -0,0 +1,97 @@
+package org.argeo.api.cms;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.login.LoginContext;
+
+/** Provides interaction with the CMS system. */
+public interface CmsView {
+       final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
+       // String KEY = "org.argeo.cms.ui.view";
+
+       String getUid();
+
+       UxContext getUxContext();
+
+       // NAVIGATION
+       void navigateTo(String state);
+
+       // SECURITY
+       void authChange(LoginContext loginContext);
+
+       void logout();
+
+       // void registerCallbackHandler(CallbackHandler callbackHandler);
+
+       // SERVICES
+       void exception(Throwable e);
+
+       CmsImageManager<?, ?> getImageManager();
+
+       boolean isAnonymous();
+
+       /**
+        * Send an event to this topic. Does nothing by default., but if implemented it
+        * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
+        */
+       default void sendEvent(String topic, Map<String, Object> properties) {
+
+       }
+
+       /**
+        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
+        * one single parameter.
+        */
+       default void sendEvent(String topic, String param, Object value) {
+               Map<String, Object> properties = new HashMap<>();
+               properties.put(param, value);
+               sendEvent(topic, properties);
+       }
+
+       default void applyStyles(Object widget) {
+
+       }
+
+       default <T> T doAs(PrivilegedAction<T> action) {
+               throw new UnsupportedOperationException();
+       }
+
+       default Void runAs(Runnable runnable) {
+               return doAs(new PrivilegedAction<Void>() {
+
+                       @Override
+                       public Void run() {
+                               if (runnable != null)
+                                       runnable.run();
+                               return null;
+                       }
+               });
+       }
+
+       default void stateChanged(String state, String title) {
+       }
+
+       default CmsSession getCmsSession() {
+               throw new UnsupportedOperationException();
+       }
+
+       default Object getData(String key) {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings("unchecked")
+       default <T> T getUiContext(Class<T> clss) {
+               return (T) getData(clss.getName());
+       }
+
+       default <T> void setUiContext(Class<T> clss, T instance) {
+               setData(clss.getName(), instance);
+       }
+
+       default void setData(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/DataAdminPrincipal.java
new file mode 100644 (file)
index 0000000..bc12bcb
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.api.cms;
+
+import java.security.Principal;
+
+/** Allows to modify any data. */
+public final class DataAdminPrincipal implements Principal {
+       private final String name = CmsConstants.ROLE_DATA_ADMIN;
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               return obj instanceof DataAdminPrincipal;
+       }
+
+       @Override
+       public String toString() {
+               return name.toString();
+       }
+
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java b/org.argeo.api.cms/src/org/argeo/api/cms/MvcProvider.java
new file mode 100644 (file)
index 0000000..c1aa600
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.api.cms;
+
+import java.util.function.BiFunction;
+
+/**
+ * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
+ * order to create a view part (W) which can then be further configured. Such
+ * object can be used as services and reference other part of the model which
+ * are relevant for all created UI part.
+ */
+@FunctionalInterface
+public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
+       W createUiPart(V parent, M context);
+       
+       /**
+        * Whether this parent view is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isViewSupported(V parent) {
+               return true;
+       }
+
+       /**
+        * Whether this context is supported.
+        * 
+        * @return true by default.
+        */
+       default boolean isModelSupported(M context) {
+               return true;
+       }
+
+       default W apply(V parent, M context) {
+               if (!isViewSupported(parent))
+                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
+               if (!isModelSupported(context))
+                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
+               return createUiPart(parent, context);
+       }
+
+       default W createUiPart(V parent) {
+               return createUiPart(parent, null);
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java b/org.argeo.api.cms/src/org/argeo/api/cms/UxContext.java
new file mode 100644 (file)
index 0000000..fb99178
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.api.cms;
+
+public interface UxContext {
+       boolean isPortrait();
+
+       boolean isLandscape();
+
+       boolean isSquare();
+
+       boolean isSmall();
+
+       /**
+        * Is a production environment (must be false by default, and be explicitly
+        * set during the CMS deployment). When false, it can activate additional UI
+        * capabilities in order to facilitate QA.
+        */
+       boolean isMasterData();
+}
diff --git a/org.argeo.api.uuid/.classpath b/org.argeo.api.uuid/.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/org.argeo.api.uuid/.project b/org.argeo.api.uuid/.project
new file mode 100644 (file)
index 0000000..be973cd
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.api.uuid</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.api.uuid/META-INF/.gitignore b/org.argeo.api.uuid/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4c36ca9
--- /dev/null
@@ -0,0 +1 @@
+x86_64/
\ No newline at end of file
diff --git a/org.argeo.api.uuid/bnd.bnd b/org.argeo.api.uuid/bnd.bnd
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/org.argeo.api.uuid/build.properties b/org.argeo.api.uuid/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/APM.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/APM.java
new file mode 100644 (file)
index 0000000..761f774
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.api.uuid;
+
+import java.io.Serializable;
+
+/** Package metadata for this package. */
+class APM implements Serializable {
+       /** Major version (equality means backward compatibility). */
+       static final int MAJOR = 2;
+       /** Minor version (if even, equality means forward compatibility). */
+       static final int MINOR = 3;
+       /** serialVersionUID to use for {@link Serializable} classes in this package. */
+       static final long SERIAL = (long) MAJOR << 32 | MINOR & 0xFFFFFFFFL;
+       /** Metadata version. */
+       private static final long serialVersionUID = 2L;
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractAsyncUuidFactory.java
new file mode 100644 (file)
index 0000000..52becc8
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.api.uuid;
+
+import java.security.DrbgParameters;
+import java.security.DrbgParameters.Capability;
+import java.security.SecureRandom;
+import java.security.SecureRandomParameters;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Execute {@link UUID} creations in {@link ForkJoinPool#commonPool()}. The goal
+ * is to provide good performance while staying within the parallelism defined
+ * for the system, so as to overwhelm it if many UUIDs are requested.
+ * Additionally, with regard to time based UUIDs, since we use
+ * {@link ConcurrentTimeUuidState}, which maintains one "clock sequence" per
+ * thread, we want to limit the number of threads accessing the actual
+ * generation method.
+ */
+public abstract class AbstractAsyncUuidFactory extends AbstractUuidFactory implements AsyncUuidFactory {
+       private SecureRandom secureRandom;
+       protected ConcurrentTimeUuidState timeUuidState;
+
+       private NodeIdSupplier nodeIdSupplier;
+       private long currentClockSequenceRange = 0;
+
+       public AbstractAsyncUuidFactory() {
+               secureRandom = createSecureRandom();
+               timeUuidState = new ConcurrentTimeUuidState(secureRandom, null);
+       }
+       /*
+        * ABSTRACT METHODS
+        */
+
+       protected abstract SecureRandom createSecureRandom();
+
+       /*
+        * STATE
+        */
+       public void reset() {
+               if (nodeIdSupplier == null)
+                       throw new IllegalStateException("No node id supplier available");
+               long nodeIdBase = nodeIdSupplier.get();
+               timeUuidState.reset(nodeIdBase, currentClockSequenceRange);
+       }
+
+       public void setNodeIdSupplier(NodeIdSupplier nodeIdSupplier) {
+               this.nodeIdSupplier = nodeIdSupplier;
+               reset();
+       }
+
+       public void setNodeIdSupplier(NodeIdSupplier nodeIdSupplier, long range) {
+               this.currentClockSequenceRange = range >= 0 ? range & 0x3F00 : range;
+               setNodeIdSupplier(nodeIdSupplier);
+       }
+
+       protected NodeIdSupplier getNodeIdSupplier() {
+               return nodeIdSupplier;
+       }
+
+       /**
+        * If positive, only clock_hi is taken from the argument (range & 0x3F00), if
+        * negative, the full range of possible values is used.
+        */
+       public void setCurrentClockSequenceRange(long range) {
+               this.currentClockSequenceRange = range >= 0 ? range & 0x3F00 : range;
+               reset();
+       }
+
+       /*
+        * SYNC OPERATIONS
+        */
+       protected UUID createRandomUUIDStrong() {
+               SecureRandomParameters parameters = secureRandom.getParameters();
+               if (parameters != null) {
+                       if (parameters instanceof DrbgParameters.Instantiation) {
+                               Capability capability = ((DrbgParameters.Instantiation) parameters).getCapability();
+                               if (capability.equals(DrbgParameters.Capability.PR_AND_RESEED)
+                                               || capability.equals(DrbgParameters.Capability.RESEED_ONLY)) {
+                                       secureRandom.reseed();
+                               }
+                       }
+               }
+               return createRandomUUID(secureRandom);
+       }
+
+       public UUID randomUUIDWeak() {
+               return createRandomUUID(ThreadLocalRandom.current());
+       }
+
+       protected UUID createTimeUUID() {
+               if (nodeIdSupplier == null)
+                       throw new IllegalStateException("No node id supplier available");
+               UUID uuid = new UUID(timeUuidState.getMostSignificantBits(), timeUuidState.getLeastSignificantBits());
+
+               assert uuid.version() == 1;
+               assert uuid.variant() == 2;
+               assert uuid.timestamp() == timeUuidState.getLastTimestamp();
+               assert uuid.clockSequence() == timeUuidState.getClockSequence();
+
+               return uuid;
+       }
+
+       /*
+        * ASYNC OPERATIONS (heavy)
+        */
+       protected CompletionStage<UUID> request(ForkJoinTask<UUID> newUuid) {
+               return CompletableFuture.supplyAsync(newUuid::invoke).minimalCompletionStage();
+       }
+
+       @Override
+       public CompletionStage<UUID> requestNameUUIDv5(UUID namespace, byte[] data) {
+               return request(futureNameUUIDv5(namespace, data));
+       }
+
+       @Override
+       public CompletionStage<UUID> requestNameUUIDv3(UUID namespace, byte[] data) {
+               return request(futureNameUUIDv3(namespace, data));
+       }
+
+       @Override
+       public CompletionStage<UUID> requestRandomUUIDStrong() {
+               return request(futureRandomUUIDStrong());
+       }
+
+       @Override
+       public CompletionStage<UUID> requestTimeUUID() {
+               return request(futureTimeUUID());
+       }
+
+       /*
+        * ASYNC OPERATIONS (light)
+        */
+       protected ForkJoinTask<UUID> submit(Callable<UUID> newUuid) {
+               return ForkJoinTask.adapt(newUuid);
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureNameUUIDv5(UUID namespace, byte[] data) {
+               return submit(() -> createNameUUIDv5(namespace, data));
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureNameUUIDv3(UUID namespace, byte[] data) {
+               return submit(() -> createNameUUIDv3(namespace, data));
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureRandomUUIDStrong() {
+               return submit(this::createRandomUUIDStrong);
+       }
+
+       @Override
+       public ForkJoinTask<UUID> futureTimeUUID() {
+               return submit(this::createTimeUUID);
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AbstractUuidFactory.java
new file mode 100644 (file)
index 0000000..1bbc439
--- /dev/null
@@ -0,0 +1,176 @@
+package org.argeo.api.uuid;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.time.temporal.Temporal;
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * Implementation of the basic RFC4122 algorithms.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122
+ */
+public abstract class AbstractUuidFactory implements UuidFactory {
+
+       /*
+        * TIME-BASED (version 1)
+        */
+
+       private final static long MOST_SIG_VERSION1 = (1l << 12);
+       private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
+
+       @Deprecated
+       protected UUID createTimeUUID(long timestamp, long clockSequence, byte[] node, int offset) {
+               Objects.requireNonNull(node, "Node array cannot be null");
+               if (node.length < offset + 6)
+                       throw new IllegalArgumentException("Node array must be at least 6 bytes long");
+
+               long mostSig = MOST_SIG_VERSION1 // base for version 1 UUID
+                               | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
+                               | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
+                               | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+
+               long leastSig = LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
+                               | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res
+                               | ((clockSequence & 0xFF) << 48) // clk_seq_low
+                               | (node[offset] & 0xFFL) //
+                               | ((node[offset + 1] & 0xFFL) << 8) //
+                               | ((node[offset + 2] & 0xFFL) << 16) //
+                               | ((node[offset + 3] & 0xFFL) << 24) //
+                               | ((node[offset + 4] & 0xFFL) << 32) //
+                               | ((node[offset + 5] & 0xFFL) << 40); //
+               UUID uuid = new UUID(mostSig, leastSig);
+
+               // tests
+               assert uuid.version() == 1;
+               assert uuid.variant() == 2;
+               assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
+               assert uuid.timestamp() == timestamp;
+               assert uuid.clockSequence() == clockSequence;
+               return uuid;
+       }
+
+       @Deprecated
+       protected UUID timeUUID(Temporal time, long clockSequence, byte[] node, int offset) {
+               // TODO add checks
+               Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, time);
+               // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
+               long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100;
+               return createTimeUUID(timestamp, clockSequence, node, offset);
+       }
+
+       /*
+        * NAME BASED (version 3 and 5)
+        */
+       protected UUID createNameUUIDv5(UUID namespace, byte[] name) {
+               return createNameUUIDv5Static(namespace, name);
+       }
+
+       static UUID createNameUUIDv5Static(UUID namespace, byte[] name) {
+               Objects.requireNonNull(namespace, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+
+               byte[] bytes = sha1(UuidBinaryUtils.toBytes(namespace), name);
+               bytes[6] &= 0x0f;
+               bytes[6] |= 0x50;// v5
+               bytes[8] &= 0x3f;
+               bytes[8] |= 0x80;// variant 1
+               UUID result = UuidBinaryUtils.fromBytes(bytes, 0);
+               return result;
+       }
+
+       protected UUID createNameUUIDv3(UUID namespace, byte[] name) {
+               return createNameUUIDv3Static(namespace, name);
+       }
+
+       static UUID createNameUUIDv3Static(UUID namespace, byte[] name) {
+               Objects.requireNonNull(namespace, "Namespace cannot be null");
+               Objects.requireNonNull(name, "Name cannot be null");
+
+               byte[] arr = new byte[name.length + 16];
+               UuidBinaryUtils.copyBytes(namespace, arr, 0);
+               System.arraycopy(name, 0, arr, 16, name.length);
+               return UUID.nameUUIDFromBytes(arr);
+       }
+
+       /*
+        * RANDOM v4
+        */
+       protected UUID createRandomUUID(Random random) {
+               byte[] arr = new byte[16];
+               random.nextBytes(arr);
+               arr[6] &= 0x0f;
+               arr[6] |= 0x40;// v4
+               arr[8] &= 0x3f;
+               arr[8] |= 0x80;// variant 1
+               return UuidBinaryUtils.fromBytes(arr);
+       }
+
+       /*
+        * SPI UTILITIES
+        */
+       /** Guarantees that a byte array of length 6 will be returned. */
+       protected static byte[] toNodeIdBytes(byte[] source, int offset) {
+               if (source == null)
+                       return null;
+               if (offset < 0 || offset + 6 > source.length)
+                       throw new ArrayIndexOutOfBoundsException(offset);
+               byte[] nodeId = new byte[6];
+               System.arraycopy(source, offset, nodeId, 0, 6);
+               return nodeId;
+       }
+
+       /**
+        * Force this node id to be identified as no MAC address.
+        * 
+        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+        */
+       protected static void forceToNoMacAddress(byte[] nodeId, int offset) {
+               assert nodeId != null && offset < nodeId.length;
+               nodeId[offset] = (byte) (nodeId[offset] | 1);
+       }
+
+       /*
+        * DIGEST UTILITIES
+        */
+
+       private final static String MD5 = "MD5";
+       private final static String SHA1 = "SHA1";
+
+       protected static byte[] sha1(byte[]... bytes) {
+               MessageDigest digest = getSha1Digest();
+               for (byte[] arr : bytes)
+                       digest.update(arr);
+               byte[] checksum = digest.digest();
+               return checksum;
+       }
+
+       protected static byte[] md5(byte[]... bytes) {
+               MessageDigest digest = getMd5Digest();
+               for (byte[] arr : bytes)
+                       digest.update(arr);
+               byte[] checksum = digest.digest();
+               return checksum;
+       }
+
+       protected static MessageDigest getSha1Digest() {
+               return getDigest(SHA1);
+       }
+
+       protected static MessageDigest getMd5Digest() {
+               return getDigest(MD5);
+       }
+
+       private static MessageDigest getDigest(String name) {
+               try {
+                       return MessageDigest.getInstance(name);
+               } catch (NoSuchAlgorithmException e) {
+                       throw new UnsupportedOperationException(name + " digest is not avalaible", e);
+               }
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/AsyncUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/AsyncUuidFactory.java
new file mode 100644 (file)
index 0000000..bd92f56
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ForkJoinTask;
+
+/** A {@link UUID} factory which creates the UUIDs asynchronously. */
+public interface AsyncUuidFactory extends UuidFactory {
+       /*
+        * TIME-BASED (version 1)
+        */
+       CompletionStage<UUID> requestTimeUUID();
+
+       ForkJoinTask<UUID> futureTimeUUID();
+
+       /*
+        * NAME BASED (version 3 and 5)
+        */
+       CompletionStage<UUID> requestNameUUIDv5(UUID namespace, byte[] data);
+
+       CompletionStage<UUID> requestNameUUIDv3(UUID namespace, byte[] data);
+
+       ForkJoinTask<UUID> futureNameUUIDv5(UUID namespace, byte[] data);
+
+       ForkJoinTask<UUID> futureNameUUIDv3(UUID namespace, byte[] data);
+
+       /*
+        * RANDOM (version 4)
+        */
+       CompletionStage<UUID> requestRandomUUIDStrong();
+
+       ForkJoinTask<UUID> futureRandomUUIDStrong();
+
+       /*
+        * DEFAULTS
+        */
+       @Override
+       default UUID randomUUIDStrong() {
+               return futureRandomUUIDStrong().invoke();
+       }
+
+       @Override
+       default UUID timeUUID() {
+               return futureTimeUUID().invoke();
+       }
+
+       @Override
+       default UUID nameUUIDv5(UUID namespace, byte[] data) {
+               return futureNameUUIDv5(namespace, data).invoke();
+       }
+
+       @Override
+       default UUID nameUUIDv3(UUID namespace, byte[] data) {
+               return futureNameUUIDv3(namespace, data).invoke();
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BasicNameUuid.java
new file mode 100644 (file)
index 0000000..b883e0d
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/** A name based UUID (v3 or v5) whose construction values are not known. */
+public class BasicNameUuid extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       public BasicNameUuid(UUID uuid) {
+               super(uuid);
+               if ((uuid.version() != 5 && uuid.version() != 3) || uuid.variant() != 2)
+                       throw new IllegalArgumentException("The provided UUID is not a name-based UUID.");
+       }
+
+       /**
+        * Always returns <code>true</true> since it is unknown from which values it was
+        * constructed..
+        */
+       @Override
+       public boolean isOpaque() {
+               return true;
+       }
+
+       /**
+        * Whether the hash of this name UUID was generated with SHA-1 (v5) or with MD5
+        * (v3).
+        */
+       public boolean isSha1() {
+               return uuid.version() == 5;
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/BinaryNameUuid.java
new file mode 100644 (file)
index 0000000..6771c7e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * A name UUID whose binary data used for its construction is known. A new byte
+ * array is created and it is copied when retrieved, so this would be
+ * inefficient and memory consuming for big data amounts. This rather meant to
+ * be used for small binary data, such as certificates, etc.
+ */
+public class BinaryNameUuid extends BasicNameUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       protected final TypedUuid namespace;
+       protected final byte[] bytes;
+
+       /** Static builder (a {@link TypedUuidFactory} may be more efficient). */
+       public BinaryNameUuid(TypedUuid namespace, byte[] bytes, boolean sha1) {
+               this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes)
+                               : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, bytes), namespace, bytes);
+       }
+
+       /**
+        * Since no check is performed, the constructor is protected so that the object
+        * can only be built by the default methods of {@link TypedUuidFactory} (in this
+        * package) or by extending the class.
+        */
+       protected BinaryNameUuid(UUID uuid, TypedUuid namespace, byte[] bytes) {
+               super(uuid);
+               this.namespace = namespace;
+               this.bytes = new byte[bytes.length];
+               System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
+       }
+
+       /** The namespace used to build this UUID. */
+       public final TypedUuid getNamespace() {
+               return namespace;
+       }
+
+       /**
+        * A <strong>copy</strong> of the bytes which have been hashed. In order to
+        * access the byte array directly, the class must be extended.
+        */
+       public final byte[] getBytes() {
+               byte[] bytes = new byte[this.bytes.length];
+               System.arraycopy(this.bytes, 0, bytes, 0, this.bytes.length);
+               return bytes;
+       }
+
+       /** Always returns <code>false</code> since the construction value is known. */
+       @Override
+       public final boolean isOpaque() {
+               return false;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentTimeUuidState.java
new file mode 100644 (file)
index 0000000..5bab359
--- /dev/null
@@ -0,0 +1,318 @@
+package org.argeo.api.uuid;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.lang.System.Logger;
+import java.security.SecureRandom;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.argeo.api.uuid.UuidFactory.TimeUuidState;
+
+/**
+ * A simple base implementation of {@link TimeUuidState}, which maintains
+ * different clock sequences for each thread, based on a specified range. This
+ * range is defined as clock_seq_hi (cf. RFC4122) and only clock_seq_low is
+ * dynamically allocated. It means that there can be at most 256 parallel clock
+ * sequences. If that limit is reached, the clock sequence which has not be used
+ * for the most time is reallocated to the new thread. It is assumed that the
+ * context where time uUIDs will be generated will often be using thread pools
+ * (e.g. {@link ForkJoinPool#commonPool(), http server, database access, etc.)
+ * and that such reallocation won't have to happen too often.
+ */
+public class ConcurrentTimeUuidState implements UuidFactory.TimeUuidState {
+       private final static Logger logger = System.getLogger(ConcurrentTimeUuidState.class.getName());
+
+       /** The maximum possible value of the clocksequence. */
+       private final static int MAX_CLOCKSEQUENCE = 0x3F00;
+
+       private final ClockSequenceProvider clockSequenceProvider;
+       private final ThreadLocal<ConcurrentTimeUuidState.Holder> currentHolder;
+
+       private final Instant startInstant;
+       /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
+       private final long startTimeStamp;
+
+       private final Clock clock;
+       private final boolean useClockForMeasurement;
+
+       private long nodeIdBase;
+
+       public ConcurrentTimeUuidState(SecureRandom secureRandom, Clock clock) {
+               useClockForMeasurement = clock != null;
+               this.clock = clock != null ? clock : Clock.systemUTC();
+
+               Objects.requireNonNull(secureRandom);
+
+               // compute the start reference
+               startInstant = Instant.now(this.clock);
+               long nowVm = nowVm();
+               Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, startInstant);
+               startTimeStamp = TimeUuid.durationToTimestamp(duration) - nowVm;
+
+               clockSequenceProvider = new ClockSequenceProvider(secureRandom);
+
+               // initalise a state per thread
+               currentHolder = new ThreadLocal<>() {
+
+                       @Override
+                       protected ConcurrentTimeUuidState.Holder initialValue() {
+                               ConcurrentTimeUuidState.Holder value = new ConcurrentTimeUuidState.Holder();
+                               value.threadId = Thread.currentThread().getId();
+                               value.lastTimestamp = 0;
+                               clockSequenceProvider.newClockSequence(value);
+                               return value;
+                       }
+               };
+       }
+
+       /*
+        * TIME OPERATIONS
+        */
+
+       public long useTimestamp() {
+               Holder holder = currentHolder.get();
+               if (holder.clockSequence < 0) {
+                       clockSequenceProvider.newClockSequence(holder);
+               }
+
+               long previousTimestamp = holder.lastTimestamp;
+               long now = computeNow();
+
+               // rare case where we are sooner
+               // (e.g. if system time has changed in between and we use the clock)
+               if (previousTimestamp > now) {
+                       clockSequenceProvider.newClockSequence(holder);
+               }
+
+               // very unlikely case where it took less than 100ns between both
+               if (previousTimestamp == now) {
+                       try {
+                               Thread.sleep(0, 100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       now = computeNow();
+                       assert previousTimestamp != now;
+               }
+               holder.lastTimestamp = now;
+               return now;
+       }
+
+       private long computeNow() {
+               if (useClockForMeasurement) {
+                       Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, Instant.now(clock));
+                       return TimeUuid.durationToTimestamp(duration);
+               } else {
+                       return startTimeStamp + nowVm();
+               }
+       }
+
+       private long nowVm() {
+               return System.nanoTime() / 100;
+       }
+
+       @Override
+       public long getClockSequence() {
+               return currentHolder.get().clockSequence;
+       }
+
+       @Override
+       public long getLastTimestamp() {
+               return currentHolder.get().lastTimestamp;
+       }
+
+       protected void reset(long nodeIdBase, long range) {
+               synchronized (clockSequenceProvider) {
+                       this.nodeIdBase = nodeIdBase;
+                       clockSequenceProvider.reset(range);
+                       clockSequenceProvider.notifyAll();
+               }
+       }
+
+       @Override
+       public long getLeastSignificantBits() {
+               return currentHolder.get().leastSig;
+       }
+
+       @Override
+       public long getMostSignificantBits() {
+               long timestamp = useTimestamp();
+               long mostSig = UuidFactory.MOST_SIG_VERSION1 | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
+                               | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
+                               | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
+               return mostSig;
+       }
+
+       /*
+        * INTERNAL CLASSSES
+        */
+       private class Holder {
+               private long lastTimestamp;
+               private long clockSequence;
+               private long threadId;
+
+               private long leastSig;
+
+               @Override
+               public boolean equals(Object obj) {
+                       boolean isItself = this == obj;
+                       if (!isItself && clockSequence == ((Holder) obj).clockSequence)
+                               throw new IllegalStateException("There is another holder with the same clockSequence " + clockSequence);
+                       return isItself;
+               }
+
+               private void setClockSequence(long clockSequence) {
+                       this.clockSequence = clockSequence;
+                       this.leastSig = nodeIdBase // already computed node base
+                                       | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res
+                                       | ((clockSequence & 0xFF) << 48); // clk_seq_low
+               }
+
+               @Override
+               public String toString() {
+                       return "Holder " + clockSequence + ", threadId=" + threadId + ", lastTimestamp=" + lastTimestamp;
+               }
+
+       }
+
+       private static class ClockSequenceProvider {
+               /** Set to an illegal value. */
+               private long range = MAX_CLOCKSEQUENCE;// this is actually clk_seq_hi
+//             private int rangeSize = 256;
+               private volatile long min;
+               private volatile long max;
+               private final AtomicLong counter = new AtomicLong(-1);
+
+               private final SecureRandom secureRandom;
+
+               private final Map<Holder, Long> activeHolders = Collections.synchronizedMap(new WeakHashMap<>());
+
+               ClockSequenceProvider(SecureRandom secureRandom) {
+                       this.secureRandom = secureRandom;
+//                     reset(range);
+               }
+
+               synchronized void reset(long range) {
+                       // long min = secureRandom.nextInt(ConcurrentTimeUuidState.MAX_CLOCKSEQUENCE -
+                       // rangeSize);
+                       // long max = min + rangeSize;
+
+                       long min, max;
+                       if (range >= 0) {
+                               if (range > MAX_CLOCKSEQUENCE)
+                                       throw new IllegalArgumentException("Range " + Long.toHexString(range) + " is too big");
+                               long previousRange = this.range;
+                               this.range = range & 0x3F00;
+                               if (this.range != range) {
+                                       this.range = previousRange;
+                                       throw new IllegalArgumentException(
+                                                       "Range is not properly formatted: " + range + " (0x" + Long.toHexString(range) + ")");
+                               }
+
+                               min = this.range;
+                               max = min | 0xFF;
+                       } else {// full range
+                               this.range = range;
+                               min = 0;
+                               max = MAX_CLOCKSEQUENCE;
+                       }
+                       assert min == (int) min;
+                       assert max == (int) max;
+
+                       // TODO rather use assertions
+                       if (min >= max)
+                               throw new IllegalArgumentException("Minimum " + min + " is bigger than maximum " + max);
+                       if (min < 0 || min > MAX_CLOCKSEQUENCE)
+                               throw new IllegalArgumentException("Minimum " + min + " is not valid");
+                       if (max < 0 || max > MAX_CLOCKSEQUENCE)
+                               throw new IllegalArgumentException("Maximum " + max + " is not valid");
+                       this.min = min;
+                       this.max = max;
+
+                       Set<Holder> active = activeHolders.keySet();
+                       int activeCount = active.size();
+                       if (activeCount > getRangeSize())
+                               throw new IllegalStateException(
+                                               "There are too many holders for range [" + min + "," + max + "] : " + activeCount);
+
+                       // reset the counter with a random value in range
+                       long firstCount = min + secureRandom.nextInt(getRangeSize());
+                       counter.set(firstCount);
+
+                       // reset holders
+                       for (Holder holder : active) {
+                               // save old clocksequence?
+                               newClockSequence(holder);
+                       }
+               }
+
+               private synchronized void newClockSequence(Holder holder) {
+                       // Too many holders, we will remove the oldes ones
+                       while (activeHolders.size() > getRangeSize()) {
+                               long oldestTimeStamp = -1;
+                               Holder holderToRemove = null;
+                               holders: for (Holder h : activeHolders.keySet()) {
+                                       if (h == holder)// skip the caller
+                                               continue holders;
+
+                                       if (oldestTimeStamp < 0) {
+                                               oldestTimeStamp = h.lastTimestamp;
+                                               holderToRemove = h;
+                                       }
+                                       if (h.lastTimestamp <= oldestTimeStamp) {
+                                               oldestTimeStamp = h.lastTimestamp;
+                                               holderToRemove = h;
+                                       }
+
+                               }
+                               assert holderToRemove != null;
+                               long oldClockSequence = holderToRemove.clockSequence;
+                               holderToRemove.clockSequence = -1;
+                               activeHolders.remove(holderToRemove);
+                               if (logger.isLoggable(WARNING))
+                                       logger.log(WARNING, "Removed " + holderToRemove + ", oldClockSequence=" + oldClockSequence);
+                       }
+
+                       long newClockSequence = -1;
+                       int tryCount = 0;// an explicit exit condition
+                       do {
+                               tryCount++;
+                               if (tryCount >= getRangeSize())
+                                       throw new IllegalStateException("No more clock sequence available");
+
+                               newClockSequence = counter.incrementAndGet();
+                               assert newClockSequence >= 0 : "Clock sequence cannot be negative";
+                               if (newClockSequence > max) {
+                                       // reset counter
+                                       newClockSequence = min;
+                                       counter.set(newClockSequence);
+                               }
+                       } while (activeHolders.containsValue(newClockSequence));
+                       // TODO use an iterator to check the values
+                       holder.setClockSequence(newClockSequence);
+                       activeHolders.put(holder, newClockSequence);
+                       if (logger.isLoggable(DEBUG)) {
+                               String clockDesc = range >= 0 ? Long.toHexString(newClockSequence & 0x00FF)
+                                               : Long.toHexString(newClockSequence | 0x8000);
+                               String rangeDesc = Long.toHexString(min | 0x8000) + "-" + Long.toHexString(max | 0x8000);
+                               logger.log(DEBUG, "New clocksequence " + clockDesc + " for thread " + Thread.currentThread().getId()
+                                               + " (in range " + rangeDesc + ")");
+                       }
+               }
+
+               private synchronized int getRangeSize() {
+                       return (int) (max - min);
+               }
+
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/ConcurrentUuidFactory.java
new file mode 100644 (file)
index 0000000..264e047
--- /dev/null
@@ -0,0 +1,80 @@
+package org.argeo.api.uuid;
+
+import static java.lang.System.Logger.Level.DEBUG;
+import static java.lang.System.Logger.Level.WARNING;
+
+import java.lang.System.Logger;
+import java.security.DrbgParameters;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * A configurable implementation of an {@link AsyncUuidFactory}, which can be
+ * used as a base class for more optimised implementations.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122
+ */
+public class ConcurrentUuidFactory extends AbstractAsyncUuidFactory implements TypedUuidFactory {
+       private final static Logger logger = System.getLogger(ConcurrentUuidFactory.class.getName());
+
+       public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId) {
+               this(initialClockRange, nodeId, 0);
+       }
+
+       public ConcurrentUuidFactory(long initialClockRange, byte[] nodeId, int offset) {
+               Objects.requireNonNull(nodeId);
+               if (offset + 6 > nodeId.length)
+                       throw new IllegalArgumentException("Offset too big: " + offset);
+               byte[] defaultNodeId = toNodeIdBytes(nodeId, offset);
+               long nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
+               setNodeIdSupplier(() -> nodeIdBase, initialClockRange);
+       }
+
+       /**
+        * Empty constructor for use with component life cycle. A {@link NodeIdSupplier}
+        * must be set externally, otherwise time based UUID won't work.
+        */
+       public ConcurrentUuidFactory() {
+               super();
+       }
+
+//     public ConcurrentUuidFactory() {
+//             byte[] defaultNodeId = getIpBytes();
+//             nodeIdBase = NodeIdSupplier.toNodeIdBase(defaultNodeId);
+//             setNodeIdSupplier(() -> nodeIdBase);
+//             assert newTimeUUID().node() == BitSet.valueOf(defaultNodeId).toLongArray()[0];
+//     }
+
+       /*
+        * DEFAULT
+        */
+       /**
+        * The default {@link UUID} to provide. This implementations returns
+        * {@link #timeUUID()} because it is fast and uses few resources.
+        */
+       @Override
+       public UUID get() {
+               return timeUUID();
+       }
+
+       @Override
+       protected SecureRandom createSecureRandom() {
+               SecureRandom secureRandom;
+               try {
+                       secureRandom = SecureRandom.getInstance("DRBG",
+                                       DrbgParameters.instantiation(256, DrbgParameters.Capability.PR_AND_RESEED, "UUID".getBytes()));
+               } catch (NoSuchAlgorithmException e) {
+                       try {
+                               logger.log(DEBUG, "DRBG secure random not found, using strong");
+                               secureRandom = SecureRandom.getInstanceStrong();
+                       } catch (NoSuchAlgorithmException e1) {
+                               logger.log(WARNING, "No strong secure random was found, using default");
+                               secureRandom = new SecureRandom();
+                       }
+               }
+               return secureRandom;
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/GUID.java
new file mode 100644 (file)
index 0000000..a9a5af1
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * A variant 6 {@link UUID}.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1
+ */
+public class GUID extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       /** Constructor based on a {@link UUID}. */
+       public GUID(UUID uuid) {
+               super(uuid);
+               if (uuid.variant() != 6)
+                       throw new IllegalArgumentException("The provided UUID is not a GUID.");
+       }
+
+       /**
+        * Formats N, D, B, P are supported:
+        * <ul>
+        * <li>D: 1db31359-bdd8-5a0f-b672-30c247d582c5</li>
+        * <li>N: 1db31359bdd85a0fb67230c247d582c5</li>
+        * <li>B: {1db31359-bdd8-5a0f-b672-30c247d582c5}</li>
+        * <li>P: (1db31359-bdd8-5a0f-b672-30c247d582c5)</li>
+        * </ul>
+        * 
+        * @see https://docs.microsoft.com/en-us/dotnet/api/system.guid.tostring
+        */
+       public static String toString(UUID uuid, char format, boolean upperCase) {
+               String str;
+               switch (format) {
+               case 'D':
+                       str = uuid.toString();
+                       break;
+               case 'N':
+                       str = UuidBinaryUtils.toCompact(uuid);
+                       break;
+               case 'B':
+                       str = "{" + uuid.toString() + "}";
+                       break;
+               case 'P':
+                       str = "(" + uuid.toString() + ")";
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unsupported format : " + format);
+               }
+               return upperCase ? str.toUpperCase() : str;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/MacAddressUuidFactory.java
new file mode 100644 (file)
index 0000000..fa68df1
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.api.uuid;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.UUID;
+
+/**
+ * An {@link UUID} factory whose node id (for time based UUIDs) is the hardware
+ * MAC address as specified in RFC4122.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc4122.html#section-4.1.6
+ */
+public class MacAddressUuidFactory extends ConcurrentUuidFactory {
+       public final static UuidFactory DEFAULT = new MacAddressUuidFactory();
+
+       public MacAddressUuidFactory() {
+               this(0);
+       }
+
+       public MacAddressUuidFactory(long initialClockRange) {
+               super(initialClockRange, localHardwareAddressAsNodeId());
+       }
+
+       public static byte[] localHardwareAddressAsNodeId() {
+               InetAddress localHost;
+               try {
+                       localHost = InetAddress.getLocalHost();
+                       NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
+                       return hardwareAddressToNodeId(nic);
+               } catch (UnknownHostException | SocketException e) {
+                       throw new IllegalStateException(e);
+               }
+
+       }
+
+       public static byte[] hardwareAddressToNodeId(NetworkInterface nic) throws SocketException {
+               byte[] hardwareAddress = nic.getHardwareAddress();
+               final int length = 6;
+               byte[] arr = new byte[length];
+               for (int i = 0; i < length; i++) {
+                       arr[i] = hardwareAddress[length - 1 - i];
+               }
+               return arr;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NameUuid.java
new file mode 100644 (file)
index 0000000..376eea4
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.api.uuid;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+/** A name UUID whose values used for its construction are known. */
+public class NameUuid extends BasicNameUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       protected final TypedUuid namespace;
+       protected final String name;
+       protected final Charset encoding;
+
+       /**
+        * Default static builder which creates a v5 (SHA1) name based {@link UUID},
+        * using UTF-8 encoding. Use
+        * {@link #NameUuid(TypedUuid, String, Charset, boolean)} for more options.
+        */
+       public NameUuid(TypedUuid namespace, String name) {
+               this(namespace, name, StandardCharsets.UTF_8, true);
+       }
+
+       /** Static builder (an {@link TypedUuidFactory} may be more efficient). */
+       public NameUuid(TypedUuid namespace, String name, Charset encoding, boolean sha1) {
+               this(sha1 ? AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding))
+                               : AbstractUuidFactory.createNameUUIDv5Static(namespace.uuid, name.getBytes(encoding)), namespace, name,
+                               encoding);
+       }
+
+       /**
+        * Since no check is performed, the constructor is protected so that the object
+        * can only be built by the default methods of {@link TypedUuidFactory} (in this
+        * package) or by extending the class.
+        */
+       protected NameUuid(UUID uuid, TypedUuid namespace, String name, Charset encoding) {
+               super(uuid);
+               assert namespace != null;
+               assert name != null;
+               assert encoding != null;
+               this.namespace = namespace;
+               this.name = name;
+               this.encoding = encoding;
+       }
+
+       /** The namespace used to build this UUID. */
+       public final TypedUuid getNamespace() {
+               return namespace;
+       }
+
+       /** The name of this UUID. */
+       public final String getName() {
+               return name;
+       }
+
+       /** The encoding used to create this UUID. */
+       public final Charset getEncoding() {
+               return encoding;
+       }
+
+       /** Always returns <code>false</code> since construction values are known. */
+       @Override
+       public final boolean isOpaque() {
+               return false;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NoOpUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NoOpUuidFactory.java
new file mode 100644 (file)
index 0000000..cca147e
--- /dev/null
@@ -0,0 +1,83 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/**
+ * An {@link UuidFactory} which does not implement any algorith and returns
+ * {@link UnsupportedOperationException} for methods requiring them. Only
+ * {@link UuidFactory#get()} and {@link UuidFactory#randomUUID()} are
+ * implemented, trivially based on {@link UUID#randomUUID()}. It can be useful
+ * as a base class for partial implementations, or whren only random
+ * {@link UUID}s are needed, but one wants to integrate with this UUID API via
+ * {@link UuidFactory}.
+ */
+public class NoOpUuidFactory implements UuidFactory {
+       public static final UuidFactory onlyJavaRandom = new NoOpUuidFactory();
+
+       /** Returns {@link #randomUUID()}. */
+       @Override
+       public UUID get() {
+               return randomUUID();
+       }
+
+       /**
+        * Creates a random UUID (v4) with {@link UUID#randomUUID()}.
+        * 
+        * @return a random {@link UUID}
+        */
+       @Override
+       public UUID randomUUID() {
+               return UUID.randomUUID();
+       }
+
+       /**
+        * Throws an {@link UnsupportedOperationException}.
+        * 
+        * @throws UnsupportedOperationException always
+        */
+       @Override
+       public UUID timeUUID() {
+               throw new UnsupportedOperationException("Time based UUIDs are not implemented");
+       }
+
+       /**
+        * Throws an {@link UnsupportedOperationException}.
+        * 
+        * @throws UnsupportedOperationException always
+        */
+       @Override
+       public UUID nameUUIDv5(UUID namespace, byte[] data) {
+               throw new UnsupportedOperationException("Name based UUIDs are not implemented");
+       }
+
+       /**
+        * Throws an {@link UnsupportedOperationException}.
+        * 
+        * @throws UnsupportedOperationException always
+        */
+       @Override
+       public UUID nameUUIDv3(UUID namespace, byte[] data) {
+               throw new UnsupportedOperationException("Name based UUIDs are not implemented");
+       }
+
+       /**
+        * Throws an {@link UnsupportedOperationException}.
+        * 
+        * @throws UnsupportedOperationException always
+        */
+       @Override
+       public UUID randomUUIDStrong() {
+               throw new UnsupportedOperationException("Strong random UUIDs are not implemented");
+       }
+
+       /**
+        * Throws an {@link UnsupportedOperationException}.
+        * 
+        * @throws UnsupportedOperationException always
+        */
+       @Override
+       public UUID randomUUIDWeak() {
+               throw new UnsupportedOperationException("Weak random UUIDs are not implemented");
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/NodeIdSupplier.java
new file mode 100644 (file)
index 0000000..94ec50d
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.api.uuid;
+
+import java.util.function.Supplier;
+
+/** A factory for node id base */
+public interface NodeIdSupplier extends Supplier<Long> {
+       static long toNodeIdBase(byte[] node) {
+               assert node.length == 6;
+               return UuidFactory.LEAST_SIG_RFC4122_VARIANT
+                               | (node[0] & 0xFFL) //
+                               | ((node[1] & 0xFFL) << 8) //
+                               | ((node[2] & 0xFFL) << 16) //
+                               | ((node[3] & 0xFFL) << 24) //
+                               | ((node[4] & 0xFFL) << 32) //
+                               | ((node[5] & 0xFFL) << 40); //
+       }
+
+       static boolean isNoMacAddressNodeId(byte[] nodeId) {
+               return (nodeId[0] & 1) != 0;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/RandomUuid.java
new file mode 100644 (file)
index 0000000..b65772e
--- /dev/null
@@ -0,0 +1,30 @@
+package org.argeo.api.uuid;
+
+import java.util.UUID;
+
+/** An opaque variant 2 random {@link UUID} (v4). */
+public final class RandomUuid extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       /** Constructor based on a {@link UUID}. */
+       public RandomUuid(UUID uuid) {
+               super(uuid);
+               if (uuid.version() != 4 && uuid.variant() != 2)
+                       throw new IllegalArgumentException("The provided UUID is not a time-based UUID.");
+       }
+
+       /**
+        * Always returns <code>true</code> since random UUIDs are by definition not
+        * opaque.
+        */
+       @Override
+       public final boolean isOpaque() {
+               return true;
+       }
+
+       /** Creates a new {@link RandomUuid} using {@link UUID#randomUUID()}. */
+       public static RandomUuid newRandomUuid() {
+               return new RandomUuid(UUID.randomUUID());
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TimeUuid.java
new file mode 100644 (file)
index 0000000..2276cd2
--- /dev/null
@@ -0,0 +1,79 @@
+package org.argeo.api.uuid;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.UUID;
+
+/**
+ * A time based UUID, whose content can therefore be usefully interpreted as
+ * time and node identifier information.
+ */
+public class TimeUuid extends TypedUuid {
+       private static final long serialVersionUID = APM.SERIAL;
+       /**
+        * Start of the Gregorian time on October 15th 1582, equivalent to
+        * <code>{@link UUID#timestamp()} == 0</code>.
+        */
+       public final static Instant TIMESTAMP_ZERO = ZonedDateTime.of(1582, 10, 15, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
+
+       /** Constructor based on a {@link UUID}. */
+       public TimeUuid(UUID uuid) {
+               super(uuid);
+               if (uuid.version() != 1 && uuid.variant() != 2)
+                       throw new IllegalArgumentException("The provided UUID is not a time based UUID.");
+       }
+
+       /** {@link UUID#timestamp()} as an {@link Instant}. */
+       public final Instant getInstant() {
+               long timestamp = uuid.timestamp();
+               return TIMESTAMP_ZERO.plus(timestampDifferenceToDuration(timestamp));
+       }
+
+       /** {@link UUID#node()} as an hex string. */
+       public final String getNodeId() {
+               return Long.toHexString(uuid.node());
+       }
+
+       /** {@link UUID#clockSequence()} as an hex string. */
+       public final String getClockSequence() {
+               return Long.toHexString(uuid.clockSequence());
+       }
+
+       /**
+        * Always returns <code>false</code> since time UUIDs are by definition not
+        * opaque.
+        */
+       @Override
+       public final boolean isOpaque() {
+               return false;
+       }
+
+       /*
+        * STATIC UTILITIES
+        */
+       /** Converts from duration in the time UUID timestamp format. */
+       public static Duration timestampDifferenceToDuration(long timestampDifference) {
+               long seconds = timestampDifference / 10000000;
+               long nano = (timestampDifference % 10000000) * 100;
+               return Duration.ofSeconds(seconds, nano);
+       }
+
+       /**
+        * A duration expressed in the time UUID timestamp format based on units of 100
+        * ns.
+        */
+       public static long durationToTimestamp(Duration duration) {
+               return (duration.getSeconds() * 10000000 + duration.getNano() / 100);
+       }
+
+       /**
+        * An instant expressed in the time UUID timestamp format based on units of 100
+        * ns since {@link #TIMESTAMP_ZERO}.
+        */
+       public static long instantToTimestamp(Instant instant) {
+               Duration duration = Duration.between(TimeUuid.TIMESTAMP_ZERO, instant);
+               return durationToTimestamp(duration);
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuid.java
new file mode 100644 (file)
index 0000000..2a0842f
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.api.uuid;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Base class for objects which are explicitly typed, based on the various
+ * variant 2 (RFC 4122) UUID versions (and variant 6 with {@link GUID}, for
+ * completion). Such a derivation hierarchy still represents the {@link UUID}
+ * itself, not the objects, data or concept that it identifies. Just like
+ * {@link UUID}s, {@link TypedUuid} should be used as identifier, not as base
+ * class for complex objects. It should rather be seen as a framework to build
+ * richer IDs, which are strictly compliant with the UUID specifications.
+ */
+public abstract class TypedUuid extends UuidHolder {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       /** Default constructor. */
+       public TypedUuid(UUID uuid) {
+               super(uuid);
+       }
+
+       /**
+        * Whether this {@link UUID} has no meaning in itself (RFC4122 v3, v4 and v5,
+        * and Microsoft GUID). Only RFC4122 v1 and v2 can be interpreted.
+        */
+       public boolean isOpaque() {
+               if (uuid.variant() == 2) {// RFC4122
+                       return uuid.version() == 4 || uuid.version() == 5 || uuid.version() == 3;
+               } else if (uuid.variant() == 6) {// Microsoft
+                       return true;
+               } else {
+                       return true;
+               }
+       }
+
+       /**
+        * Constructs a {@link TypedUuid} of the most appropriate subtype, based on this
+        * {@link UUID}. For name based UUIDs, it will return an opaque
+        * {@link BasicNameUuid}; {@link NameUuid} and {@link BinaryNameUuid} may be
+        * more useful.
+        */
+       public static TypedUuid of(UUID uuid) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               if (uuid.variant() == 2) {// RFC 4122
+                       switch (uuid.version()) {
+                       case 1:
+                               return new TimeUuid(uuid);
+                       case 4:
+                               return new RandomUuid(uuid);
+                       case 3:
+                       case 5:
+                               return new BasicNameUuid(uuid);
+                       default:
+                               throw new IllegalArgumentException("UUIDs with version " + uuid.version() + " are not supported.");
+                       }
+               } else if (uuid.variant() == 6) {// GUID
+                       return new GUID(uuid);
+               } else {
+                       throw new IllegalArgumentException("UUIDs with variant " + uuid.variant() + " are not supported.");
+               }
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/TypedUuidFactory.java
new file mode 100644 (file)
index 0000000..770e9b7
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.api.uuid;
+
+import java.nio.charset.Charset;
+
+/** An {@link UuidFactory} which also (trivially) produces {@link TypedUuid}. */
+public interface TypedUuidFactory extends UuidFactory {
+       /** Creates a {@link TimeUuid} (v1). */
+       default TimeUuid newTimeUuid() {
+               return new TimeUuid(timeUUID());
+       }
+
+       /** Creates an MD5 {@link NameUuid} (v3). */
+       default NameUuid newNameUuidV3(TypedUuid namespace, String name, Charset charset) {
+               return new NameUuid(nameUUIDv3(namespace.get(), name, charset), namespace, name, charset);
+       }
+
+       /** Creates a {@link RandomUuid}, using {@link #randomUUID()}. */
+       default RandomUuid newRandomUuid() {
+               return new RandomUuid(randomUUID());
+       }
+
+       /** Creates an SHA1 {@link NameUuid} (v5). */
+       default NameUuid newNameUuidV5(TypedUuid namespace, String name, Charset charset) {
+               return new NameUuid(nameUUIDv5(namespace.get(), name, charset), namespace, name, charset);
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidBinaryUtils.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidBinaryUtils.java
new file mode 100644 (file)
index 0000000..dfe87b7
--- /dev/null
@@ -0,0 +1,176 @@
+package org.argeo.api.uuid;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/** Static utilities around conversion of {@link UUID} from/to bytes. */
+public class UuidBinaryUtils {
+       /**
+        * Singleton constructor, should only be extended to provide additional static
+        * utilities.
+        */
+       protected UuidBinaryUtils() {
+       }
+
+       /**
+        * Convert bytes to an UUID, starting to read the array at this offset.
+        */
+       public static UUID fromBytes(byte[] data, int offset) {
+               Objects.requireNonNull(data, "Byte array cannot be null");
+               long msb = 0;
+               long lsb = 0;
+               for (int i = offset; i < 8 + offset; i++)
+                       msb = (msb << 8) | (data[i] & 0xff);
+               for (int i = 8 + offset; i < 16 + offset; i++)
+                       lsb = (lsb << 8) | (data[i] & 0xff);
+               return new UUID(msb, lsb);
+       }
+
+       /*
+        * UTILITIES
+        */
+       /**
+        * Convert bytes to an UUID. Byte array must not be null and be exactly of
+        * length 16.
+        */
+       public static UUID fromBytes(byte[] data) {
+               Objects.requireNonNull(data, "Byte array must not be null");
+               if (data.length != 16)
+                       throw new IllegalArgumentException("Byte array as length " + data.length);
+               return fromBytes(data, 0);
+       }
+
+       @Deprecated
+       protected static long longFromBytes(byte[] data) {
+               long msb = 0;
+               for (int i = 0; i < data.length; i++)
+                       msb = (msb << 8) | (data[i] & 0xff);
+               return msb;
+       }
+
+       /** Convert this UUID to a byte array of length 16. */
+       public static byte[] toBytes(UUID uuid) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               long msb = uuid.getMostSignificantBits();
+               long lsb = uuid.getLeastSignificantBits();
+               return toBytes(msb, lsb);
+       }
+
+       /** Copies this {@link UUID} as bytes, using 16 bytes. */
+       public static void copyBytes(UUID uuid, byte[] arr, int offset) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               long msb = uuid.getMostSignificantBits();
+               long lsb = uuid.getLeastSignificantBits();
+               copyBytes(msb, lsb, arr, offset);
+       }
+
+       /** Convert two longs to a byte array with length 16. */
+       protected static byte[] toBytes(long long1, long long2) {
+               byte[] result = new byte[16];
+               for (int i = 0; i < 8; i++)
+                       result[i] = (byte) ((long1 >> ((7 - i) * 8)) & 0xff);
+               for (int i = 8; i < 16; i++)
+                       result[i] = (byte) ((long2 >> ((15 - i) * 8)) & 0xff);
+               return result;
+       }
+
+       /** Copy these two longs to this byte array, using 16 bytes. */
+       protected static void copyBytes(long long1, long long2, byte[] arr, int offset) {
+               assert arr.length >= 16 + offset;
+               for (int i = offset; i < 8 + offset; i++)
+                       arr[i] = (byte) ((long1 >> ((7 - i) * 8)) & 0xff);
+               for (int i = 8 + offset; i < 16 + offset; i++)
+                       arr[i] = (byte) ((long2 >> ((15 - i) * 8)) & 0xff);
+       }
+
+       final protected static char[] hexArray = "0123456789abcdef".toCharArray();
+
+       /** Converts a byte array to an hex String. */
+       public static String toHexString(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 2];
+               for (int j = 0; j < bytes.length; j++) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 2] = hexArray[v >>> 4];
+                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+               }
+               return new String(hexChars);
+       }
+
+       /*
+        * COMPACT STRING
+        */
+
+       /** To a 32 characters hex string without '-'. */
+       public static String toCompact(UUID uuid) {
+               return toHexString(toBytes(uuid));
+       }
+
+       /**
+        * Converts an UUID hex representation without '-' to an {@link UUID}.
+        */
+       public static UUID fromCompact(String compact) {
+               return UUID.fromString(UuidBinaryUtils.compactToStd(compact));
+       }
+
+       /**
+        * Converts an UUID hex representation without '-' to the standard form (with
+        * '-').
+        */
+       public static String compactToStd(String compact) {
+               if (compact.length() != 32)
+                       throw new IllegalArgumentException(
+                                       "Compact UUID '" + compact + "' has length " + compact.length() + " and not 32.");
+               StringBuilder sb = new StringBuilder(36);
+               for (int i = 0; i < 32; i++) {
+                       if (i == 8 || i == 12 || i == 16 || i == 20)
+                               sb.append('-');
+                       sb.append(compact.charAt(i));
+               }
+               String std = sb.toString();
+               assert std.length() == 36;
+               assert UUID.fromString(std).toString().equals(std);
+               return std;
+       }
+
+       /**
+        * Converts an UUID to a binary string (list of 0 and 1), with a separator to
+        * make it more readable.
+        */
+       public static String toBinaryString(UUID uuid, int charsPerSegment, char separator) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               String binaryString = UuidBinaryUtils.toBinaryString(uuid);
+               StringBuilder sb = new StringBuilder(128 + (128 / charsPerSegment));
+               for (int i = 0; i < binaryString.length(); i++) {
+                       if (i != 0 && i % charsPerSegment == 0)
+                               sb.append(separator);
+                       sb.append(binaryString.charAt(i));
+               }
+               return sb.toString();
+       }
+
+       /** Converts an UUID to a binary string (list of 0 and 1). */
+       public static String toBinaryString(UUID uuid) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               String most = zeroTo64Chars(Long.toBinaryString(uuid.getMostSignificantBits()));
+               String least = zeroTo64Chars(Long.toBinaryString(uuid.getLeastSignificantBits()));
+               String binaryString = most + least;
+               assert binaryString.length() == 128;
+               return binaryString;
+       }
+
+       /*
+        * LOW-LEVEL UTILITIES
+        */
+       protected static String zeroTo64Chars(String str) {
+               assert str.length() <= 64;
+               if (str.length() < 64) {
+                       StringBuilder sb = new StringBuilder(64);
+                       for (int i = 0; i < 64 - str.length(); i++)
+                               sb.append('0');
+                       sb.append(str);
+                       return sb.toString();
+               } else
+                       return str;
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidFactory.java
new file mode 100644 (file)
index 0000000..4ab6c2e
--- /dev/null
@@ -0,0 +1,226 @@
+package org.argeo.api.uuid;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.nio.charset.Charset;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Supplier;
+
+/**
+ * A provider of RFC 4122 {@link UUID}s. Only the RFC 4122 variant (also known
+ * as Leach–Salz variant) is supported. The default, returned by the
+ * {@link Supplier#get()} method MUST be a v4 UUID (random).
+ * 
+ * @see UUID
+ * @see https://datatracker.ietf.org/doc/html/rfc4122
+ */
+public interface UuidFactory extends Supplier<UUID> {
+
+       /*
+        * DEFAULT
+        */
+       /**
+        * The default {@link UUID} to provide, either random (v4) or time based (v1).
+        * It SHOULD wrap either {@link #timeUUID()} (recommended) or
+        * {@link #randomUUID()}.
+        */
+       @Override
+       UUID get();
+
+       /*
+        * TIME-BASED (version 1)
+        */
+       /**
+        * A new time based {@link UUID} (v1) with efforts to make it unique on this
+        * node.
+        */
+       UUID timeUUID();
+
+       /*
+        * NAME BASED (version 3 and 5)
+        */
+       /**
+        * A new {@link UUID} v5, which an SHA1 digest of namespace and the provided
+        * bytes. This use to build names and implementation MAY restrict the maximal
+        * size of the byte array.
+        * 
+        * @see UuidFactory#NAMESPACE_UUID_DNS
+        * @see UuidFactory#NAMESPACE_UUID_URL
+        * @see UuidFactory#NAMESPACE_UUID_OID
+        * @see UuidFactory#NAMESPACE_UUID_X500
+        */
+       UUID nameUUIDv5(UUID namespace, byte[] data);
+
+       /**
+        * A new {@link UUID} v3, which a MD5 digest of namespace and the provided
+        * bytes. This use to build names and implementation MAY restrict the maximal
+        * size of the byte array.
+        * 
+        * @see UuidFactory#NAMESPACE_UUID_DNS
+        * @see UuidFactory#NAMESPACE_UUID_URL
+        * @see UuidFactory#NAMESPACE_UUID_OID
+        * @see UuidFactory#NAMESPACE_UUID_X500
+        */
+       UUID nameUUIDv3(UUID namespace, byte[] data);
+
+       /**
+        * A convenience method to generate a name based UUID v5 based on a string,
+        * using the UTF-8 charset.
+        * 
+        * @see UuidFactory#nameUUIDv5(UUID, byte[])
+        */
+       default UUID nameUUIDv5(UUID namespace, String name) {
+               return nameUUIDv5(namespace, name, UTF_8);
+       }
+
+       /**
+        * A convenience method to generate a name based UUID v5 based on a string.
+        * 
+        * @see UuidFactory#nameUUIDv5(UUID, byte[])
+        */
+       default UUID nameUUIDv5(UUID namespace, String name, Charset charset) {
+               Objects.requireNonNull(name, "Name cannot be null");
+               return nameUUIDv5(namespace, name.getBytes(charset));
+       }
+
+       /**
+        * A convenience method to generate a name based UUID v3 based on a string,
+        * using the UTF-8 charset.
+        * 
+        * @see UuidFactory#nameUUIDv3(UUID, byte[])
+        */
+       default UUID nameUUIDv3(UUID namespace, String name) {
+               return nameUUIDv3(namespace, name, UTF_8);
+       }
+
+       /**
+        * A convenience method to generate a name based UUID v3 based on a string.
+        * 
+        * @see UuidFactory#nameUUIDv3(UUID, byte[])
+        */
+       default UUID nameUUIDv3(UUID namespace, String name, Charset charset) {
+               Objects.requireNonNull(name, "Name cannot be null");
+               return nameUUIDv3(namespace, name.getBytes(charset));
+       }
+
+       /*
+        * RANDOM (version 4)
+        */
+       /**
+        * A random UUID at least as good as {@link UUID#randomUUID()}, but with efforts
+        * to make it even more random, using more secure algorithms and resseeding.
+        */
+       UUID randomUUIDStrong();
+
+       /**
+        * An {@link UUID} generated based on {@link ThreadLocalRandom}. Implementations
+        * should always provide it synchronously.
+        */
+       UUID randomUUIDWeak();
+
+       /**
+        * The default random {@link UUID} (v4) generator to use. This default
+        * implementation returns {@link #randomUUIDStrong()}. In general, one should
+        * use {@link UUID#randomUUID()} to generate random UUID, as it is certainly the
+        * best balanced and to avoid unnecessary dependencies with an API. The
+        * implementations provided here are either when is looking for something
+        * "stronger" ({@link #randomUUIDStrong()} or faster {@link #randomUUIDWeak()}.
+        */
+       default UUID randomUUID() {
+               return randomUUIDStrong();
+       }
+
+       /*
+        * STANDARD UUIDs
+        */
+
+       /** Nil UUID (00000000-0000-0000-0000-000000000000). */
+       final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+       /**
+        * Standard DNS namespace ID for type 3 or 5 UUID (as defined in Appendix C of
+        * RFC4122).
+        */
+       final static UUID NAMESPACE_UUID_DNS = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
+       /**
+        * Standard URL namespace ID for type 3 or 5 UUID (as defined in Appendix C of
+        * RFC4122).
+        */
+       final static UUID NAMESPACE_UUID_URL = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
+       /**
+        * Standard OID namespace ID (typically an LDAP type) for type 3 or 5 UUID (as
+        * defined in Appendix C of RFC4122).
+        */
+       final static UUID NAMESPACE_UUID_OID = UUID.fromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8");
+       /**
+        * Standard X500 namespace ID (typically an LDAP DN) for type 3 or 5 UUID (as
+        * defined in Appendix C of RFC4122).
+        */
+       final static UUID NAMESPACE_UUID_X500 = UUID.fromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8");
+
+       /*
+        * BIT LEVEL CONSTANTS
+        */
+       /** Base for the most significant bit of version 1 (time based) UUIDs. */
+       long MOST_SIG_VERSION1 = (1l << 12);
+       /** Base for the least significant part of RFC4122 (variant 2) UUIDs. */
+       long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
+
+       /*
+        * UTILITIES
+        */
+       /** Whether this {@link UUID} is random (v4). */
+       static boolean isRandom(UUID uuid) {
+               return uuid.version() == 4;
+       }
+
+       /** Whether this {@link UUID} is time based (v1). */
+       static boolean isTimeBased(UUID uuid) {
+               return uuid.version() == 1;
+       }
+
+       /**
+        * Whether this UUID is time based but was not generated from an IEEE 802
+        * address, as per Section 4.5 of RFC4122.
+        * 
+        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.5
+        */
+       static boolean isTimeBasedWithMacAddress(UUID uuid) {
+               if (uuid.version() == 1) {
+                       return (uuid.node() & 1L) == 0;
+               } else
+                       return false;
+       }
+
+       /** Whether this {@link UUID} is name based (v3 or v5). */
+       static boolean isNameBased(UUID uuid) {
+               return uuid.version() == 3 || uuid.version() == 5;
+       }
+
+       /**
+        * The state of a time based UUID generator, as described and discussed in
+        * section 4.2.1 of RFC4122.
+        * 
+        * @see https://datatracker.ietf.org/doc/html/rfc4122#section-4.2.1
+        */
+       interface TimeUuidState {
+               /** Current node id and clock sequence for this thread. */
+               long getLeastSignificantBits();
+
+               /** A new current timestamp for this thread. */
+               long getMostSignificantBits();
+
+               /**
+                * The last timestamp which was produced by this thread, as returned by
+                * {@link UUID#timestamp()}.
+                */
+               long getLastTimestamp();
+
+               /**
+                * The current clock sequence for this thread, as returned by
+                * {@link UUID#clockSequence()}.
+                */
+               long getClockSequence();
+       }
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidHolder.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/UuidHolder.java
new file mode 100644 (file)
index 0000000..253f0ad
--- /dev/null
@@ -0,0 +1,71 @@
+package org.argeo.api.uuid;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+/**
+ * An immutable wrapper for an {@link UUID}, which can be used as a base for a
+ * derivation hierarchy, while strongly enforcing semantic equality with the
+ * underlying {@link UUID}. It is therefore immutable, and all base methods are
+ * directly and trivially based on {@link UUID} methods; they do represent the
+ * same unique "thing" (be it an entity, a point in time, etc.), consistently
+ * with the fundamental concept of uuid.
+ */
+public class UuidHolder implements Supplier<UUID>, Serializable {
+       private static final long serialVersionUID = APM.SERIAL;
+
+       /**
+        * The wrapped {@link UUID}. Protected rather than private, since it is
+        * immutable and a {@link UUID} is itself immutable.
+        */
+       protected final UUID uuid;
+
+       /**
+        * Constructs a new {@link UuidHolder} based on this uuid.
+        * 
+        * @param uuid the UUID to wrap, cannot be null.
+        * @throws NullPointerException if the provided uuid is null.
+        */
+       protected UuidHolder(UUID uuid) {
+               Objects.requireNonNull(uuid, "UUID cannot be null");
+               this.uuid = uuid;
+       }
+
+       /** The wrapped {@link UUID}. */
+       public final UUID getUuid() {
+               return uuid;
+       }
+
+       /** The wrapped {@link UUID}. */
+       @Override
+       public final UUID get() {
+               return getUuid();
+       }
+
+       /** Calls {@link UUID#hashCode()} on the wrapped {@link UUID}. */
+       @Override
+       public final int hashCode() {
+               return uuid.hashCode();
+       }
+
+       /**
+        * Equals only with non-null {@link UuidHolder} if and only if their wrapped
+        * uuid are equals by calling {@link UUID#equals(Object)}.
+        */
+       @Override
+       public final boolean equals(Object obj) {
+               if (obj == null || !(obj instanceof UuidHolder))
+                       return false;
+               UuidHolder typedUuid = (UuidHolder) obj;
+               return uuid.equals(typedUuid.uuid);
+       }
+
+       /** Calls {@link UUID#toString()} on the wrapped {@link UUID}. */
+       @Override
+       public final String toString() {
+               return uuid.toString();
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/DirectLibuuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/DirectLibuuidFactory.java
new file mode 100644 (file)
index 0000000..df3bb31
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.api.uuid.libuuid;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+import org.argeo.api.uuid.UuidBinaryUtils;
+import org.argeo.api.uuid.UuidFactory;
+
+/**
+ * @deprecated Rather use {@link LibuuidFactory}. This is just a proof of
+ *             concept that using shared memory in order to limit the JNI
+ *             overhead does not yield any significant performance gain. But it
+ *             could be an approach for computing and transferring bulk UUIDs
+ *             computations in one go, vi
+ *             {@link ByteBuffer#allocateDirect(int)}.
+ */
+public class DirectLibuuidFactory implements UuidFactory {
+       static {
+               System.loadLibrary("Java_org_argeo_api_uuid_libuuid");
+       }
+
+       @Override
+       public UUID get() {
+               return timeUUID();
+       }
+
+       @Override
+       public UUID timeUUID() {
+               ByteBuffer buf = ByteBuffer.allocateDirect(16);
+               timeUUID(buf);
+               byte[] arr = new byte[16];
+               buf.get(arr);
+               return UuidBinaryUtils.fromBytes(arr);
+       }
+
+       protected native void timeUUID(ByteBuffer uuidBuf);
+
+       @Override
+       public UUID nameUUIDv5(UUID namespace, byte[] data) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public UUID nameUUIDv3(UUID namespace, byte[] data) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public UUID randomUUIDStrong() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public UUID randomUUIDWeak() {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/LibuuidFactory.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/libuuid/LibuuidFactory.java
new file mode 100644 (file)
index 0000000..dd54c81
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.api.uuid.libuuid;
+
+import java.util.UUID;
+
+import org.argeo.api.uuid.TypedUuidFactory;
+import org.argeo.api.uuid.UuidFactory;
+
+/** An {@link UuidFactory} based on a native library. */
+public class LibuuidFactory implements UuidFactory, TypedUuidFactory {
+       static {
+               System.loadLibrary("Java_org_argeo_api_uuid_libuuid");
+       }
+
+       @Override
+       public UUID get() {
+               return timeUUID();
+       }
+
+       @Override
+       public native UUID timeUUID();
+
+       @Override
+       public native UUID nameUUIDv5(UUID namespace, byte[] data);
+
+       @Override
+       public native UUID nameUUIDv3(UUID namespace, byte[] data);
+
+       @Override
+       public native UUID randomUUIDStrong();
+
+       @Override
+       public UUID randomUUIDWeak() {
+               return randomUUIDStrong();
+       }
+
+}
diff --git a/org.argeo.api.uuid/src/org/argeo/api/uuid/package-info.java b/org.argeo.api.uuid/src/org/argeo/api/uuid/package-info.java
new file mode 100644 (file)
index 0000000..e896bf4
--- /dev/null
@@ -0,0 +1,2 @@
+/** API and utilities around {@link java.util.UUID}s. */
+package org.argeo.api.uuid;
diff --git a/org.argeo.api/.classpath b/org.argeo.api/.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.api/.project b/org.argeo.api/.project
deleted file mode 100644 (file)
index a396aad..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.api</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.api/META-INF/.gitignore b/org.argeo.api/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.api/bnd.bnd b/org.argeo.api/bnd.bnd
deleted file mode 100644 (file)
index dfa2450..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Provide-Capability: cms.datamodel;name=ldap;cnd=/org/argeo/api/ldap.cnd;abstract=true
diff --git a/org.argeo.api/build.properties b/org.argeo.api/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.api/pom.xml b/org.argeo.api/pom.xml
deleted file mode 100644 (file)
index 8f3c15c..0000000
+++ /dev/null
@@ -1,15 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.api</artifactId>
-       <name>Argeo Node API</name>
-       <packaging>jar</packaging>
-       <dependencies>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.api/src/org/argeo/api/ArgeoLogListener.java b/org.argeo.api/src/org/argeo/api/ArgeoLogListener.java
deleted file mode 100644 (file)
index 51861fd..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.api;
-
-/** Framework agnostic interface for log notifications */
-public interface ArgeoLogListener {
-       /**
-        * Appends a log
-        * 
-        * @param username
-        *            authentified user, null for anonymous
-        * @param level
-        *            INFO, DEBUG, WARN, etc. (logging framework specific)
-        * @param category
-        *            hierarchy (logging framework specific)
-        * @param thread
-        *            name of the thread which logged this message
-        * @param msg
-        *            any object as long as its toString() method returns the
-        *            message
-        * @param exception
-        *            exception in log4j ThrowableStrRep format
-        */
-       public void appendLog(String username, Long timestamp, String level,
-                       String category, String thread, Object msg, String[] exception);
-}
diff --git a/org.argeo.api/src/org/argeo/api/ArgeoLogger.java b/org.argeo.api/src/org/argeo/api/ArgeoLogger.java
deleted file mode 100644 (file)
index dbcd7d7..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.api;
-
-/**
- * Logging framework agnostic identifying a logging service, to which one can
- * register
- */
-public interface ArgeoLogger {
-       /**
-        * Register for events by threads with the same authentication (or all
-        * threads if admin)
-        */
-       public void register(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents);
-
-       /**
-        * For admin use only: register for all users
-        * 
-        * @param listener
-        *            the log listener
-        * @param numberOfPreviousEvents
-        *            the number of previous events to notify
-        * @param everything
-        *            if true even anonymous is logged
-        */
-       public void registerForAll(ArgeoLogListener listener,
-                       Integer numberOfPreviousEvents, boolean everything);
-
-       public void unregister(ArgeoLogListener listener);
-
-       public void unregisterForAll(ArgeoLogListener listener);
-}
diff --git a/org.argeo.api/src/org/argeo/api/DataAdminLoginModule.java b/org.argeo.api/src/org/argeo/api/DataAdminLoginModule.java
deleted file mode 100644 (file)
index 295196a..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.api;
-
-import java.util.Map;
-
-import javax.security.auth.AuthPermission;
-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.argeo.api.security.DataAdminPrincipal;
-
-/**
- * Log-in a system process as data admin. Protection is via
- * {@link AuthPermission} on this login module, so if it can be accessed it will
- * always succeed.
- */
-public class DataAdminLoginModule 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 {
-               subject.getPrincipals().add(new DataAdminPrincipal());
-               return true;
-       }
-
-       @Override
-       public boolean abort() throws LoginException {
-               return true;
-       }
-
-       @Override
-       public boolean logout() throws LoginException {
-               subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
-               return true;
-       }
-}
diff --git a/org.argeo.api/src/org/argeo/api/DataModelNamespace.java b/org.argeo.api/src/org/argeo/api/DataModelNamespace.java
deleted file mode 100644 (file)
index 421b11a..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.api;
-
-import org.osgi.resource.Namespace;
-
-/** CMS Data Model capability namespace. */
-public class DataModelNamespace extends Namespace {
-
-       public static final String CMS_DATA_MODEL_NAMESPACE = "cms.datamodel";
-       public static final String NAME = "name";
-       public static final String CND = "cnd";
-       /** If 'true', indicates that no repository should be published */
-       public static final String ABSTRACT = "abstract";
-
-       private DataModelNamespace() {
-               // empty
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeConstants.java b/org.argeo.api/src/org/argeo/api/NodeConstants.java
deleted file mode 100644 (file)
index e53730e..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-package org.argeo.api;
-
-public interface NodeConstants {
-       /*
-        * DN ATTRIBUTES (RFC 4514)
-        */
-       String CN = "cn";
-       String L = "l";
-       String ST = "st";
-       String O = "o";
-       String OU = "ou";
-       String C = "c";
-       String STREET = "street";
-       String DC = "dc";
-       String UID = "uid";
-
-       /*
-        * STANDARD ATTRIBUTES
-        */
-       String LABELED_URI = "labeledUri";
-
-       /*
-        * COMMON NAMES
-        */
-       String NODE = "node";
-
-       /*
-        * JCR CONVENTIONS
-        */
-       String NODE_REPOSITORY = NODE;
-       String EGO_REPOSITORY = "ego";
-       String SYS_WORKSPACE = "sys";
-       String HOME_WORKSPACE = "home";
-       String SRV_WORKSPACE = "srv";
-       String GUESTS_WORKSPACE = "guests";
-       String PUBLIC_WORKSPACE = "public";
-       String SECURITY_WORKSPACE = "security";
-
-       /*
-        * BASE DNs
-        */
-       String DEPLOY_BASEDN = "ou=deploy,ou=node";
-
-       /*
-        * STANDARD VALUES
-        */
-       String DEFAULT = "default";
-
-       /*
-        * RESERVED ROLES
-        */
-       String ROLES_BASEDN = "ou=roles,ou=node";
-       String TOKENS_BASEDN = "ou=tokens,ou=node";
-       String ROLE_ADMIN = "cn=admin," + ROLES_BASEDN;
-       String ROLE_USER_ADMIN = "cn=userAdmin," + ROLES_BASEDN;
-       String ROLE_DATA_ADMIN = "cn=dataAdmin," + ROLES_BASEDN;
-       // Special system groups that cannot be edited:
-       // user U anonymous = everyone
-       String ROLE_USER = "cn=user," + ROLES_BASEDN;
-       String ROLE_ANONYMOUS = "cn=anonymous," + ROLES_BASEDN;
-       // Account lifecycle
-       String ROLE_REGISTERING = "cn=registering," + ROLES_BASEDN;
-
-       /*
-        * LOGIN CONTEXTS
-        */
-       String LOGIN_CONTEXT_NODE = "NODE";
-       String LOGIN_CONTEXT_USER = "USER";
-       String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS";
-       String LOGIN_CONTEXT_DATA_ADMIN = "DATA_ADMIN";
-       String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER";
-       String LOGIN_CONTEXT_KEYRING = "KEYRING";
-
-       /*
-        * PATHS
-        */
-       String PATH_DATA = "/data";
-       String PATH_JCR = "/jcr";
-       String PATH_FILES = "/files";
-       // String PATH_JCR_PUB = "/pub";
-
-       /*
-        * FILE SYSTEMS
-        */
-       String SCHEME_NODE = NODE;
-
-       /*
-        * KERBEROS
-        */
-       String NODE_SERVICE = NODE;
-
-       /*
-        * INIT FRAMEWORK PROPERTIES
-        */
-       String NODE_INIT = "argeo.node.init";
-       String I18N_DEFAULT_LOCALE = "argeo.i18n.defaultLocale";
-       String I18N_LOCALES = "argeo.i18n.locales";
-       // Node Security
-       String ROLES_URI = "argeo.node.roles.uri";
-       String TOKENS_URI = "argeo.node.tokens.uri";
-       /** URI to an LDIF file or LDAP server used as initialization or backend */
-       String USERADMIN_URIS = "argeo.node.useradmin.uris";
-       // Transaction manager
-       String TRANSACTION_MANAGER = "argeo.node.transaction.manager";
-       String TRANSACTION_MANAGER_SIMPLE = "simple";
-       String TRANSACTION_MANAGER_BITRONIX = "bitronix";
-       // Node
-       /** Properties configuring the node repository */
-       String NODE_REPO_PROP_PREFIX = "argeo.node.repo.";
-       /** Additional standalone repositories, related to data models. */
-       String NODE_REPOS_PROP_PREFIX = "argeo.node.repos.";
-       // HTTP
-       String HTTP_PORT = "org.osgi.service.http.port";
-       String HTTP_PORT_SECURE = "org.osgi.service.http.port.secure";
-       /**
-        * The HTTP header used to convey the DN of a client verified by a reverse
-        * proxy. Typically SSL_CLIENT_S_DN for Apache.
-        */
-       String HTTP_PROXY_SSL_DN = "argeo.http.proxy.ssl.dn";
-
-       /*
-        * PIDs
-        */
-       String NODE_STATE_PID = "org.argeo.api.state";
-       String NODE_DEPLOYMENT_PID = "org.argeo.api.deployment";
-       String NODE_INSTANCE_PID = "org.argeo.api.instance";
-
-       String NODE_KEYRING_PID = "org.argeo.api.keyring";
-       String NODE_FS_PROVIDER_PID = "org.argeo.api.fsProvider";
-
-       /*
-        * FACTORY PIDs
-        */
-       String NODE_REPOS_FACTORY_PID = "org.argeo.api.repos";
-       String NODE_USER_ADMIN_PID = "org.argeo.api.userAdmin";
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeDeployment.java b/org.argeo.api/src/org/argeo/api/NodeDeployment.java
deleted file mode 100644 (file)
index d678bac..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.argeo.api;
-
-/** A configured node deployment. */
-public interface NodeDeployment {
-       Long getAvailableSince();
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeInstance.java b/org.argeo.api/src/org/argeo/api/NodeInstance.java
deleted file mode 100644 (file)
index 167ba81..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.api;
-
-import javax.naming.ldap.LdapName;
-
-/** The structured data */
-public interface NodeInstance {
-       /**
-        * To be used as an identifier of a workgroup, typically as a value for the
-        * 'businessCategory' attribute in LDAP.
-        */
-       public final static String WORKGROUP = "workgroup";
-
-       /** Mark this group as a workgroup */
-       void createWorkgroup(LdapName groupDn);
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeNames.java b/org.argeo.api/src/org/argeo/api/NodeNames.java
deleted file mode 100644 (file)
index b74069f..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.api;
-
-/** JCR types in the http://www.argeo.org/node namespace */
-@Deprecated
-public interface NodeNames {
-       String LDAP_UID = "ldap:"+NodeConstants.UID;
-       String LDAP_CN = "ldap:"+NodeConstants.CN;
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeOID.java b/org.argeo.api/src/org/argeo/api/NodeOID.java
deleted file mode 100644 (file)
index ade1163..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.api;
-
-interface NodeOID {
-       String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
-
-       // ATTRIBUTE TYPES
-       String ATTRIBUTE_TYPES = BASE + ".4";
-
-       // OBJECT CLASSES
-       String OBJECT_CLASSES = BASE + ".6";
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeState.java b/org.argeo.api/src/org/argeo/api/NodeState.java
deleted file mode 100644 (file)
index a824ac2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.api;
-
-import java.util.List;
-import java.util.Locale;
-
-/** A running node process. */
-public interface NodeState {
-       Locale getDefaultLocale();
-
-       List<Locale> getLocales();
-
-       String getHostname();
-
-       Long getAvailableSince();
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeTypes.java b/org.argeo.api/src/org/argeo/api/NodeTypes.java
deleted file mode 100644 (file)
index ca41ad2..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.api;
-
-/** JCR types in the http://www.argeo.org/node namespace */
-@Deprecated
-public interface NodeTypes {
-       String NODE_USER_HOME = "node:userHome";
-       String NODE_GROUP_HOME = "node:groupHome";
-}
diff --git a/org.argeo.api/src/org/argeo/api/NodeUtils.java b/org.argeo.api/src/org/argeo/api/NodeUtils.java
deleted file mode 100644 (file)
index 0370511..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-package org.argeo.api;
-
-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;
-
-/** Utilities related to Argeo model in JCR */
-public class NodeUtils {
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link NodeConstants#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(NodeConstants.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 NodeConstants#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 NodeConstants#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(NodeConstants.LABELED_URI, uri);
-                       if (alias != null)
-                               parameters.put(NodeConstants.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 (!NodeConstants.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 (!NodeConstants.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(NodeConstants.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(NodeConstants.NODE, node);
-       }
-
-       /**
-        * Open a JCR session with full read/write rights on the data, as
-        * {@link NodeConstants#ROLE_USER_ADMIN}, using the
-        * {@link NodeConstants#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(NodeConstants.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 NodeUtils() {
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/PublishNamespace.java b/org.argeo.api/src/org/argeo/api/PublishNamespace.java
deleted file mode 100644 (file)
index ff95754..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.argeo.api;
-
-import org.osgi.resource.Namespace;
-
-/** Namespace defining which resources can be published. Typically use to expose icon of scripts to the web. */
-public class PublishNamespace extends Namespace {
-
-       public static final String CMS_PUBLISH_NAMESPACE = "cms.publish";
-       public static final String PKG = "pkg";
-       public static final String FILE = "file";
-
-       private PublishNamespace() {
-               // empty
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/ldap.cnd b/org.argeo.api/src/org/argeo/api/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.api/src/org/argeo/api/node.cnd b/org.argeo.api/src/org/argeo/api/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.api/src/org/argeo/api/package-info.java b/org.argeo.api/src/org/argeo/api/package-info.java
deleted file mode 100644 (file)
index 1ea483a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-/**
- * Abstractions or constants related to an Argeo Node, an active repository of
- * linked data.
- */
-package org.argeo.api;
\ No newline at end of file
diff --git a/org.argeo.api/src/org/argeo/api/security/AnonymousPrincipal.java b/org.argeo.api/src/org/argeo/api/security/AnonymousPrincipal.java
deleted file mode 100644 (file)
index d07b055..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.api.security;
-
-import java.security.Principal;
-
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.NodeConstants;
-
-/** Marker for anonymous users. */
-public final class AnonymousPrincipal implements Principal {
-       private final String name = NodeConstants.ROLE_ANONYMOUS;
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               return this == obj;
-       }
-
-       @Override
-       public String toString() {
-               return name.toString();
-       }
-
-       public LdapName getLdapName(){
-               return NodeSecurityUtils.ROLE_ANONYMOUS_NAME;
-       }
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/CryptoKeyring.java b/org.argeo.api/src/org/argeo/api/security/CryptoKeyring.java
deleted file mode 100644 (file)
index 5ac73c6..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.argeo.api.security;
-
-/**
- * Marker interface for an advanced keyring based on cryptography.
- */
-public interface CryptoKeyring extends Keyring {
-       public void changePassword(char[] oldPassword, char[] newPassword);
-
-       public void unlock(char[] password);
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/DataAdminPrincipal.java b/org.argeo.api/src/org/argeo/api/security/DataAdminPrincipal.java
deleted file mode 100644 (file)
index 7581d8d..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.api.security;
-
-import java.security.Principal;
-
-import org.argeo.api.NodeConstants;
-
-/** Allows to modify any data. */
-public final class DataAdminPrincipal implements Principal {
-       private final String name = NodeConstants.ROLE_DATA_ADMIN;
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               return obj instanceof DataAdminPrincipal;
-       }
-
-       @Override
-       public String toString() {
-               return name.toString();
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/Keyring.java b/org.argeo.api/src/org/argeo/api/security/Keyring.java
deleted file mode 100644 (file)
index e0aa854..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.api.security;
-
-import java.io.InputStream;
-
-/**
- * Access to private (typically encrypted) data. The keyring is responsible for
- * retrieving the necessary credentials. <b>Experimental. This API may
- * change.</b>
- */
-public interface Keyring {
-       /**
-        * Returns the confidential information as chars. Must ask for it if it is
-        * not stored.
-        */
-       public char[] getAsChars(String path);
-
-       /**
-        * Returns the confidential information as a stream. Must ask for it if it
-        * is not stored.
-        */
-       public InputStream getAsStream(String path);
-
-       public void set(String path, char[] arr);
-
-       public void set(String path, InputStream in);
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/NodeSecurityUtils.java b/org.argeo.api/src/org/argeo/api/security/NodeSecurityUtils.java
deleted file mode 100644 (file)
index 2458512..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.api.security;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.api.NodeConstants;
-
-public class NodeSecurityUtils {
-       public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME,
-                       ROLE_USER_ADMIN_NAME;
-       public final static List<LdapName> RESERVED_ROLES;
-       static {
-               try {
-                       ROLE_ADMIN_NAME = new LdapName(NodeConstants.ROLE_ADMIN);
-                       ROLE_DATA_ADMIN_NAME = new LdapName(NodeConstants.ROLE_DATA_ADMIN);
-                       ROLE_USER_NAME = new LdapName(NodeConstants.ROLE_USER);
-                       ROLE_USER_ADMIN_NAME = new LdapName(NodeConstants.ROLE_USER_ADMIN);
-                       ROLE_ANONYMOUS_NAME = new LdapName(NodeConstants.ROLE_ANONYMOUS);
-                       RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList(
-                                       new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME }));
-               } catch (InvalidNameException e) {
-                       throw new Error("Cannot initialize login module class", e);
-               }
-       }
-
-       public static void checkUserName(LdapName name) throws IllegalArgumentException {
-               if (RESERVED_ROLES.contains(name))
-                       throw new IllegalArgumentException(name + " is a reserved name");
-       }
-
-       public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException {
-//             if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName))
-//                     throw new IllegalArgumentException(roleName + " cannot be listed as role");
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/PBEKeySpecCallback.java b/org.argeo.api/src/org/argeo/api/security/PBEKeySpecCallback.java
deleted file mode 100644 (file)
index 81fc724..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.argeo.api.security;
-
-import javax.crypto.spec.PBEKeySpec;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.PasswordCallback;
-
-/**
- * All information required to set up a {@link PBEKeySpec} bar the password
- * itself (use a {@link PasswordCallback})
- */
-public class PBEKeySpecCallback implements Callback {
-       private String secretKeyFactory;
-       private byte[] salt;
-       private Integer iterationCount;
-       /** Can be null for some algorithms */
-       private Integer keyLength;
-       /** Can be null, will trigger secret key encryption if not */
-       private String secretKeyEncryption;
-
-       private String encryptedPasswordHashCipher;
-       private byte[] encryptedPasswordHash;
-
-       public void set(String secretKeyFactory, byte[] salt,
-                       Integer iterationCount, Integer keyLength,
-                       String secretKeyEncryption) {
-               this.secretKeyFactory = secretKeyFactory;
-               this.salt = salt;
-               this.iterationCount = iterationCount;
-               this.keyLength = keyLength;
-               this.secretKeyEncryption = secretKeyEncryption;
-//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
-//             this.encryptedPasswordHash = encryptedPasswordHash;
-       }
-
-       public String getSecretKeyFactory() {
-               return secretKeyFactory;
-       }
-
-       public byte[] getSalt() {
-               return salt;
-       }
-
-       public Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       public Integer getKeyLength() {
-               return keyLength;
-       }
-
-       public String getSecretKeyEncryption() {
-               return secretKeyEncryption;
-       }
-
-       public String getEncryptedPasswordHashCipher() {
-               return encryptedPasswordHashCipher;
-       }
-
-       public byte[] getEncryptedPasswordHash() {
-               return encryptedPasswordHash;
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/security/package-info.java b/org.argeo.api/src/org/argeo/api/security/package-info.java
deleted file mode 100644 (file)
index 4af6169..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Security related API. */
-package org.argeo.api.security;
\ No newline at end of file
diff --git a/org.argeo.api/src/org/argeo/api/tabular/ArrayTabularRow.java b/org.argeo.api/src/org/argeo/api/tabular/ArrayTabularRow.java
deleted file mode 100644 (file)
index 3a14151..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.api.tabular;
-
-import java.util.List;
-
-/** Minimal tabular row wrapping an {@link Object} array */
-public class ArrayTabularRow implements TabularRow {
-       private final Object[] arr;
-
-       public ArrayTabularRow(List<?> objs) {
-               this.arr = objs.toArray();
-       }
-
-       public Object get(Integer col) {
-               return arr[col];
-       }
-
-       public int size() {
-               return arr.length;
-       }
-
-       public Object[] toArray() {
-               return arr;
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/TabularColumn.java b/org.argeo.api/src/org/argeo/api/tabular/TabularColumn.java
deleted file mode 100644 (file)
index 772ca59..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.api.tabular;
-
-/** The column in a tabular content */
-public class TabularColumn {
-       private String name;
-       /**
-        * JCR types, see
-        * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
-        * ?javax/jcr/PropertyType.html
-        */
-       private Integer type;
-
-       /** column with default type */
-       public TabularColumn(String name) {
-               super();
-               this.name = name;
-       }
-
-       public TabularColumn(String name, Integer type) {
-               super();
-               this.name = name;
-               this.type = type;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public void setName(String name) {
-               this.name = name;
-       }
-
-       public Integer getType() {
-               return type;
-       }
-
-       public void setType(Integer type) {
-               this.type = type;
-       }
-
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/TabularContent.java b/org.argeo.api/src/org/argeo/api/tabular/TabularContent.java
deleted file mode 100644 (file)
index 3c9d049..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.api.tabular;
-
-import java.util.List;
-
-/**
- * Content organized as a table, possibly with headers. Only JCR types are
- * supported even though there is not direct dependency on JCR.
- */
-public interface TabularContent {
-       /** The headers of this table or <code>null</code> is none available. */
-       public List<TabularColumn> getColumns();
-
-       public TabularRowIterator read();
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/TabularRow.java b/org.argeo.api/src/org/argeo/api/tabular/TabularRow.java
deleted file mode 100644 (file)
index a79b072..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.argeo.api.tabular;
-
-/** A row of tabular data */
-public interface TabularRow {
-       /** The value at this column index */
-       public Object get(Integer col);
-
-       /** The raw objects (direct references) */
-       public Object[] toArray();
-
-       /** Number of columns */
-       public int size();
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/TabularRowIterator.java b/org.argeo.api/src/org/argeo/api/tabular/TabularRowIterator.java
deleted file mode 100644 (file)
index 27a9c6f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.argeo.api.tabular;
-
-import java.util.Iterator;
-
-/** Navigation of rows */
-public interface TabularRowIterator extends Iterator<TabularRow> {
-       /**
-        * Current row number, has to be incremented by each call to next() ; starts at 0, will
-        * therefore be 1 for the first row returned.
-        */
-       public Long getCurrentRowNumber();
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/TabularWriter.java b/org.argeo.api/src/org/argeo/api/tabular/TabularWriter.java
deleted file mode 100644 (file)
index c434ffa..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.argeo.api.tabular;
-
-
-/** Write to a tabular content */
-public interface TabularWriter {
-       /** Append a new row of data */
-       public void appendRow(Object[] row);
-
-       /** Finish persisting data and release resources */
-       public void close();
-}
diff --git a/org.argeo.api/src/org/argeo/api/tabular/package-info.java b/org.argeo.api/src/org/argeo/api/tabular/package-info.java
deleted file mode 100644 (file)
index 738281f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Tabular format API. */
-package org.argeo.api.tabular;
\ No newline at end of file
diff --git a/org.argeo.cms.e4.rap/.classpath b/org.argeo.cms.e4.rap/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.cms.e4.rap/.project b/org.argeo.cms.e4.rap/.project
deleted file mode 100644 (file)
index 40c9e01..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.e4.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ds.core.builder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.cms.e4.rap/META-INF/.gitignore b/org.argeo.cms.e4.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml b/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml
deleted file mode 100644 (file)
index 1f688ba..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" name="CMS Admin RAP">
-   <implementation class="org.argeo.cms.e4.rap.CmsE4AdminApp"/>
-   <service>
-      <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
-      <property name="contextName" type="String" value="cms"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.cms.e4.rap/bnd.bnd b/org.argeo.cms.e4.rap/bnd.bnd
deleted file mode 100644 (file)
index 90dc8d4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-Bundle-ActivationPolicy: lazy
-Service-Component: OSGI-INF/cms-admin-rap.xml
-
-Import-Package: org.argeo.api,\
-org.eclipse.swt,\
-org.eclipse.swt.graphics,\
-org.eclipse.e4.ui.workbench,\
-org.eclipse.rap.rwt.client,\
-org.eclipse.nebula.widgets.richtext;resolution:=optional,\
-*
diff --git a/org.argeo.cms.e4.rap/build.properties b/org.argeo.cms.e4.rap/build.properties
deleted file mode 100644 (file)
index c58ea21..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .,\
-               OSGI-INF/
diff --git a/org.argeo.cms.e4.rap/pom.xml b/org.argeo.cms.e4.rap/pom.xml
deleted file mode 100644 (file)
index 81f2fca..0000000
+++ /dev/null
@@ -1,41 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.e4.rap</artifactId>
-       <name>CMS E4 RAP</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.e4</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <!-- Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-
-               <!-- UI -->
-               <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.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java
deleted file mode 100644 (file)
index 8c43a80..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.ui.dialogs.CmsFeedback;
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Base class for CMS RAP applications. */
-public abstract class AbstractRapE4App implements ApplicationConfiguration {
-       private String e4Xmi;
-       private String path;
-       private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
-       private Map<String, String> baseProperties = new HashMap<String, String>();
-
-       private BundleContext bundleContext;
-       public final static String CONTEXT_NAME_PROPERTY = "contextName";
-       private String contextName;
-
-       /**
-        * To be overridden in order to add multiple entry points, directly or using
-        * {@link #addE4EntryPoint(Application, String, String, Map)}.
-        */
-       protected void addEntryPoints(Application application) {
-       }
-
-       public void configure(Application application) {
-               application.setExceptionHandler(new ExceptionHandler() {
-
-                       @Override
-                       public void handleException(Throwable throwable) {
-                               CmsFeedback.show("Unexpected RWT exception", throwable);
-                       }
-               });
-
-               if (e4Xmi != null) {// backward compatibility
-                       addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
-               } else {
-                       addEntryPoints(application);
-               }
-       }
-
-       protected Map<String, String> getBaseProperties() {
-               return baseProperties;
-       }
-
-//     protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
-//             CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
-//             application.addEntryPoint(path, entryPointFactory, properties);
-//             application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-//     }
-
-       protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
-               E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
-               CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
-               application.addEntryPoint(path, entryPointFactory, properties);
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-       }
-
-       /**
-        * To be overridden for further configuration.
-        * 
-        * @see E4ApplicationConfig
-        */
-       protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
-               return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
-       }
-
-       @Deprecated
-       public void setPageTitle(String pageTitle) {
-               if (pageTitle != null)
-                       baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
-       }
-
-       /** Returns a new map used to customise and entry point. */
-       public Map<String, String> customise(String pageTitle) {
-               Map<String, String> custom = new HashMap<>(getBaseProperties());
-               if (pageTitle != null)
-                       custom.put(WebClient.PAGE_TITLE, pageTitle);
-               return custom;
-       }
-
-       @Deprecated
-       public void setE4Xmi(String e4Xmi) {
-               this.e4Xmi = e4Xmi;
-       }
-
-       @Deprecated
-       public void setPath(String path) {
-               this.path = path;
-       }
-
-       public void setLifeCycleUri(String lifeCycleUri) {
-               this.lifeCycleUri = lifeCycleUri;
-       }
-
-       protected BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       protected void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-       public String getContextName() {
-               return contextName;
-       }
-
-       public void setContextName(String contextName) {
-               this.contextName = contextName;
-       }
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               this.bundleContext = bundleContext;
-               for (String key : properties.keySet()) {
-                       Object value = properties.get(key);
-                       if (value != null)
-                               baseProperties.put(key, value.toString());
-               }
-
-               if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
-                       assert properties.get(CONTEXT_NAME_PROPERTY) != null;
-                       contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
-               } else {
-                       contextName = "<unknown context>";
-               }
-       }
-
-       public void destroy(Map<String, Object> properties) {
-
-       }
-}
diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java
deleted file mode 100644 (file)
index 66be1e8..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import org.eclipse.rap.rwt.application.Application;
-
-/**
- * Access to canonical views of the core CMS concepts, useful for devleopers and
- * operators.
- */
-public class CmsE4AdminApp extends AbstractRapE4App {
-       @Override
-       protected void addEntryPoints(Application application) {
-               addE4EntryPoint(application, "/devops", "org.argeo.cms.e4/e4xmi/cms-devops.e4xmi",
-                               customise("Argeo CMS DevOps"));
-               addE4EntryPoint(application, "/ego", "org.argeo.cms.e4/e4xmi/cms-ego.e4xmi", customise("Argeo CMS Ego"));
-       }
-
-}
diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java
deleted file mode 100644 (file)
index a5a3234..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.security.PrivilegedAction;
-
-import javax.security.auth.Subject;
-
-import org.eclipse.rap.e4.E4ApplicationConfig;
-import org.eclipse.rap.e4.E4EntryPointFactory;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-
-public class CmsE4EntryPointFactory extends E4EntryPointFactory {
-       public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
-
-       public CmsE4EntryPointFactory(E4ApplicationConfig config) {
-               super(config);
-       }
-
-       public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
-               super(defaultConfig(e4Xmi, lifeCycleUri));
-       }
-
-       public CmsE4EntryPointFactory(String e4Xmi) {
-               this(e4Xmi, DEFAULT_LIFECYCLE_URI);
-       }
-
-       public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
-               E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
-               return config;
-       }
-
-       @Override
-       public EntryPoint create() {
-               EntryPoint ep = createEntryPoint();
-               EntryPoint authEp = new EntryPoint() {
-
-                       @Override
-                       public int createUI() {
-                               Subject subject = new Subject();
-                               return Subject.doAs(subject, new PrivilegedAction<Integer>() {
-
-                                       @Override
-                                       public Integer run() {
-                                               // SPNEGO
-                                               // HttpServletRequest request = RWT.getRequest();
-                                               // String authorization = request.getHeader(HEADER_AUTHORIZATION);
-                                               // if (authorization == null || !authorization.startsWith("Negotiate")) {
-                                               // HttpServletResponse response = RWT.getResponse();
-                                               // response.setStatus(401);
-                                               // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
-                                               // response.setDateHeader("Date", System.currentTimeMillis());
-                                               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60
-                                               // * 1000));
-                                               // response.setHeader("Accept-Ranges", "bytes");
-                                               // response.setHeader("Connection", "Keep-Alive");
-                                               // response.setHeader("Keep-Alive", "timeout=5, max=97");
-                                               // // response.setContentType("text/html; charset=UTF-8");
-                                               // }
-
-                                               JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-                                               Integer exitCode = ep.createUI();
-                                               jsExecutor.execute("location.reload()");
-                                               return exitCode;
-                                       }
-
-                               });
-                       }
-               };
-               return authEp;
-       }
-
-       protected EntryPoint createEntryPoint() {
-               return super.create();
-       }
-}
diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
deleted file mode 100644 (file)
index 12ee3c5..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.security.AccessController;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.UxContext;
-import org.argeo.cms.ui.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.SimpleImageManager;
-import org.argeo.cms.ui.util.SimpleUxContext;
-import org.argeo.cms.ui.widgets.auth.CmsLoginShell;
-import org.eclipse.e4.core.services.events.IEventBroker;
-import org.eclipse.e4.ui.workbench.UIEvents;
-import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
-import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventHandler;
-
-@SuppressWarnings("restriction")
-public class CmsLoginLifecycle implements CmsView {
-       private final static Log log = LogFactory.getLog(CmsLoginLifecycle.class);
-
-       private UxContext uxContext;
-       private CmsImageManager imageManager;
-
-       private LoginContext loginContext;
-       private BrowserNavigation browserNavigation;
-
-       private String state = null;
-       private String uid;
-
-       @PostContextCreate
-       boolean login(final IEventBroker eventBroker) {
-               uid = UUID.randomUUID().toString();
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
-                               private static final long serialVersionUID = -3668136623771902865L;
-
-                               @Override
-                               public void navigated(BrowserNavigationEvent event) {
-                                       state = event.getState();
-                                       if (uxContext != null)// is logged in
-                                               stateChanged();
-                               }
-                       });
-
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               Display display = Display.getCurrent();
-//             UiContext.setData(CmsView.KEY, this);
-               CmsLoginShell loginShell = new CmsLoginShell(this);
-               CmsView.registerCmsView(loginShell.getShell(), this);
-               loginShell.setSubject(subject);
-               try {
-                       // try pre-auth
-                       loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject, loginShell);
-                       loginContext.login();
-               } catch (LoginException e) {
-                       loginShell.createUi();
-                       loginShell.open();
-
-                       while (!loginShell.getShell().isDisposed()) {
-                               if (!display.readAndDispatch())
-                                       display.sleep();
-                       }
-               }
-               if (CurrentUser.getUsername(getSubject()) == null)
-                       return false;
-               uxContext = new SimpleUxContext();
-               imageManager = new SimpleImageManager();
-
-               eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
-                       @Override
-                       public void handleEvent(Event event) {
-                               startupComplete();
-                               eventBroker.unsubscribe(this);
-                       }
-               });
-
-               // lcs.changeApplicationLocale(Locale.FRENCH);
-               return true;
-       }
-
-       @PreSave
-       void destroy() {
-               // logout();
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               browserNavigation.pushState(state, state);
-       }
-
-       @Override
-       public void authChange(LoginContext loginContext) {
-               if (loginContext == null)
-                       throw new IllegalArgumentException("Login context cannot be null");
-               // logout previous login context
-               // if (this.loginContext != null)
-               // try {
-               // this.loginContext.logout();
-               // } catch (LoginException e1) {
-               // System.err.println("Could not log out: " + e1);
-               // }
-               this.loginContext = loginContext;
-       }
-
-       @Override
-       public void logout() {
-               if (loginContext == null)
-                       throw new IllegalStateException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       throw new IllegalStateException("Cannot log out", e);
-               }
-       }
-
-       @Override
-       public void exception(Throwable e) {
-               String msg = "Unexpected exception in Eclipse 4 RAP";
-               log.error(msg, e);
-               CmsFeedback.show(msg, e);
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       // CALLBACKS
-       protected void startupComplete() {
-       }
-
-       protected void stateChanged() {
-
-       }
-
-       // GETTERS
-       protected BrowserNavigation getBrowserNavigation() {
-               return browserNavigation;
-       }
-
-       protected String getState() {
-               return state;
-       }
-
-}
diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java
deleted file mode 100644 (file)
index 12c4f63..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.e4.rap;
-
-import java.util.Enumeration;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.eclipse.rap.rwt.application.Application;
-import org.osgi.framework.Bundle;
-
-/** Simple RAP app which loads all e4xmi files. */
-public class SimpleRapE4App extends AbstractRapE4App {
-       private final static Log log = LogFactory.getLog(SimpleRapE4App.class);
-
-       private String baseE4xmi = "/e4xmi";
-
-       @Override
-       protected void addEntryPoints(Application application) {
-               Bundle bundle = getBundleContext().getBundle();
-               Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
-               while (paths.hasMoreElements()) {
-                       String p = paths.nextElement();
-                       if (p.endsWith(".e4xmi")) {
-                               String e4xmiPath = bundle.getSymbolicName() + '/' + p;
-                               String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
-                               addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
-                               if (log.isDebugEnabled())
-                                       log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
-                       }
-               }
-       }
-
-}
diff --git a/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java
deleted file mode 100644 (file)
index 1122f19..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse 4 RAP specific extensions. */
-package org.argeo.cms.e4.rap;
\ No newline at end of file
diff --git a/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 3fd7fda..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.ui.widgets.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 6fa13d4..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="javax.transaction.UserTransaction" 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 8714dab..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.ui.widgets.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 dc80445..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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.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>
-        </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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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.ui.theme/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 fd1842a..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.1-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.1-SNAPSHOT</version>
-               </dependency>
-
-               <!-- UI -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-                       <version>2.1-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 326a67e..0000000
+++ /dev/null
@@ -1,105 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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 1eb6dae..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.api.NodeUtils;
-import org.argeo.cms.CmsException;
-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 5275637..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 javax.transaction.UserTransaction;
-
-import org.argeo.api.security.CryptoKeyring;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.ui.dialogs.CmsMessageDialog;
-import org.argeo.eclipse.ui.dialogs.ErrorFeedback;
-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 UserTransaction 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 4fc1d98..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.NodeConstants;
-import org.argeo.api.security.CryptoKeyring;
-import org.argeo.api.security.Keyring;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.jcr.JcrBrowserUtils;
-import org.argeo.cms.ui.jcr.NodeContentProvider;
-import org.argeo.cms.ui.jcr.NodeLabelProvider;
-import org.argeo.cms.ui.jcr.OsgiRepositoryRegister;
-import org.argeo.cms.ui.jcr.PropertiesContentProvider;
-import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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(CmsUiUtils.noSpaceGridLayout());
-
-               try {
-                       this.userSession = this.nodeRepository.login(NodeConstants.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(CmsUiUtils.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 7b1e19d..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.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.api.security.Keyring;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.cms.e4.jcr.JcrBrowserView;
-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(NodeConstants.LABELED_URI, checkedUriStr);
-                               Repository repository = repositoryFactory.getRepository(params);
-                               if (username.getText().trim().equals("")) {// anonymous
-                                       // FIXME make it more generic
-                                       session = repository.login(NodeConstants.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 = NodeUtils.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 c831831..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import java.util.Collection;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 Log log = LogFactory.getLog(getClass());
-
-       public AbstractOsgiComposite(Composite parent, int style) {
-               super(parent, style);
-               parent.setLayout(CmsUiUtils.noSpaceGridLayout());
-               setLayout(CmsUiUtils.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 2212b7b..0000000
+++ /dev/null
@@ -1,575 +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.cms.CmsException;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsLink;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 = CmsUiUtils.noSpaceGridLayout();
-               layout.numColumns = 2;
-               parent.setLayout(layout);
-
-               // Left
-               Composite leftCmp = new Composite(parent, SWT.NO_FOCUS);
-               leftCmp.setLayoutData(CmsUiUtils.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 = CmsUiUtils.noSpaceGridLayout();
-               parent.setLayout(layout);
-               Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
-               filterCmp.setLayoutData(CmsUiUtils.fillWidth());
-
-               // top filter
-               addFilterPanel(filterCmp);
-
-               // scrolled composite
-               scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
-               scrolledCmp.setLayoutData(CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.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;
-                       CmsUiUtils.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(CmsUiUtils.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 Point imageWidth = new Point(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);
-               CmsUiUtils.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 = CmsUiUtils.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(CmsUiUtils.noSpaceGridLayout());
-
-                       entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE);
-                       Table table = entityViewer.getTable();
-
-                       table.setLayoutData(CmsUiUtils.fillAll());
-                       table.setLinesVisible(true);
-                       table.setHeaderVisible(false);
-                       CmsUiUtils.markup(table);
-
-                       CmsUiUtils.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 7e8a991..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import org.argeo.cms.ui.util.CmsUiUtils;
-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));
-               CmsUiUtils.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 a750e95..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.NodeConstants;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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,
-                               "(" + NodeConstants.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(NodeConstants.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));
-               CmsUiUtils.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 a746b3c..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeDeployment;
-import org.argeo.api.NodeState;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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<NodeState> nodeStateRef = bc.getServiceReference(NodeState.class);
-               if (nodeStateRef == null)
-                       throw new IllegalStateException("No CMS state available");
-               NodeState nodeState = bc.getService(nodeStateRef);
-               ServiceReference<NodeDeployment> nodeDeploymentRef = bc.getServiceReference(NodeDeployment.class);
-               Label label = new Label(composite, SWT.WRAP);
-               CmsUiUtils.markup(label);
-               if (nodeDeploymentRef == null) {
-                       label.setText("Not yet deployed on <br>" + nodeState.getHostname() + "</br>, please configure below.");
-               } else {
-                       Object stateUuid = nodeStateRef.getProperty(NodeConstants.CN);
-                       NodeDeployment 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);
-               CmsUiUtils.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 05923fb..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.ui.util.CmsUiUtils;
-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));
-               CmsUiUtils.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 122f5cf..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.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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(CmsUiUtils.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 4edbf56..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.cms.e4.maintenance;
-
-import java.net.URI;
-
-import org.argeo.cms.ui.util.CmsUiUtils;
-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));
-               CmsUiUtils.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 0c73fd7..0000000
+++ /dev/null
@@ -1,174 +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.cms.CmsException;
-import org.argeo.cms.auth.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).getAuthorization().toString();
-                       }
-
-                       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 CmsException("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 8499356..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.argeo.cms.e4.parts;
-
-import static org.argeo.cms.ui.util.CmsUiUtils.lbl;
-import static org.argeo.cms.ui.util.CmsUiUtils.txt;
-
-import java.security.AccessController;
-import java.time.ZonedDateTime;
-
-import javax.annotation.PostConstruct;
-import javax.security.auth.Subject;
-
-import org.argeo.cms.auth.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-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();
-
-               lbl(p, "<strong>" + CurrentUser.getDisplayName() + "</strong>");
-               txt(p, username);
-               lbl(p, "Roles:");
-               roles: for (String role : CurrentUser.roles()) {
-                       if (username.equals(role))
-                               continue roles;
-                       txt(p, role);
-               }
-
-               Subject subject = Subject.getSubject(AccessController.getContext());
-               if (subject != null) {
-                       CmsSession cmsSession = CmsSession.getCmsSession(bc, subject);
-                       ZonedDateTime loggedIndSince = cmsSession.getCreationTime();
-                       lbl(p, "Session:");
-                       txt(p, cmsSession.getUuid().toString());
-                       lbl(p, "Logged in since:");
-                       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 31f2d1c..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.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 2c75049..0000000
+++ /dev/null
@@ -1,572 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import static org.argeo.api.NodeInstance.WORKGROUP;
-import static org.argeo.cms.auth.UserAdminUtils.setProperty;
-import static org.argeo.naming.LdapAttrs.businessCategory;
-import static org.argeo.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 javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.transaction.UserTransaction;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeInstance;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.CmsException;
-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.ui.eclipse.forms.AbstractFormPart;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.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 NodeInstance 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(NodeConstants.SRV_WORKSPACE);
-               } catch (RepositoryException e) {
-                       throw new CmsException("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(CmsUiUtils.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 = NodeUtils.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 = NodeUtils.getGroupHome(groupsSession, cn);
-                                       if (workgroupHome != null)
-                                               return; // already marked as workgroup, do nothing
-                                       else
-                                               try {
-                                                       // improve transaction management
-                                                       userAdminWrapper.beginTransactionIfNeeded();
-                                                       nodeInstance.createWorkgroup(new LdapName(group.getName()));
-                                                       setProperty(group, businessCategory, WORKGROUP);
-                                                       userAdminWrapper.commitOrNotifyTransactionStateChange();
-                                                       userAdminWrapper
-                                                                       .notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group));
-                                                       part.refresh();
-                                               } catch (InvalidNameException e1) {
-                                                       throw new CmsException("Cannot create Workgroup for " + group.toString(), e1);
-                                               }
-
-                               }
-                       }
-               });
-
-               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(CmsUiUtils.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
-                               UserTransaction transaction = userAdminWrapper.beginTransactionIfNeeded();
-                               User user = (User) role;
-                               myGroup.addMember(user);
-                               if (UserAdminWrapper.COMMIT_ON_SAVE)
-                                       try {
-                                               transaction.commit();
-                                       } catch (Exception e) {
-                                               throw new CmsException("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 9104a6a..0000000
+++ /dev/null
@@ -1,252 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-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.naming.LdapAttrs;
-import org.argeo.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 Log log = LogFactory.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(NodeConstants.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(NodeConstants.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(NodeConstants.TOKENS_BASEDN)
-                                                       .append("))");
-
-                                       if (!showSystemRoles)
-                                               builder.append("(!(").append(LdapAttrs.DN).append("=*").append(NodeConstants.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(NodeConstants.ROLES_BASEDN).append("))(!(").append(LdapAttrs.DN).append("=*")
-                                                               .append(NodeConstants.TOKENS_BASEDN).append(")))");
-                                       else
-                                               builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=")
-                                                               .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*")
-                                                               .append(NodeConstants.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 a5fb610..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import javax.transaction.UserTransaction;
-
-/** 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(
-                       UserTransaction 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 60d232a..0000000
+++ /dev/null
@@ -1,155 +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 javax.transaction.Status;
-import javax.transaction.UserTransaction;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-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 UserTransaction 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 UserTransaction beginTransactionIfNeeded() {
-               try {
-                       // UserTransaction userTransaction = getUserTransaction();
-                       if (userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION) {
-                               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.getStatus() == Status.STATUS_NO_TRANSACTION)
-                               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(NodeConstants.ROLES_BASEDN))
-                               continue;
-                       if (baseDn.equalsIgnoreCase(NodeConstants.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 UserTransaction 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(UserTransaction 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 7513102..0000000
+++ /dev/null
@@ -1,625 +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 javax.transaction.SystemException;
-import javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-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.naming.LdapAttrs;
-import org.argeo.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 Log log = LogFactory.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;
-               UserTransaction ut = userAdminWrapper.getUserTransaction();
-               try {
-                       if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION
-                                       && !MessageDialog.openConfirm(getShell(), "Existing Transaction",
-                                                       "A user transaction is already existing, " + "are you sure you want to proceed ?"))
-                               return false;
-               } catch (SystemException e) {
-                       throw new CmsException("Cannot get user transaction state " + "before user batch update", e);
-               }
-
-               // 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 {
-                               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();
-                               }
-                       }
-               }
-       }
-
-       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 {
-                               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 block, the system might be in a dirty state");
-                                       e.printStackTrace();
-                               }
-                       }
-               }
-       }
-
-       // @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(NodeConstants.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(NodeConstants.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 18753f7..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.naming.LdapAttrs.cn;
-import static org.argeo.naming.LdapAttrs.givenName;
-import static org.argeo.naming.LdapAttrs.mail;
-import static org.argeo.naming.LdapAttrs.sn;
-import static org.argeo.naming.LdapAttrs.uid;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.inject.Inject;
-
-import org.argeo.api.NodeConstants;
-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.ui.eclipse.forms.AbstractFormPart;
-//import org.argeo.cms.ui.eclipse.forms.FormToolkit;
-import org.argeo.cms.ui.eclipse.forms.IManagedForm;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.ColumnDefinition;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.parts.LdifUsersTable;
-import org.argeo.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(CmsUiUtils.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(CmsUiUtils.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(NodeConstants.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 a9a4ede..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.cms.e4.users;
-
-import org.argeo.cms.e4.CmsE4Utils;
-import org.argeo.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 4b94f5c..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.NodeConstants;
-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.naming.LdapAttrs;
-import org.argeo.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(NodeConstants.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 e49b110..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.naming.LdapAttrs;
-import org.argeo.osgi.useradmin.UserAdminConf;
-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 1faa90d..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.naming.LdapAttrs;
-import org.argeo.osgi.useradmin.UserAdminConf;
-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 eb88194..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.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 a312c0d..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.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 d9a75b8..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.cms.e4.users.providers;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeInstance;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.cms.e4.users.SecurityAdminImages;
-import org.argeo.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(NodeConstants.ROLES_BASEDN))
-                       return SecurityAdminImages.ICON_ROLE;
-               else if (user.getType() == Role.GROUP) {
-                       String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory);
-                       if (businessCategory != null && businessCategory.equals(NodeInstance.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 e090fe2..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.NodeConstants;
-import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.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(".*(" + NodeConstants.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.pgsql/.classpath b/org.argeo.cms.pgsql/.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/org.argeo.cms.pgsql/.project b/org.argeo.cms.pgsql/.project
new file mode 100644 (file)
index 0000000..ff01ad9
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.pgsql</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.cms.pgsql/bnd.bnd b/org.argeo.cms.pgsql/bnd.bnd
new file mode 100644 (file)
index 0000000..9c73009
--- /dev/null
@@ -0,0 +1 @@
+Import-Package: org.postgresql;version="[42,43)"
diff --git a/org.argeo.cms.pgsql/build.properties b/org.argeo.cms.pgsql/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java b/org.argeo.cms.pgsql/src/org/argeo/cms/pgsql/util/CheckPg.java
new file mode 100644 (file)
index 0000000..9db43df
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.cms.pgsql.util;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.postgresql.Driver;
+
+/** Simple PostgreSQL check. */
+public class CheckPg {
+
+       public List<String> listTables() {
+               String osUser = System.getProperty("user.name");
+
+               String url = "jdbc:postgresql://localhost/" + osUser;
+               Properties props = new Properties();
+               props.setProperty("user", osUser);
+               props.setProperty("password", "changeit");
+               List<String> result = new ArrayList<>();
+
+               Driver driver = new Driver();
+               try (Connection conn = driver.connect(url, props); Statement s = conn.createStatement();) {
+                       s.execute("SELECT * FROM pg_catalog.pg_tables");
+                       ResultSet rs = s.getResultSet();
+                       while (rs.next()) {
+                               result.add(rs.getString("tablename"));
+                       }
+                       return result;
+               } catch (SQLException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public static void main(String[] args) {
+               new CheckPg().listTables().forEach(System.out::println);
+       }
+
+}
diff --git a/org.argeo.cms.ui.rap/.classpath b/org.argeo.cms.ui.rap/.classpath
deleted file mode 100644 (file)
index e801ebf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.cms.ui.rap/.project b/org.argeo.cms.ui.rap/.project
deleted file mode 100644 (file)
index 1a37a67..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ui.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.cms.ui.rap/META-INF/.gitignore b/org.argeo.cms.ui.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.cms.ui.rap/bnd.bnd b/org.argeo.cms.ui.rap/bnd.bnd
deleted file mode 100644 (file)
index 7f5c929..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-Import-Package:\
-org.eclipse.swt,\
-org.argeo.eclipse.ui,\
-javax.jcr.nodetype,\
-javax.jcr.security,\
-org.eclipse.swt.graphics,\
-*
diff --git a/org.argeo.cms.ui.rap/build.properties b/org.argeo.cms.ui.rap/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.cms.ui.rap/pom.xml b/org.argeo.cms.ui.rap/pom.xml
deleted file mode 100644 (file)
index 37fdd9a..0000000
+++ /dev/null
@@ -1,64 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.ui.rap</artifactId>
-       <name>CMS UI RAP</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <!-- Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-
-               <!-- Theme -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui.theme</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </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.rap/src/org/argeo/cms/ui/script/AppUi.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java
deleted file mode 100644 (file)
index 15b1817..0000000
+++ /dev/null
@@ -1,268 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptException;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsPane;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.argeo.eclipse.ui.Selected;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-import org.osgi.framework.BundleContext;
-
-public class AppUi implements CmsUiProvider, Branding {
-       private final CmsScriptApp app;
-
-       private CmsUiProvider ui;
-       private String createUi;
-       private Object impl;
-       private String script;
-       // private Branding branding = new Branding();
-
-       private EntryPointFactory factory;
-
-       // Branding
-       private String themeId;
-       private String additionalHeaders;
-       private String bodyHtml;
-       private String pageTitle;
-       private String pageOverflow;
-       private String favicon;
-
-       public AppUi(CmsScriptApp app) {
-               this.app = app;
-       }
-
-       public AppUi(CmsScriptApp app, String scriptPath) {
-               this.app = app;
-               this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC),
-                               app.getScriptEngine(), scriptPath);
-       }
-
-       public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) {
-               this.app = app;
-               this.ui = uiProvider;
-       }
-
-       public AppUi(CmsScriptApp app, EntryPointFactory factory) {
-               this.app = app;
-               this.factory = factory;
-       }
-
-       public void apply(Repository repository, Application application, Branding appBranding, String path) {
-               Map<String, String> factoryProperties = new HashMap<>();
-               if (appBranding != null)
-                       appBranding.applyBranding(factoryProperties);
-               applyBranding(factoryProperties);
-               if (factory != null) {
-                       application.addEntryPoint("/" + path, factory, factoryProperties);
-               } else {
-                       EntryPointFactory entryPointFactory = new EntryPointFactory() {
-                               @Override
-                               public EntryPoint create() {
-                                       SimpleErgonomics ergonomics = new SimpleErgonomics(repository, NodeConstants.SYS_WORKSPACE,
-                                                       "/home/root/argeo:keyring", AppUi.this, factoryProperties);
-//                                     CmsUiProvider header = app.getHeader();
-//                                     if (header != null)
-//                                             ergonomics.setHeader(header);
-                                       app.applySides(ergonomics);
-                                       Integer headerHeight = app.getHeaderHeight();
-                                       if (headerHeight != null)
-                                               ergonomics.setHeaderHeight(headerHeight);
-                                       return ergonomics;
-                               }
-                       };
-                       application.addEntryPoint("/" + path, entryPointFactory, factoryProperties);
-               }
-       }
-
-       public void setUi(CmsUiProvider uiProvider) {
-               this.ui = uiProvider;
-       }
-
-       public void applyBranding(Map<String, String> properties) {
-               if (themeId != null)
-                       properties.put(WebClient.THEME_ID, themeId);
-               if (additionalHeaders != null)
-                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
-               if (bodyHtml != null)
-                       properties.put(WebClient.BODY_HTML, bodyHtml);
-               if (pageTitle != null)
-                       properties.put(WebClient.PAGE_TITLE, pageTitle);
-               if (pageOverflow != null)
-                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
-               if (favicon != null)
-                       properties.put(WebClient.FAVICON, favicon);
-       }
-
-       // public Branding getBranding() {
-       // return branding;
-       // }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               CmsPane cmsPane = new CmsPane(parent, SWT.NONE);
-
-               if (false) {
-                       // QA
-                       CmsUiUtils.style(cmsPane.getQaArea(), "qa");
-                       Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT);
-                       CmsUiUtils.style(reload, "qa");
-                       reload.setText("Reload");
-                       reload.addSelectionListener(new Selected() {
-                               private static final long serialVersionUID = 1L;
-
-                               @Override
-                               public void widgetSelected(SelectionEvent e) {
-                                       new Thread() {
-                                               @Override
-                                               public void run() {
-                                                       app.reload();
-                                               }
-                                       }.start();
-                                       RWT.getClient().getService(JavaScriptExecutor.class)
-                                                       .execute("setTimeout('location.reload()',1000)");
-                               }
-                       });
-
-                       // Support
-                       CmsUiUtils.style(cmsPane.getSupportArea(), "support");
-                       Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE);
-                       CmsUiUtils.style(msg, "support");
-                       msg.setText("UNSUPPORTED DEVELOPMENT VERSION");
-               }
-
-               if (ui != null) {
-                       ui.createUi(cmsPane.getMainArea(), context);
-               }
-               if (createUi != null) {
-                       Invocable invocable = (Invocable) app.getScriptEngine();
-                       try {
-                               invocable.invokeFunction(createUi, cmsPane.getMainArea(), context);
-
-                       } catch (NoSuchMethodException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       } catch (ScriptException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       }
-               }
-               if (impl != null) {
-                       Invocable invocable = (Invocable) app.getScriptEngine();
-                       try {
-                               invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context);
-
-                       } catch (NoSuchMethodException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       } catch (ScriptException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
-                       }
-               }
-
-               // Invocable invocable = (Invocable) app.getScriptEngine();
-               // try {
-               // invocable.invokeMethod(AppUi.this, "initUi", parent, context);
-               //
-               // } catch (NoSuchMethodException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // } catch (ScriptException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
-
-               return null;
-       }
-
-       public void setCreateUi(String createUi) {
-               this.createUi = createUi;
-       }
-
-       public void setImpl(Object impl) {
-               this.impl = impl;
-       }
-
-       public Object getImpl() {
-               return impl;
-       }
-
-       public String getScript() {
-               return script;
-       }
-
-       public void setScript(String script) {
-               this.script = script;
-       }
-
-       // Branding
-       public String getThemeId() {
-               return themeId;
-       }
-
-       public void setThemeId(String themeId) {
-               this.themeId = themeId;
-       }
-
-       public String getAdditionalHeaders() {
-               return additionalHeaders;
-       }
-
-       public void setAdditionalHeaders(String additionalHeaders) {
-               this.additionalHeaders = additionalHeaders;
-       }
-
-       public String getBodyHtml() {
-               return bodyHtml;
-       }
-
-       public void setBodyHtml(String bodyHtml) {
-               this.bodyHtml = bodyHtml;
-       }
-
-       public String getPageTitle() {
-               return pageTitle;
-       }
-
-       public void setPageTitle(String pageTitle) {
-               this.pageTitle = pageTitle;
-       }
-
-       public String getPageOverflow() {
-               return pageOverflow;
-       }
-
-       public void setPageOverflow(String pageOverflow) {
-               this.pageOverflow = pageOverflow;
-       }
-
-       public String getFavicon() {
-               return favicon;
-       }
-
-       public void setFavicon(String favicon) {
-               this.favicon = favicon;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java
deleted file mode 100644 (file)
index f72338e..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.util.Map;
-
-public interface Branding {
-       public void applyBranding(Map<String, String> properties);
-
-       public String getThemeId();
-
-       public String getAdditionalHeaders();
-
-       public String getBodyHtml();
-
-       public String getPageTitle();
-
-       public String getPageOverflow();
-
-       public String getFavicon();
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java
deleted file mode 100644 (file)
index 3f09871..0000000
+++ /dev/null
@@ -1,422 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.script.ScriptEngine;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsConstants;
-import org.argeo.cms.ui.CmsTheme;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.web.BundleResourceLoader;
-import org.argeo.cms.web.SimpleErgonomics;
-import org.argeo.cms.web.WebThemeUtils;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-
-public class CmsScriptApp implements Branding {
-       public final static String CONTEXT_NAME = "contextName";
-
-       ServiceRegistration<ApplicationConfiguration> appConfigReg;
-
-       private ScriptEngine scriptEngine;
-
-       private final static Log log = LogFactory.getLog(CmsScriptApp.class);
-
-       private String webPath;
-       private String repo = "(cn=node)";
-
-       // private Branding branding = new Branding();
-       private CmsTheme theme;
-
-       private List<String> resources = new ArrayList<>();
-
-       private Map<String, AppUi> ui = new HashMap<>();
-
-       private CmsUiProvider header;
-       private Integer headerHeight = null;
-       private CmsUiProvider lead;
-       private CmsUiProvider end;
-       private CmsUiProvider footer;
-
-       // Branding
-       private String themeId;
-       private String additionalHeaders;
-       private String bodyHtml;
-       private String pageTitle;
-       private String pageOverflow;
-       private String favicon;
-
-       public CmsScriptApp(ScriptEngine scriptEngine) {
-               super();
-               this.scriptEngine = scriptEngine;
-       }
-
-       public void apply(BundleContext bundleContext, Repository repository, Application application) {
-               BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-               // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
-               application.setExceptionHandler(new CmsExceptionHandler());
-
-               // loading animated gif
-               application.addResource(CmsConstants.LOADING_IMAGE, createResourceLoader(CmsConstants.LOADING_IMAGE));
-               // empty image
-               application.addResource(CmsConstants.NO_IMAGE, createResourceLoader(CmsConstants.NO_IMAGE));
-
-               for (String resource : resources) {
-                       application.addResource(resource, bundleRL);
-                       if (log.isTraceEnabled())
-                               log.trace("Resource " + resource);
-               }
-
-               if (theme != null) {
-                       WebThemeUtils.apply(application, theme);
-                       String themeHeaders = theme.getHtmlHeaders();
-                       if (themeHeaders != null) {
-                               if (additionalHeaders == null)
-                                       additionalHeaders = themeHeaders;
-                               else
-                                       additionalHeaders = themeHeaders + "\n" + additionalHeaders;
-                       }
-                       themeId = theme.getThemeId();
-               }
-
-               // client JavaScript
-               Bundle appBundle = bundleRL.getBundle();
-               BundleContext bc = appBundle.getBundleContext();
-               HttpService httpService = bc.getService(bc.getServiceReference(HttpService.class));
-               HttpContext httpContext = new BundleHttpContext(bc);
-               Enumeration<URL> themeResources = appBundle.findEntries("/js/", "*", true);
-               if (themeResources != null)
-                       bundleResources: while (themeResources.hasMoreElements()) {
-                               try {
-                                       String name = themeResources.nextElement().getPath();
-                                       if (name.endsWith("/"))
-                                               continue bundleResources;
-                                       String alias = "/" + getWebPath() + name;
-
-                                       httpService.registerResources(alias, name, httpContext);
-                                       if (log.isDebugEnabled())
-                                               log.debug("Mapped " + name + " to alias " + alias);
-
-                               } catch (NamespaceException e) {
-                                       // TODO Auto-generated catch block
-                                       e.printStackTrace();
-                               }
-                       }
-
-               // App UIs
-               for (String appUiName : ui.keySet()) {
-                       AppUi appUi = ui.get(appUiName);
-                       appUi.apply(repository, application, this, appUiName);
-
-               }
-
-       }
-
-       public void applySides(SimpleErgonomics simpleErgonomics) {
-               simpleErgonomics.setHeader(header);
-               simpleErgonomics.setLead(lead);
-               simpleErgonomics.setEnd(end);
-               simpleErgonomics.setFooter(footer);
-       }
-
-       public void register(BundleContext bundleContext, ApplicationConfiguration appConfig) {
-               Hashtable<String, String> props = new Hashtable<>();
-               props.put(CONTEXT_NAME, webPath);
-               appConfigReg = bundleContext.registerService(ApplicationConfiguration.class, appConfig, props);
-       }
-
-       public void reload() {
-               BundleContext bundleContext = appConfigReg.getReference().getBundle().getBundleContext();
-               ApplicationConfiguration appConfig = bundleContext.getService(appConfigReg.getReference());
-               appConfigReg.unregister();
-               register(bundleContext, appConfig);
-
-               // BundleContext bundleContext = (BundleContext)
-               // getScriptEngine().get("bundleContext");
-               // try {
-               // Bundle bundle = bundleContext.getBundle();
-               // bundle.stop();
-               // bundle.start();
-               // } catch (BundleException e) {
-               // // TODO Auto-generated catch block
-               // e.printStackTrace();
-               // }
-       }
-
-       private static ResourceLoader createResourceLoader(final String resourceName) {
-               return new ResourceLoader() {
-                       public InputStream getResourceAsStream(String resourceName) throws IOException {
-                               return getClass().getClassLoader().getResourceAsStream(resourceName);
-                       }
-               };
-       }
-
-       public List<String> getResources() {
-               return resources;
-       }
-
-       public AppUi newUi(String name) {
-               if (ui.containsKey(name))
-                       throw new IllegalArgumentException("There is already an UI named " + name);
-               AppUi appUi = new AppUi(this);
-               // appUi.setApp(this);
-               ui.put(name, appUi);
-               return appUi;
-       }
-
-       public void addUi(String name, AppUi appUi) {
-               if (ui.containsKey(name))
-                       throw new IllegalArgumentException("There is already an UI named " + name);
-               // appUi.setApp(this);
-               ui.put(name, appUi);
-       }
-
-       public void applyBranding(Map<String, String> properties) {
-               if (themeId != null)
-                       properties.put(WebClient.THEME_ID, themeId);
-               if (additionalHeaders != null)
-                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
-               if (bodyHtml != null)
-                       properties.put(WebClient.BODY_HTML, bodyHtml);
-               if (pageTitle != null)
-                       properties.put(WebClient.PAGE_TITLE, pageTitle);
-               if (pageOverflow != null)
-                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
-               if (favicon != null)
-                       properties.put(WebClient.FAVICON, favicon);
-       }
-
-       class CmsExceptionHandler implements ExceptionHandler {
-
-               @Override
-               public void handleException(Throwable throwable) {
-                       // TODO be smarter
-                       CmsUiUtils.getCmsView().exception(throwable);
-               }
-
-       }
-
-       // public Branding getBranding() {
-       // return branding;
-       // }
-
-       ScriptEngine getScriptEngine() {
-               return scriptEngine;
-       }
-
-       public static String toJson(Node node) {
-               try {
-                       StringBuilder sb = new StringBuilder();
-                       sb.append('{');
-                       PropertyIterator pit = node.getProperties();
-                       int count = 0;
-                       while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               int type = p.getType();
-                               if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.PATH) {
-                                       Node ref = p.getNode();
-                                       if (count != 0)
-                                               sb.append(',');
-                                       // TODO limit depth?
-                                       sb.append(toJson(ref));
-                                       count++;
-                               } else if (!p.isMultiple()) {
-                                       if (count != 0)
-                                               sb.append(',');
-                                       sb.append('\"').append(p.getName()).append("\":\"").append(p.getString()).append('\"');
-                                       count++;
-                               }
-                       }
-                       sb.append('}');
-                       return sb.toString();
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot convert " + node + " to JSON", e);
-               }
-       }
-
-       public void fromJson(Node node, String json) {
-               // TODO
-       }
-
-       public CmsTheme getTheme() {
-               return theme;
-       }
-
-       public void setTheme(CmsTheme theme) {
-               this.theme = theme;
-       }
-
-       public String getWebPath() {
-               return webPath;
-       }
-
-       public void setWebPath(String context) {
-               this.webPath = context;
-       }
-
-       public String getRepo() {
-               return repo;
-       }
-
-       public void setRepo(String repo) {
-               this.repo = repo;
-       }
-
-       public Map<String, AppUi> getUi() {
-               return ui;
-       }
-
-       public void setUi(Map<String, AppUi> ui) {
-               this.ui = ui;
-       }
-
-       // Branding
-       public String getThemeId() {
-               return themeId;
-       }
-
-       public void setThemeId(String themeId) {
-               this.themeId = themeId;
-       }
-
-       public String getAdditionalHeaders() {
-               return additionalHeaders;
-       }
-
-       public void setAdditionalHeaders(String additionalHeaders) {
-               this.additionalHeaders = additionalHeaders;
-       }
-
-       public String getBodyHtml() {
-               return bodyHtml;
-       }
-
-       public void setBodyHtml(String bodyHtml) {
-               this.bodyHtml = bodyHtml;
-       }
-
-       public String getPageTitle() {
-               return pageTitle;
-       }
-
-       public void setPageTitle(String pageTitle) {
-               this.pageTitle = pageTitle;
-       }
-
-       public String getPageOverflow() {
-               return pageOverflow;
-       }
-
-       public void setPageOverflow(String pageOverflow) {
-               this.pageOverflow = pageOverflow;
-       }
-
-       public String getFavicon() {
-               return favicon;
-       }
-
-       public void setFavicon(String favicon) {
-               this.favicon = favicon;
-       }
-
-       public CmsUiProvider getHeader() {
-               return header;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public Integer getHeaderHeight() {
-               return headerHeight;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public CmsUiProvider getLead() {
-               return lead;
-       }
-
-       public void setLead(CmsUiProvider lead) {
-               this.lead = lead;
-       }
-
-       public CmsUiProvider getEnd() {
-               return end;
-       }
-
-       public void setEnd(CmsUiProvider end) {
-               this.end = end;
-       }
-
-       public CmsUiProvider getFooter() {
-               return footer;
-       }
-
-       public void setFooter(CmsUiProvider footer) {
-               this.footer = footer;
-       }
-
-       static class BundleHttpContext implements HttpContext {
-               private BundleContext bundleContext;
-
-               public BundleHttpContext(BundleContext bundleContext) {
-                       super();
-                       this.bundleContext = bundleContext;
-               }
-
-               @Override
-               public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
-                       // TODO Auto-generated method stub
-                       return true;
-               }
-
-               @Override
-               public URL getResource(String name) {
-
-                       return bundleContext.getBundle().getEntry(name);
-               }
-
-               @Override
-               public String getMimeType(String name) {
-                       return null;
-               }
-
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java
deleted file mode 100644 (file)
index d7c1a63..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.net.URL;
-
-import javax.jcr.Repository;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.wiring.BundleWiring;
-
-public class CmsScriptRwtApplication implements ApplicationConfiguration {
-       public final static String APP = "APP";
-       public final static String BC = "BC";
-
-       private final Log log = LogFactory.getLog(CmsScriptRwtApplication.class);
-
-       BundleContext bundleContext;
-       Repository repository;
-
-       ScriptEngine engine;
-
-       public void init(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-               ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-               ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
-               try {
-//                     Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
-//                     ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
-//                     engine = scriptEngineManager.getEngineByName("JavaScript");
-//                     if (engine == null) {// Nashorn
-//                             Thread.currentThread().setContextClassLoader(originalCcl);
-//                             scriptEngineManager = new ScriptEngineManager();
-//                             Thread.currentThread().setContextClassLoader(bundleCl);
-//                             engine = scriptEngineManager.getEngineByName("JavaScript");
-//                     }
-                       engine = loadScriptEngine(originalCcl, bundleCl);
-
-                       // Load script
-                       URL appUrl = bundleContext.getBundle().getEntry("cms/app.js");
-                       // System.out.println("Loading " + appUrl);
-                       // System.out.println("Loading " + appUrl.getHost());
-                       // System.out.println("Loading " + appUrl.getPath());
-
-                       CmsScriptApp app = new CmsScriptApp(engine);
-                       engine.put(APP, app);
-                       engine.put(BC, bundleContext);
-                       try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-                               engine.eval(reader);
-                       } catch (IOException | ScriptException e) {
-                               throw new CmsException("Cannot execute " + appUrl, e);
-                       }
-
-                       if (log.isDebugEnabled())
-                               log.debug("CMS script app initialized from " + appUrl);
-
-               } catch (Exception e) {
-                       e.printStackTrace();
-               } finally {
-                       Thread.currentThread().setContextClassLoader(originalCcl);
-               }
-       }
-
-       public void destroy(BundleContext bundleContext) {
-               engine = null;
-       }
-
-       @Override
-       public void configure(Application application) {
-               load(application);
-       }
-
-       void load(Application application) {
-               CmsScriptApp app = getApp();
-               app.apply(bundleContext, repository, application);
-               if (log.isDebugEnabled())
-                       log.debug("CMS script app loaded to " + app.getWebPath());
-       }
-
-       CmsScriptApp getApp() {
-               if (engine == null)
-                       throw new IllegalStateException("CMS script app is not initialized");
-               return (CmsScriptApp) engine.get(APP);
-       }
-
-       void update() {
-
-               try {
-                       bundleContext.getBundle().update();
-               } catch (BundleException e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       private static ScriptEngine loadScriptEngine(ClassLoader originalCcl, ClassLoader bundleCl) {
-               Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
-               ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
-               ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
-               if (engine == null) {// Nashorn
-                       Thread.currentThread().setContextClassLoader(originalCcl);
-                       scriptEngineManager = new ScriptEngineManager();
-                       Thread.currentThread().setContextClassLoader(bundleCl);
-                       engine = scriptEngineManager.getEngineByName("JavaScript");
-               }
-               return engine;
-       }
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java
deleted file mode 100644 (file)
index 7813156..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import javax.jcr.Repository;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class ScriptAppActivator implements BundleActivator {
-       private final static Log log = LogFactory.getLog(ScriptAppActivator.class);
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-               try {
-                       CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication();
-                       appConfig.init(context);
-                       CmsScriptApp app = appConfig.getApp();
-                       ServiceTracker<Repository, Repository> repoSt = new ServiceTracker<Repository, Repository>(context,
-                                       FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) {
-
-                               @Override
-                               public Repository addingService(ServiceReference<Repository> reference) {
-                                       Repository repository = super.addingService(reference);
-                                       appConfig.setRepository(repository);
-                                       CmsScriptApp app = appConfig.getApp();
-                                       app.register(context, appConfig);
-                                       return repository;
-                               }
-
-                       };
-                       repoSt.open();
-               } catch (Exception e) {
-                       log.error("Cannot initialise script bundle " + context.getBundle().getSymbolicName(), e);
-                       throw e;
-               }
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java
deleted file mode 100644 (file)
index bf68fc2..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.argeo.cms.ui.script;
-
-import java.net.URL;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.script.Invocable;
-import javax.script.ScriptEngine;
-import javax.script.ScriptException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.osgi.framework.BundleContext;
-
-class ScriptUi implements CmsUiProvider {
-       private final static Log log = LogFactory.getLog(ScriptUi.class);
-
-       private boolean development = true;
-       private ScriptEngine scriptEngine;
-
-       private URL appUrl;
-       // private BundleContext bundleContext;
-       // private String path;
-
-       // private Bindings bindings;
-       // private String script;
-
-       public ScriptUi(BundleContext bundleContext,ScriptEngine scriptEngine, String path) {
-               this.scriptEngine = scriptEngine;
-////           ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
-//             ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
-//             ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
-//             try {
-////                   Thread.currentThread().setContextClassLoader(bundleCl);
-////                   scriptEngine = scriptEngineManager.getEngineByName("JavaScript");
-////                   scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext);
-//                     scriptEngine = CmsScriptRwtApplication.loadScriptEngine(originalCcl, bundleCl);
-//
-//             } catch (Exception e) {
-//                     e.printStackTrace();
-//             } finally {
-//                     Thread.currentThread().setContextClassLoader(originalCcl);
-//             }
-               this.appUrl = bundleContext.getBundle().getEntry(path);
-               load();
-       }
-
-       private void load() {
-//             try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-//                     scriptEngine.eval(reader);
-//             } catch (IOException | ScriptException e) {
-//                     log.warn("Cannot execute " + appUrl, e);
-//             }
-
-               try {
-                       scriptEngine.eval("load('" + appUrl + "')");
-               } catch (ScriptException e) {
-                       log.warn("Cannot execute " + appUrl, e);
-               }
-
-       }
-
-       // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws
-       // ScriptException {
-       // super();
-       // this.scriptEngine = scriptEngine;
-       // this.script = script;
-       // bindings = scriptEngine.createBindings();
-       // scriptEngine.eval(script, bindings);
-       // }
-
-       @Override
-       public Control createUi(Composite parent, Node context) throws RepositoryException {
-               long begin = System.currentTimeMillis();
-               // if (bindings == null) {
-               // bindings = scriptEngine.createBindings();
-               // try {
-               // scriptEngine.eval(script, bindings);
-               // } catch (ScriptException e) {
-               // log.warn("Cannot evaluate script", e);
-               // }
-               // }
-               // Bindings bindings = scriptEngine.createBindings();
-               // bindings.put("parent", parent);
-               // bindings.put("context", context);
-               // URL appUrl = bundleContext.getBundle().getEntry(path);
-               // try (Reader reader = new InputStreamReader(appUrl.openStream())) {
-               // scriptEngine.eval(reader,bindings);
-               // } catch (IOException | ScriptException e) {
-               // log.warn("Cannot execute " + appUrl, e);
-               // }
-
-               if (development)
-                       load();
-
-               Invocable invocable = (Invocable) scriptEngine;
-               try {
-                       invocable.invokeFunction("createUi", parent, context);
-               } catch (NoSuchMethodException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               } catch (ScriptException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
-               }
-
-               long duration = System.currentTimeMillis() - begin;
-               if (log.isTraceEnabled())
-                       log.trace(appUrl + " UI in " + duration + " ms");
-               return null;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js
deleted file mode 100644 (file)
index be9618d..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-// CMS
-var ScrolledPage = Java.type('org.argeo.cms.ui.widgets.ScrolledPage');
-
-var CmsScriptApp = Java.type('org.argeo.cms.ui.script.CmsScriptApp');
-var AppUi = Java.type('org.argeo.cms.ui.script.AppUi');
-var Theme = Java.type('org.argeo.cms.ui.script.Theme');
-var ScriptUi = Java.type('org.argeo.cms.ui.script.ScriptUi');
-var CmsUtils = Java.type('org.argeo.cms.ui.util.CmsUiUtils');
-var SimpleCmsHeader = Java.type('org.argeo.cms.ui.util.SimpleCmsHeader');
-var CmsLink = Java.type('org.argeo.cms.ui.util.CmsLink');
-var MenuLink = Java.type('org.argeo.cms.ui.util.MenuLink');
-var UserMenuLink = Java.type('org.argeo.cms.ui.util.UserMenuLink');
-
-// SWT
-var SWT = Java.type('org.eclipse.swt.SWT');
-var Composite = Java.type('org.eclipse.swt.widgets.Composite');
-var Label = Java.type('org.eclipse.swt.widgets.Label');
-var Button = Java.type('org.eclipse.swt.widgets.Button');
-var Text = Java.type('org.eclipse.swt.widgets.Text');
-var Browser = Java.type('org.eclipse.swt.browser.Browser');
-
-var FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
-var GridLayout = Java.type('org.eclipse.swt.layout.GridLayout');
-var RowLayout = Java.type('org.eclipse.swt.layout.RowLayout');
-var FormLayout = Java.type('org.eclipse.swt.layout.FormLayout');
-var GridData = Java.type('org.eclipse.swt.layout.GridData');
-
-function loadNode(node) {
-       var json = CmsScriptApp.toJson(node)
-       var fromJson = JSON.parse(json)
-       return fromJson
-}
-
-function newArea(parent, style, layout) {
-       var control = new Composite(parent, SWT.NONE)
-       control.setLayout(layout)
-       CmsUtils.style(control, style)
-       return control
-}
-
-function newLabel(parent, style, text) {
-       var control = new Label(parent, SWT.WRAP)
-       control.setText(text)
-       CmsUtils.style(control, style)
-       CmsUtils.markup(control)
-       return control
-}
-
-function newButton(parent, style, text) {
-       var control = new Button(parent, SWT.FLAT)
-       control.setText(text)
-       CmsUtils.style(control, style)
-       CmsUtils.markup(control)
-       return control
-}
-
-function newFormLabel(parent, style, text) {
-       return newLabel(parent, style, '<b>' + text + '</b>')
-}
-
-function newText(parent, style, msg) {
-       var control = new Text(parent, SWT.NONE)
-       control.setMessage(msg)
-       CmsUtils.style(control, style)
-       return control
-}
-
-function newScrolledPage(parent) {
-       var scrolled = new ScrolledPage(parent, SWT.NONE)
-       scrolled.setLayoutData(CmsUtils.fillAll())
-       scrolled.setLayout(CmsUtils.noSpaceGridLayout())
-       var page = new Composite(scrolled, SWT.NONE)
-       page.setLayout(CmsUtils.noSpaceGridLayout())
-       page.setBackgroundMode(SWT.INHERIT_NONE)
-       return page
-}
-
-function gridData(control) {
-       var gridData = new GridData()
-       control.setLayoutData(gridData)
-       return gridData
-}
-
-function gridData(control, hAlign, vAlign) {
-       var gridData = new GridData(hAlign, vAlign, false, false)
-       control.setLayoutData(gridData)
-       return gridData
-}
-
-// print(__FILE__, __LINE__, __DIR__)
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java
deleted file mode 100644 (file)
index 7440596..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS user interface scripting. */
-package org.argeo.cms.ui.script;
\ No newline at end of file
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java
deleted file mode 100644 (file)
index 887490c..0000000
+++ /dev/null
@@ -1,393 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.argeo.naming.SharedSecret.X_SHARED_SECRET;
-
-import java.io.IOException;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Node;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.HttpRequestCallback;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
-import org.argeo.cms.ui.CmsStyles;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.naming.AuthPassword;
-import org.argeo.naming.SharedSecret;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-
-/** Manages history and navigation */
-@Deprecated
-public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implements CmsView {
-       private static final long serialVersionUID = 906558779562569784L;
-
-       private final Log log = LogFactory.getLog(AbstractCmsEntryPoint.class);
-
-       // private final Subject subject;
-       private LoginContext loginContext;
-
-       private final Repository repository;
-       private final String workspace;
-       private final String defaultPath;
-       private final Map<String, String> factoryProperties;
-
-       // Current state
-       private Session session;
-       private Node node;
-       private String nodePath;// useful when changing auth
-       private String state;
-       private Throwable exception;
-
-       // Client services
-       private final JavaScriptExecutor jsExecutor;
-       private final BrowserNavigation browserNavigation;
-
-       public AbstractCmsEntryPoint(Repository repository, String workspace, String defaultPath,
-                       Map<String, String> factoryProperties) {
-               this.repository = repository;
-               this.workspace = workspace;
-               this.defaultPath = defaultPath;
-               this.factoryProperties = new HashMap<String, String>(factoryProperties);
-               // subject = new Subject();
-
-               // Initial login
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
-                                       new HttpRequestCallbackHandler(UiContext.getHttpRequest(), UiContext.getHttpResponse()));
-                       lc.login();
-               } catch (LoginException e) {
-                       try {
-                               lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS);
-                               lc.login();
-                       } catch (LoginException e1) {
-                               throw new CmsException("Cannot log in as anonymous", e1);
-                       }
-               }
-               authChange(lc);
-
-               jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(new CmsNavigationListener());
-       }
-
-       @Override
-       protected Shell createShell(Display display) {
-               Shell shell = super.createShell(display);
-               shell.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_SHELL);
-               display.disposeExec(new Runnable() {
-
-                       @Override
-                       public void run() {
-                               if (log.isTraceEnabled())
-                                       log.trace("Logging out " + session);
-                               JcrUtils.logoutQuietly(session);
-                       }
-               });
-               return shell;
-       }
-
-       @Override
-       protected final void createContents(final Composite parent) {
-               // UiContext.setData(CmsView.KEY, this);
-               CmsView.registerCmsView(parent.getShell(), this);
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               try {
-                                       initUi(parent);
-                               } catch (Exception e) {
-                                       throw new CmsException("Cannot create entrypoint contents", e);
-                               }
-                               return null;
-                       }
-               });
-       }
-
-       /** Create UI */
-       protected abstract void initUi(Composite parent);
-
-       /** Recreate UI after navigation or auth change */
-       protected abstract void refresh();
-
-       /**
-        * The node to return when no node was found (for authenticated users and
-        * anonymous)
-        */
-//     private Node getDefaultNode(Session session) throws RepositoryException {
-//             if (!session.hasPermission(defaultPath, "read")) {
-//                     String userId = session.getUserID();
-//                     if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
-//                             // TODO throw a special exception
-//                             throw new CmsException("Login required");
-//                     else
-//                             throw new CmsException("Unauthorized");
-//             }
-//             return session.getNode(defaultPath);
-//     }
-
-       protected String getBaseTitle() {
-               return factoryProperties.get(WebClient.PAGE_TITLE);
-       }
-
-       public void navigateTo(String state) {
-               exception = null;
-               String title = setState(state);
-               doRefresh();
-               if (browserNavigation != null)
-                       browserNavigation.pushState(state, title);
-       }
-
-       // @Override
-       // public synchronized Subject getSubject() {
-       // return subject;
-       // }
-
-       // @Override
-       // public LoginContext getLoginContext() {
-       // return loginContext;
-       // }
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public synchronized void logout() {
-               if (loginContext == null)
-                       throw new CmsException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-                       LoginContext anonymousLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS);
-                       anonymousLc.login();
-                       authChange(anonymousLc);
-               } catch (LoginException e) {
-                       log.error("Cannot logout", e);
-               }
-       }
-
-       @Override
-       public synchronized void authChange(LoginContext lc) {
-               if (lc == null)
-                       throw new CmsException("Login context cannot be null");
-               // logout previous login context
-               if (this.loginContext != null)
-                       try {
-                               this.loginContext.logout();
-                       } catch (LoginException e1) {
-                               log.warn("Could not log out: " + e1);
-                       }
-               this.loginContext = lc;
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               try {
-                                       JcrUtils.logoutQuietly(session);
-                                       session = repository.login(workspace);
-                                       if (nodePath != null)
-                                               try {
-                                                       node = session.getNode(nodePath);
-                                               } catch (PathNotFoundException e) {
-                                                       navigateTo("~");
-                                               }
-
-                                       // refresh UI
-                                       doRefresh();
-                               } catch (RepositoryException e) {
-                                       throw new CmsException("Cannot perform auth change", e);
-                               }
-                               return null;
-                       }
-
-               });
-       }
-
-       @Override
-       public void exception(final Throwable e) {
-               AbstractCmsEntryPoint.this.exception = e;
-               log.error("Unexpected exception in CMS", e);
-               doRefresh();
-       }
-
-       protected synchronized void doRefresh() {
-               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               refresh();
-                               return null;
-                       }
-               });
-       }
-
-       /** Sets the state of the entry point and retrieve the related JCR node. */
-       protected synchronized String setState(String newState) {
-               String previousState = this.state;
-
-               String newNodePath = null;
-               String prefix = null;
-               this.state = newState;
-               if (newState.equals("~"))
-                       this.state = "";
-
-               try {
-                       int firstSlash = state.indexOf('/');
-                       if (firstSlash == 0) {
-                               newNodePath = state;
-                               prefix = "";
-                       } else if (firstSlash > 0) {
-                               prefix = state.substring(0, firstSlash);
-                               newNodePath = state.substring(firstSlash);
-                       } else {
-                               newNodePath = defaultPath;
-                               prefix = state;
-
-                       }
-
-                       // auth
-                       int colonIndex = prefix.indexOf('$');
-                       if (colonIndex > 0) {
-                               SharedSecret token = new SharedSecret(new AuthPassword(X_SHARED_SECRET + '$' + prefix)) {
-
-                                       @Override
-                                       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                               super.handle(callbacks);
-                                               // handle HTTP context
-                                               for (Callback callback : callbacks) {
-                                                       if (callback instanceof HttpRequestCallback) {
-                                                               ((HttpRequestCallback) callback).setRequest(UiContext.getHttpRequest());
-                                                               ((HttpRequestCallback) callback).setResponse(UiContext.getHttpResponse());
-                                                       }
-                                               }
-                                       }
-                               };
-                               LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
-                               lc.login();
-                               authChange(lc);// sets the node as well
-                               // } else {
-                               // // TODO check consistency
-                               // }
-                       } else {
-                               Node newNode = null;
-                               if (session.nodeExists(newNodePath))
-                                       newNode = session.getNode(newNodePath);
-                               else {
-//                                     throw new CmsException("Data " + newNodePath + " does not exist");
-                                       newNode = null;
-                               }
-                               setNode(newNode);
-                       }
-                       String title = publishMetaData(getNode());
-
-                       if (log.isTraceEnabled())
-                               log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
-
-                       return title;
-               } catch (Exception e) {
-                       log.error("Cannot set state '" + state + "'", e);
-                       if (state.equals("") || newState.equals("~") || newState.equals(previousState))
-                               return "Unrecoverable exception : " + e.getClass().getSimpleName();
-                       if (previousState.equals(""))
-                               previousState = "~";
-                       navigateTo(previousState);
-                       throw new CmsException("Unexpected issue when accessing #" + newState, e);
-               }
-       }
-
-       private String publishMetaData(Node node) throws RepositoryException {
-               // Title
-               String title;
-               if (node != null && node.isNodeType(NodeType.MIX_TITLE) && node.hasProperty(Property.JCR_TITLE))
-                       title = node.getProperty(Property.JCR_TITLE).getString() + " - " + getBaseTitle();
-               else
-                       title = getBaseTitle();
-
-               HttpServletRequest request = UiContext.getHttpRequest();
-               if (request == null)
-                       return null;
-
-               StringBuilder js = new StringBuilder();
-               if (title == null)
-                       title = "";
-               title = title.replace("'", "\\'");// sanitize
-               js.append("document.title = '" + title + "';");
-               jsExecutor.execute(js.toString());
-               return title;
-       }
-
-       // Simply remove some illegal character
-       // private String clean(String stringToClean) {
-       // return stringToClean.replaceAll("'", "").replaceAll("\\n", "")
-       // .replaceAll("\\t", "");
-       // }
-
-       protected synchronized Node getNode() {
-               return node;
-       }
-
-       private synchronized void setNode(Node node) throws RepositoryException {
-               this.node = node;
-               this.nodePath = node == null ? null : node.getPath();
-       }
-
-       protected String getState() {
-               return state;
-       }
-
-       protected Throwable getException() {
-               return exception;
-       }
-
-       protected void resetException() {
-               exception = null;
-       }
-
-       protected Session getSession() {
-               return session;
-       }
-
-       private class CmsNavigationListener implements BrowserNavigationListener {
-               private static final long serialVersionUID = -3591018803430389270L;
-
-               @Override
-               public void navigated(BrowserNavigationEvent event) {
-                       setState(event.getState());
-                       doRefresh();
-               }
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java
deleted file mode 100644 (file)
index ca93e62..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.osgi.framework.Bundle;
-
-/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
-       private final Bundle bundle;
-
-       public BundleResourceLoader(Bundle bundle) {
-               this.bundle = bundle;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               URL res = bundle.getEntry(resourceName);
-               if (res == null) {
-                       res = bundle.getResource(resourceName);
-                       if (res == null)
-                               throw new IllegalArgumentException(
-                                               "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
-               }
-               return res.openStream();
-       }
-
-       public Bundle getBundle() {
-               return bundle;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java
deleted file mode 100644 (file)
index 0cf9a72..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.argeo.cms.ui.CmsTheme;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
-public class CmsThemeResourceLoader implements ResourceLoader {
-       private final CmsTheme theme;
-
-       public CmsThemeResourceLoader(CmsTheme theme) {
-               super();
-               this.theme = theme;
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               return theme.getResourceAsStream(resourceName);
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java
deleted file mode 100644 (file)
index c1bd3ad..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.cms.web;
-
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsApp;
-import org.argeo.cms.ui.CmsAppListener;
-import org.argeo.cms.ui.CmsTheme;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.util.LangUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.swt.widgets.Display;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.event.EventAdmin;
-
-/** An RWT web app integrating with a {@link CmsApp}. */
-public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
-       private final static Log log = LogFactory.getLog(CmsWebApp.class);
-
-       private BundleContext bundleContext;
-       private CmsApp cmsApp;
-       private String cmsAppId;
-       private EventAdmin eventAdmin;
-
-       private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
-
-       private final static String CONTEXT_NAME = "contextName";
-       private String contextName;
-
-       private final static String FAVICON_PNG = "favicon.png";
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               this.bundleContext = bundleContext;
-               contextName = properties.get(CONTEXT_NAME);
-               if (cmsApp != null) {
-                       if (cmsApp.allThemesAvailable())
-                               publishWebApp();
-               }
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-               if (cmsApp != null) {
-                       cmsApp.removeCmsAppListener(this);
-                       cmsApp = null;
-               }
-       }
-
-       @Override
-       public void configure(Application application) {
-               // TODO make it configurable?
-               // SWT compatibility is required for:
-               // - Browser.execute()
-               // - blocking dialogs
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-               for (String uiName : cmsApp.getUiNames()) {
-                       CmsTheme theme = cmsApp.getTheme(uiName);
-                       if (theme != null)
-                               WebThemeUtils.apply(application, theme);
-               }
-
-               Map<String, String> properties = new HashMap<>();
-               addEntryPoints(application, properties);
-               application.setExceptionHandler(this);
-       }
-
-       @Override
-       public void handleException(Throwable throwable) {
-               Display display = Display.getCurrent();
-               if (display != null && !display.isDisposed()) {
-                       CmsView cmsView = CmsView.getCmsView(display.getActiveShell());
-                       cmsView.exception(throwable);
-               } else {
-                       log.error("Unexpected exception outside an UI thread", throwable);
-               }
-
-       }
-
-       protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
-               for (String uiName : cmsApp.getUiNames()) {
-                       Map<String, String> properties = new HashMap<>(commonProperties);
-                       CmsTheme theme = cmsApp.getTheme(uiName);
-                       if (theme != null) {
-                               properties.put(WebClient.THEME_ID, theme.getThemeId());
-                               properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
-                               properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
-                               Set<String> imagePaths = theme.getImagesPaths();
-                               if (imagePaths.contains(FAVICON_PNG)) {
-                                       properties.put(WebClient.FAVICON, FAVICON_PNG);
-                               }
-                       } else {
-                               properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
-                       }
-                       String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
-                       application.addEntryPoint(entryPointName, () -> {
-                               CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
-                               entryPoint.setEventAdmin(eventAdmin);
-                               return entryPoint;
-                       }, properties);
-                       if (log.isDebugEnabled())
-                               log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
-               }
-//             if (log.isDebugEnabled())
-//                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
-       }
-
-       CmsApp getCmsApp() {
-               return cmsApp;
-       }
-
-       BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
-               this.cmsApp = cmsApp;
-               this.cmsAppId = properties.get(Constants.SERVICE_PID);
-               this.cmsApp.addCmsAppListener(this);
-       }
-
-       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
-               String cmsAppId = properties.get(Constants.SERVICE_PID);
-               if (!cmsAppId.equals(this.cmsAppId))
-                       return;
-               if (this.cmsApp != null) {
-                       this.cmsApp.removeCmsAppListener(this);
-               }
-               if (rwtAppReg != null)
-                       rwtAppReg.unregister();
-               this.cmsApp = null;
-       }
-
-       @Override
-       public void themingUpdated() {
-               if (cmsApp != null && cmsApp.allThemesAvailable())
-                       publishWebApp();
-       }
-
-       protected void publishWebApp() {
-               Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
-               if (rwtAppReg != null)
-                       rwtAppReg.unregister();
-               if (bundleContext != null) {
-                       rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
-                       if (log.isDebugEnabled())
-                               log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
-               }
-       }
-
-       public void setEventAdmin(EventAdmin eventAdmin) {
-               this.eventAdmin = eventAdmin;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
deleted file mode 100644 (file)
index b1691cb..0000000
+++ /dev/null
@@ -1,351 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
-import org.argeo.cms.ui.CmsApp;
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.UxContext;
-import org.argeo.cms.ui.dialogs.CmsFeedback;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.cms.ui.util.SimpleUxContext;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.client.service.BrowserNavigation;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
-import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
-import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.SWTError;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-
-/** The {@link CmsView} for a {@link CmsWebApp}. */
-@SuppressWarnings("restriction")
-public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener {
-       private static final long serialVersionUID = 7733510691684570402L;
-       private final static Log log = LogFactory.getLog(CmsWebEntryPoint.class);
-
-       private EventAdmin eventAdmin;
-
-       private final CmsWebApp cmsWebApp;
-       private final String uiName;
-
-       private LoginContext loginContext;
-       private String state;
-       private Throwable exception;
-       private UxContext uxContext;
-       private CmsImageManager imageManager;
-
-       private Composite ui;
-
-       private String uid;
-
-       // Client services
-       // private final JavaScriptExecutor jsExecutor;
-       private final BrowserNavigation browserNavigation;
-
-       /** Experimental OS-like multi windows. */
-       private boolean multipleShells = false;
-
-       public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
-               assert cmsWebApp != null;
-               assert uiName != null;
-               this.cmsWebApp = cmsWebApp;
-               this.uiName = uiName;
-               uid = UUID.randomUUID().toString();
-
-               // Initial login
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER,
-                                       new HttpRequestCallbackHandler(UiContext.getHttpRequest(), UiContext.getHttpResponse()));
-                       lc.login();
-               } catch (LoginException e) {
-                       try {
-                               lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS);
-                               lc.login();
-                       } catch (LoginException e1) {
-                               throw new IllegalStateException("Cannot log in as anonymous", e1);
-                       }
-               }
-               authChange(lc);
-
-               // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
-               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
-               if (browserNavigation != null)
-                       browserNavigation.addBrowserNavigationListener(this);
-       }
-
-       protected void createContents(Composite parent) {
-               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-                       @Override
-                       public Void run() {
-                               try {
-                                       uxContext = new SimpleUxContext();
-                                       imageManager = new DefaultImageManager();
-                                       CmsSession cmsSession = getCmsSession();
-                                       if (cmsSession != null) {
-                                               UiContext.setLocale(cmsSession.getLocale());
-                                               LocaleUtils.setThreadLocale(cmsSession.getLocale());
-                                       } else {
-                                               Locale rwtLocale = RWT.getUISession().getLocale();
-                                               LocaleUtils.setThreadLocale(rwtLocale);
-                                       }
-                                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
-                                       ui = cmsWebApp.getCmsApp().initUi(parent);
-                                       ui.setLayoutData(CmsUiUtils.fillAll());
-                                       // we need ui to be set before refresh so that CmsView can store UI context data
-                                       // in it.
-                                       cmsWebApp.getCmsApp().refreshUi(ui, null);
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot create entrypoint contents", e);
-                               }
-                               return null;
-                       }
-               });
-       }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       public <T> T doAs(PrivilegedAction<T> action) {
-               return Subject.doAs(getSubject(), action);
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public synchronized void logout() {
-               if (loginContext == null)
-                       throw new IllegalArgumentException("Login context should not be null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-                       LoginContext anonymousLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS);
-                       anonymousLc.login();
-                       authChange(anonymousLc);
-               } catch (LoginException e) {
-                       log.error("Cannot logout", e);
-               }
-       }
-
-       @Override
-       public synchronized void authChange(LoginContext lc) {
-               if (lc == null)
-                       throw new IllegalArgumentException("Login context cannot be null");
-               // logout previous login context
-               if (this.loginContext != null)
-                       try {
-                               this.loginContext.logout();
-                       } catch (LoginException e1) {
-                               log.warn("Could not log out: " + e1);
-                       }
-               this.loginContext = lc;
-               doRefresh();
-       }
-
-       @Override
-       public void exception(final Throwable e) {
-               if (e instanceof SWTError) {
-                       SWTError swtError = (SWTError) e;
-                       if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
-                               return;
-               }
-               ui.getDisplay().syncExec(() -> {
-//                     CmsFeedback.show("Unexpected exception in CMS", e);
-                       exception = e;
-//             log.error("Unexpected exception in CMS", e);
-                       doRefresh();
-               });
-       }
-
-       protected synchronized void doRefresh() {
-               if (ui != null)
-                       Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
-                               @Override
-                               public Void run() {
-                                       if (exception != null) {
-                                               // TODO internationalise
-                                               CmsFeedback.show("Unexpected exception", exception);
-                                               exception = null;
-                                               // TODO report
-                                       }
-                                       cmsWebApp.getCmsApp().refreshUi(ui, state);
-                                       return null;
-                               }
-                       });
-       }
-
-       /** Sets the state of the entry point and retrieve the related JCR node. */
-       protected String setState(String newState) {
-               cmsWebApp.getCmsApp().setState(ui, newState);
-               state = newState;
-               return null;
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               exception = null;
-               String title = setState(state);
-               if (title != null)
-                       doRefresh();
-               if (browserNavigation != null)
-                       browserNavigation.pushState(state, title);
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       @Override
-       public void navigated(BrowserNavigationEvent event) {
-               setState(event.getState());
-               // doRefresh();
-       }
-
-       @Override
-       public void sendEvent(String topic, Map<String, Object> properties) {
-               if (properties == null)
-                       properties = new HashMap<>();
-               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
-                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
-                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
-               properties.put(CMS_VIEW_UID_PROPERTY, uid);
-               eventAdmin.sendEvent(new Event(topic, properties));
-       }
-
-       @Override
-       public void stateChanged(String state, String title) {
-               browserNavigation.pushState(state, title);
-       }
-
-       @Override
-       public CmsSession getCmsSession() {
-               CmsSession cmsSession = CmsSession.getCmsSession(cmsWebApp.getBundleContext(), getSubject());
-               return cmsSession;
-       }
-
-       @Override
-       public Object getData(String key) {
-               if (ui != null) {
-                       return ui.getData(key);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       @Override
-       public void setData(String key, Object value) {
-               if (ui != null) {
-                       ui.setData(key, value);
-               } else {
-                       throw new IllegalStateException("UI is not initialized");
-               }
-       }
-
-       /*
-        * EntryPoint IMPLEMENTATION
-        */
-
-       @Override
-       public int createUI() {
-               Display display = new Display();
-               Shell shell = createShell(display);
-               shell.setLayout(CmsUiUtils.noSpaceGridLayout());
-               CmsView.registerCmsView(shell, this);
-               createContents(shell);
-               shell.layout();
-//             if (shell.getMaximized()) {
-//                     shell.layout();
-//             } else {
-////                   shell.pack();
-//             }
-               shell.open();
-               if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
-                       eventLoop: while (!shell.isDisposed()) {
-                               try {
-                                       if (!display.readAndDispatch()) {
-                                               display.sleep();
-                                       }
-                               } catch (Throwable e) {
-                                       if (e instanceof SWTError) {
-                                               SWTError swtError = (SWTError) e;
-                                               if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
-                                                       log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
-                                                       continue eventLoop;
-                                               } else {
-                                                       log.error("Unexpected SWT error in event loop, shutting down...", e);
-                                                       break eventLoop;
-                                               }
-                                       } else if (e instanceof ThreadDeath) {
-                                               throw (ThreadDeath) e;
-                                       } else if (e instanceof Error) {
-                                               log.error("Unexpected error in event loop, shutting down...", e);
-                                               break eventLoop;
-                                       } else {
-                                               log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
-                                               continue eventLoop;
-                                       }
-                               }
-                       }
-                       if (!display.isDisposed())
-                               display.dispose();
-               }
-               return 0;
-       }
-
-       protected Shell createShell(Display display) {
-               Shell shell;
-               if (!multipleShells) {
-                       shell = new Shell(display, SWT.NO_TRIM);
-                       shell.setMaximized(true);
-               } else {
-                       shell = new Shell(display, SWT.SHELL_TRIM);
-                       shell.setSize(800, 600);
-               }
-               return shell;
-       }
-
-       public void setEventAdmin(EventAdmin eventAdmin) {
-               this.eventAdmin = eventAdmin;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java
deleted file mode 100644 (file)
index 188391c..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.argeo.cms.web;
-
-import static org.argeo.cms.ui.util.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.ui.util.BundleCmsTheme;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.osgi.framework.BundleContext;
-
-/** Lightweight web app using only RWT and not the whole Eclipse platform. */
-public class MinimalWebApp implements ApplicationConfiguration {
-
-       private BundleCmsTheme theme;
-
-       public void init(BundleContext bundleContext, Map<String, Object> properties) {
-               if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
-                       String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
-                       theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
-               }
-       }
-
-       public void destroy() {
-
-       }
-
-       /** To be overridden. Does nothing by default. */
-       protected void addEntryPoints(Application application, Map<String, String> properties) {
-
-       }
-
-       @Override
-       public void configure(Application application) {
-               if (theme != null)
-                       WebThemeUtils.apply(application, theme);
-
-               Map<String, String> properties = new HashMap<>();
-               if (theme != null) {
-                       properties.put(WebClient.THEME_ID, theme.getThemeId());
-                       properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
-               } else {
-                       properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
-               }
-               addEntryPoints(application, properties);
-
-       }
-
-       public void setTheme(BundleCmsTheme theme) {
-               this.theme = theme;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java
deleted file mode 100644 (file)
index fd89310..0000000
+++ /dev/null
@@ -1,415 +0,0 @@
-package org.argeo.cms.web;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.Privilege;
-import javax.jcr.version.VersionManager;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsConstants;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.LifeCycleUiProvider;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.StyleSheetResourceLoader;
-import org.argeo.jcr.JcrUtils;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.rap.rwt.application.EntryPointFactory;
-import org.eclipse.rap.rwt.application.ExceptionHandler;
-import org.eclipse.rap.rwt.client.WebClient;
-import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-
-/** A basic generic app based on {@link SimpleErgonomics}. */
-@Deprecated
-public class SimpleApp implements CmsConstants, ApplicationConfiguration {
-       private final static Log log = LogFactory.getLog(SimpleApp.class);
-
-       private String contextName = null;
-
-       private Map<String, Map<String, String>> branding = new HashMap<String, Map<String, String>>();
-       private Map<String, List<String>> styleSheets = new HashMap<String, List<String>>();
-
-       private List<String> resources = new ArrayList<String>();
-
-       private BundleContext bundleContext;
-
-       private Repository repository;
-       private String workspace = null;
-       private String jcrBasePath = "/";
-       private List<String> roPrincipals = Arrays.asList(NodeConstants.ROLE_ANONYMOUS, NodeConstants.ROLE_USER);
-       private List<String> rwPrincipals = Arrays.asList(NodeConstants.ROLE_USER);
-
-       private CmsUiProvider header;
-       private Map<String, CmsUiProvider> pages = new LinkedHashMap<String, CmsUiProvider>();
-
-       private Integer headerHeight = 40;
-
-       private ServiceRegistration<ApplicationConfiguration> appReg;
-
-       public void configure(Application application) {
-               try {
-                       BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
-
-                       application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-                       // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
-
-                       application.setExceptionHandler(new CmsExceptionHandler());
-
-                       // loading animated gif
-                       application.addResource(LOADING_IMAGE, createResourceLoader(LOADING_IMAGE));
-                       // empty image
-                       application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE));
-
-                       for (String resource : resources) {
-                               application.addResource(resource, bundleRL);
-                               if (log.isTraceEnabled())
-                                       log.trace("Resource " + resource);
-                       }
-
-                       Map<String, String> defaultBranding = null;
-                       if (branding.containsKey("*"))
-                               defaultBranding = branding.get("*");
-                       // String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
-
-                       // entry points
-                       for (String page : pages.keySet()) {
-                               Map<String, String> properties = defaultBranding != null ? new HashMap<String, String>(defaultBranding)
-                                               : new HashMap<String, String>();
-                               if (branding.containsKey(page)) {
-                                       properties.putAll(branding.get(page));
-                               }
-                               // favicon
-                               if (properties.containsKey(WebClient.FAVICON)) {
-                                       String themeId = defaultBranding.get(WebClient.THEME_ID);
-                                       Bundle themeBundle = findThemeBundle(bundleContext, themeId);
-                                       String faviconRelPath = properties.get(WebClient.FAVICON);
-                                       application.addResource(faviconRelPath,
-                                                       new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
-                                       if (log.isTraceEnabled())
-                                               log.trace("Favicon " + faviconRelPath);
-
-                               }
-
-                               // page title
-                               if (!properties.containsKey(WebClient.PAGE_TITLE)) {
-                                       if (page.length() > 0)
-                                               properties.put(WebClient.PAGE_TITLE, Character.toUpperCase(page.charAt(0)) + page.substring(1));
-                               }
-
-                               // default body HTML
-                               if (!properties.containsKey(WebClient.BODY_HTML))
-                                       properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY);
-
-                               //
-                               // ADD ENTRY POINT
-                               //
-                               application.addEntryPoint("/" + page,
-                                               new CmsEntryPointFactory(pages.get(page), repository, workspace, properties), properties);
-                               log.info("Page /" + page);
-                       }
-
-                       // stylesheets and themes
-                       Set<Bundle> themeBundles = new HashSet<>();
-                       for (String themeId : styleSheets.keySet()) {
-                               Bundle themeBundle = findThemeBundle(bundleContext, themeId);
-                               StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
-                                               themeBundle != null ? themeBundle : bundleContext.getBundle());
-                               if (themeBundle != null)
-                                       themeBundles.add(themeBundle);
-                               List<String> cssLst = styleSheets.get(themeId);
-                               if (log.isDebugEnabled())
-                                       log.debug("Theme " + themeId);
-                               for (String css : cssLst) {
-                                       application.addStyleSheet(themeId, css, styleSheetRL);
-                                       if (log.isDebugEnabled())
-                                               log.debug(" CSS " + css);
-                               }
-
-                       }
-                       for (Bundle themeBundle : themeBundles) {
-                               BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.png");
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.gif");
-                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.jpg");
-                       }
-               } catch (RuntimeException e) {
-                       // Easier access to initialisation errors
-                       log.error("Unexpected exception when configuring RWT application.", e);
-                       throw e;
-               }
-       }
-
-       public void init() throws RepositoryException {
-               Session session = null;
-               try {
-                       session = NodeUtils.openDataAdminSession(repository, workspace);
-                       // session = JcrUtils.loginOrCreateWorkspace(repository, workspace);
-                       VersionManager vm = session.getWorkspace().getVersionManager();
-                       JcrUtils.mkdirs(session, jcrBasePath);
-                       session.save();
-                       if (!vm.isCheckedOut(jcrBasePath))
-                               vm.checkout(jcrBasePath);
-                       for (String principal : rwPrincipals)
-                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_WRITE);
-                       for (String principal : roPrincipals)
-                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_READ);
-
-                       for (String pageName : pages.keySet()) {
-                               try {
-                                       initPage(session, pages.get(pageName));
-                                       session.save();
-                               } catch (Exception e) {
-                                       throw new CmsException("Cannot initialize page " + pageName, e);
-                               }
-                       }
-
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-
-               // publish to OSGi
-               register();
-       }
-
-       protected void initPage(Session adminSession, CmsUiProvider page) throws RepositoryException {
-               if (page instanceof LifeCycleUiProvider)
-                       ((LifeCycleUiProvider) page).init(adminSession);
-       }
-
-       public void destroy() {
-               for (String pageName : pages.keySet()) {
-                       try {
-                               CmsUiProvider page = pages.get(pageName);
-                               if (page instanceof LifeCycleUiProvider)
-                                       ((LifeCycleUiProvider) page).destroy();
-                       } catch (Exception e) {
-                               log.error("Cannot destroy page " + pageName, e);
-                       }
-               }
-       }
-
-       protected void register() {
-               Hashtable<String, String> props = new Hashtable<String, String>();
-               if (contextName != null)
-                       props.put("contextName", contextName);
-               appReg = bundleContext.registerService(ApplicationConfiguration.class, this, props);
-               if (log.isDebugEnabled())
-                       log.debug("Registered " + (contextName == null ? "/" : contextName));
-       }
-
-       protected void unregister() {
-               appReg.unregister();
-               if (log.isDebugEnabled())
-                       log.debug("Unregistered " + (contextName == null ? "/" : contextName));
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public void setPages(Map<String, CmsUiProvider> pages) {
-               this.pages = pages;
-       }
-
-       public void setJcrBasePath(String basePath) {
-               this.jcrBasePath = basePath;
-       }
-
-       public void setRoPrincipals(List<String> roPrincipals) {
-               this.roPrincipals = roPrincipals;
-       }
-
-       public void setRwPrincipals(List<String> rwPrincipals) {
-               this.rwPrincipals = rwPrincipals;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public void setBranding(Map<String, Map<String, String>> branding) {
-               this.branding = branding;
-       }
-
-       public void setStyleSheets(Map<String, List<String>> styleSheets) {
-               this.styleSheets = styleSheets;
-       }
-
-       public void setBundleContext(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-
-       public void setResources(List<String> resources) {
-               this.resources = resources;
-       }
-
-       public void setContextName(String contextName) {
-               this.contextName = contextName;
-       }
-
-       private static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL,
-                       String pattern) {
-               Enumeration<URL> themeResources = themeBundle.findEntries("/", pattern, true);
-               if (themeResources == null)
-                       return;
-               while (themeResources.hasMoreElements()) {
-                       String resource = themeResources.nextElement().getPath();
-                       // remove first '/' so that RWT registers it
-                       resource = resource.substring(1);
-                       if (!resource.endsWith("/")) {
-                               application.addResource(resource, themeBRL);
-                               if (log.isTraceEnabled())
-                                       log.trace("Registered " + resource + " from theme " + themeBundle);
-                       }
-
-               }
-
-       }
-
-       private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
-               if (themeId == null)
-                       return null;
-               // TODO optimize
-               // TODO deal with multiple versions
-               Bundle themeBundle = null;
-               if (themeId != null) {
-                       for (Bundle bundle : bundleContext.getBundles())
-                               if (themeId.equals(bundle.getSymbolicName())) {
-                                       themeBundle = bundle;
-                                       break;
-                               }
-               }
-               return themeBundle;
-       }
-
-       class CmsExceptionHandler implements ExceptionHandler {
-
-               @Override
-               public void handleException(Throwable throwable) {
-                       // TODO be smarter
-                       CmsUiUtils.getCmsView().exception(throwable);
-               }
-
-       }
-
-       private class CmsEntryPointFactory implements EntryPointFactory {
-               private final CmsUiProvider page;
-               private final Repository repository;
-               private final String workspace;
-               private final Map<String, String> properties;
-
-               public CmsEntryPointFactory(CmsUiProvider page, Repository repository, String workspace,
-                               Map<String, String> properties) {
-                       this.page = page;
-                       this.repository = repository;
-                       this.workspace = workspace;
-                       this.properties = properties;
-               }
-
-               @Override
-               public EntryPoint create() {
-                       SimpleErgonomics entryPoint = new SimpleErgonomics(repository, workspace, jcrBasePath, page, properties) {
-                               private static final long serialVersionUID = -637940404865527290L;
-
-                               @Override
-                               protected void createAdminArea(Composite parent) {
-                                       Composite adminArea = new Composite(parent, SWT.NONE);
-                                       adminArea.setLayout(new FillLayout());
-                                       Button refresh = new Button(adminArea, SWT.PUSH);
-                                       refresh.setText("Reload App");
-                                       refresh.addSelectionListener(new SelectionAdapter() {
-                                               private static final long serialVersionUID = -7671999525536351366L;
-
-                                               @Override
-                                               public void widgetSelected(SelectionEvent e) {
-                                                       long timeBeforeReload = 1000;
-                                                       RWT.getClient().getService(JavaScriptExecutor.class).execute(
-                                                                       "setTimeout(function() { " + "location.reload();" + "}," + timeBeforeReload + ");");
-                                                       reloadApp();
-                                               }
-                                       });
-                               }
-                       };
-                       // entryPoint.setState("");
-                       entryPoint.setHeader(header);
-                       entryPoint.setHeaderHeight(headerHeight);
-                       // CmsSession.current.set(entryPoint);
-                       return entryPoint;
-               }
-
-               private void reloadApp() {
-                       new Thread("Refresh app") {
-                               @Override
-                               public void run() {
-                                       unregister();
-                                       register();
-                               }
-                       }.start();
-               }
-       }
-
-       private static ResourceLoader createResourceLoader(final String resourceName) {
-               return new ResourceLoader() {
-                       public InputStream getResourceAsStream(String resourceName) throws IOException {
-                               return getClass().getClassLoader().getResourceAsStream(resourceName);
-                       }
-               };
-       }
-
-       // private static ResourceLoader createUrlResourceLoader(final URL url) {
-       // return new ResourceLoader() {
-       // public InputStream getResourceAsStream(String resourceName)
-       // throws IOException {
-       // return url.openStream();
-       // }
-       // };
-       // }
-
-       /*
-        * TEXTS
-        */
-       private static String DEFAULT_LOADING_BODY = "<div"
-                       + " style=\"position: absolute; left: 50%; top: 50%; margin: -32px -32px; width: 64px; height:64px\">"
-                       + "<img src=\"./rwt-resources/" + LOADING_IMAGE
-                       + "\" width=\"32\" height=\"32\" style=\"margin: 16px 16px\"/>" + "</div>";
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java
deleted file mode 100644 (file)
index 9760f9d..0000000
+++ /dev/null
@@ -1,240 +0,0 @@
-package org.argeo.cms.web;
-
-import java.util.Map;
-import java.util.UUID;
-
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsStyles;
-import org.argeo.cms.ui.CmsUiProvider;
-import org.argeo.cms.ui.UxContext;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.util.DefaultImageManager;
-import org.argeo.cms.ui.util.SimpleUxContext;
-import org.argeo.cms.ui.util.SystemNotifications;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/** Simple header/body ergonomics. */
-@Deprecated
-public class SimpleErgonomics extends AbstractCmsEntryPoint {
-       private static final long serialVersionUID = 8743413921359548523L;
-
-       private final static Log log = LogFactory.getLog(SimpleErgonomics.class);
-
-       private boolean uiInitialized = false;
-       private Composite headerArea;
-       private Composite leftArea;
-       private Composite rightArea;
-       private Composite footerArea;
-       private Composite bodyArea;
-       private final CmsUiProvider uiProvider;
-
-       private CmsUiProvider header;
-       private Integer headerHeight = 0;
-       private Integer footerHeight = 0;
-       private CmsUiProvider lead;
-       private CmsUiProvider end;
-       private CmsUiProvider footer;
-
-       private CmsImageManager imageManager = new DefaultImageManager();
-       private UxContext uxContext = null;
-       private String uid;
-
-       public SimpleErgonomics(Repository repository, String workspace, String defaultPath, CmsUiProvider uiProvider,
-                       Map<String, String> factoryProperties) {
-               super(repository, workspace, defaultPath, factoryProperties);
-               this.uiProvider = uiProvider;
-       }
-
-       @Override
-       protected void initUi(Composite parent) {
-               uid = UUID.randomUUID().toString();
-               parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               parent.setLayout(CmsUiUtils.noSpaceGridLayout(new GridLayout(3, false)));
-
-               uxContext = new SimpleUxContext();
-               if (!getUxContext().isMasterData())
-                       createAdminArea(parent);
-               headerArea = new Composite(parent, SWT.NONE);
-               headerArea.setLayout(new FillLayout());
-               GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
-               headerData.heightHint = headerHeight;
-               headerArea.setLayoutData(headerData);
-
-               // TODO: bi-directional
-               leftArea = new Composite(parent, SWT.NONE);
-               leftArea.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
-               leftArea.setLayout(CmsUiUtils.noSpaceGridLayout());
-
-               bodyArea = new Composite(parent, SWT.NONE);
-               bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
-               bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
-               bodyArea.setLayout(CmsUiUtils.noSpaceGridLayout());
-
-               // TODO: bi-directional
-               rightArea = new Composite(parent, SWT.NONE);
-               rightArea.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
-               rightArea.setLayout(CmsUiUtils.noSpaceGridLayout());
-
-               footerArea = new Composite(parent, SWT.NONE);
-               // footerArea.setLayout(new FillLayout());
-               GridData footerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
-               footerData.heightHint = footerHeight;
-               footerArea.setLayoutData(footerData);
-
-               uiInitialized = true;
-               refresh();
-       }
-
-       @Override
-       protected void refresh() {
-               if (!uiInitialized)
-                       return;
-               if (getState() == null)
-                       setState("");
-               refreshSides();
-               refreshBody();
-               if (log.isTraceEnabled())
-                       log.trace("UI refreshed " + getNode());
-       }
-
-       protected void createAdminArea(Composite parent) {
-       }
-
-       @Deprecated
-       protected void refreshHeader() {
-               if (header == null)
-                       return;
-
-               for (Control child : headerArea.getChildren())
-                       child.dispose();
-               try {
-                       header.createUi(headerArea, getNode());
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh header", e);
-               }
-               headerArea.layout(true, true);
-       }
-
-       protected void refreshSides() {
-               refresh(headerArea, header, CmsStyles.CMS_HEADER);
-               refresh(leftArea, lead, CmsStyles.CMS_LEAD);
-               refresh(rightArea, end, CmsStyles.CMS_END);
-               refresh(footerArea, footer, CmsStyles.CMS_FOOTER);
-       }
-
-       private void refresh(Composite area, CmsUiProvider uiProvider, String style) {
-               if (uiProvider == null)
-                       return;
-
-               for (Control child : area.getChildren())
-                       child.dispose();
-               CmsUiUtils.style(area, style);
-               try {
-                       uiProvider.createUi(area, getNode());
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh header", e);
-               }
-               area.layout(true, true);
-       }
-
-       protected void refreshBody() {
-               // Exception
-               Throwable exception = getException();
-               if (exception != null) {
-                       SystemNotifications systemNotifications = new SystemNotifications(bodyArea);
-                       systemNotifications.notifyException(exception);
-                       resetException();
-                       return;
-                       // TODO report
-               }
-
-               // clear
-               for (Control child : bodyArea.getChildren())
-                       child.dispose();
-               bodyArea.setLayout(CmsUiUtils.noSpaceGridLayout());
-
-               try {
-                       Node node = getNode();
-//                     if (node == null)
-//                             log.error("Context cannot be null");
-//                     else
-                       uiProvider.createUi(bodyArea, node);
-               } catch (RepositoryException e) {
-                       throw new CmsException("Cannot refresh body", e);
-               }
-
-               bodyArea.layout(true, true);
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               return imageManager;
-       }
-
-       public void setHeader(CmsUiProvider header) {
-               this.header = header;
-       }
-
-       public void setHeaderHeight(Integer headerHeight) {
-               this.headerHeight = headerHeight;
-       }
-
-       public void setImageManager(CmsImageManager imageManager) {
-               this.imageManager = imageManager;
-       }
-
-       public CmsUiProvider getLead() {
-               return lead;
-       }
-
-       public void setLead(CmsUiProvider lead) {
-               this.lead = lead;
-       }
-
-       public CmsUiProvider getEnd() {
-               return end;
-       }
-
-       public void setEnd(CmsUiProvider end) {
-               this.end = end;
-       }
-
-       public CmsUiProvider getFooter() {
-               return footer;
-       }
-
-       public void setFooter(CmsUiProvider footer) {
-               this.footer = footer;
-       }
-
-       public CmsUiProvider getHeader() {
-               return header;
-       }
-
-       public void setFooterHeight(Integer footerHeight) {
-               this.footerHeight = footerHeight;
-       }
-
-}
diff --git a/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java b/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java
deleted file mode 100644 (file)
index a71ff97..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.cms.web;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsTheme;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.service.ResourceLoader;
-
-/** Web specific utilities around theming. */
-public class WebThemeUtils {
-       private final static Log log = LogFactory.getLog(WebThemeUtils.class);
-
-       public static void apply(Application application, CmsTheme theme) {
-               ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
-               resources: for (String path : theme.getImagesPaths()) {
-                       if (path.startsWith("target/"))
-                               continue resources; // skip maven output
-                       application.addResource(path, resourceLoader);
-                       if (log.isTraceEnabled())
-                               log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
-               }
-               for (String path : theme.getRapCssPaths()) {
-                       application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
-                       if (log.isDebugEnabled())
-                               log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
-               }
-       }
-
-}
diff --git a/org.argeo.cms.ui.theme/.classpath b/org.argeo.cms.ui.theme/.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.theme/.project b/org.argeo.cms.ui.theme/.project
deleted file mode 100644 (file)
index 5f90021..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.cms.ui.theme</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.theme/META-INF/.gitignore b/org.argeo.cms.ui.theme/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.theme/bnd.bnd b/org.argeo.cms.ui.theme/bnd.bnd
deleted file mode 100644 (file)
index 2b2a02f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Bundle-ActivationPolicy: lazy
diff --git a/org.argeo.cms.ui.theme/build.properties b/org.argeo.cms.ui.theme/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.cms.ui.theme/icons/actions/add.png b/org.argeo.cms.ui.theme/icons/actions/add.png
deleted file mode 100644 (file)
index 5c06bf0..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/add.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/actions/close-all.png b/org.argeo.cms.ui.theme/icons/actions/close-all.png
deleted file mode 100644 (file)
index 81bfc95..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/close-all.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/actions/delete.png b/org.argeo.cms.ui.theme/icons/actions/delete.png
deleted file mode 100644 (file)
index 9712723..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/delete.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/actions/edit.png b/org.argeo.cms.ui.theme/icons/actions/edit.png
deleted file mode 100644 (file)
index ad3db9f..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/edit.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/actions/save-all.png b/org.argeo.cms.ui.theme/icons/actions/save-all.png
deleted file mode 100644 (file)
index f48ed32..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/save-all.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/actions/save.png b/org.argeo.cms.ui.theme/icons/actions/save.png
deleted file mode 100644 (file)
index 1c58ada..0000000
Binary files a/org.argeo.cms.ui.theme/icons/actions/save.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/active.gif b/org.argeo.cms.ui.theme/icons/active.gif
deleted file mode 100644 (file)
index 7d24707..0000000
Binary files a/org.argeo.cms.ui.theme/icons/active.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/add.gif b/org.argeo.cms.ui.theme/icons/add.gif
deleted file mode 100644 (file)
index 252d7eb..0000000
Binary files a/org.argeo.cms.ui.theme/icons/add.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/add.png b/org.argeo.cms.ui.theme/icons/add.png
deleted file mode 100644 (file)
index c7edfec..0000000
Binary files a/org.argeo.cms.ui.theme/icons/add.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/addFolder.gif b/org.argeo.cms.ui.theme/icons/addFolder.gif
deleted file mode 100644 (file)
index d3f43d9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/addFolder.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/addPrivileges.gif b/org.argeo.cms.ui.theme/icons/addPrivileges.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/org.argeo.cms.ui.theme/icons/addPrivileges.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/addRepo.gif b/org.argeo.cms.ui.theme/icons/addRepo.gif
deleted file mode 100644 (file)
index 26d81c0..0000000
Binary files a/org.argeo.cms.ui.theme/icons/addRepo.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/addWorkspace.png b/org.argeo.cms.ui.theme/icons/addWorkspace.png
deleted file mode 100644 (file)
index bbee775..0000000
Binary files a/org.argeo.cms.ui.theme/icons/addWorkspace.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/adminLog.gif b/org.argeo.cms.ui.theme/icons/adminLog.gif
deleted file mode 100644 (file)
index 6ef3bca..0000000
Binary files a/org.argeo.cms.ui.theme/icons/adminLog.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/batch.gif b/org.argeo.cms.ui.theme/icons/batch.gif
deleted file mode 100644 (file)
index b8ca14a..0000000
Binary files a/org.argeo.cms.ui.theme/icons/batch.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/begin.gif b/org.argeo.cms.ui.theme/icons/begin.gif
deleted file mode 100755 (executable)
index feb8e94..0000000
Binary files a/org.argeo.cms.ui.theme/icons/begin.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/binary.png b/org.argeo.cms.ui.theme/icons/binary.png
deleted file mode 100644 (file)
index fdf4f82..0000000
Binary files a/org.argeo.cms.ui.theme/icons/binary.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/browser.gif b/org.argeo.cms.ui.theme/icons/browser.gif
deleted file mode 100644 (file)
index 6c7320c..0000000
Binary files a/org.argeo.cms.ui.theme/icons/browser.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/bundles.gif b/org.argeo.cms.ui.theme/icons/bundles.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/bundles.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/changePassword.gif b/org.argeo.cms.ui.theme/icons/changePassword.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/org.argeo.cms.ui.theme/icons/changePassword.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/clear.gif b/org.argeo.cms.ui.theme/icons/clear.gif
deleted file mode 100644 (file)
index 6bc10f9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/clear.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/close-all.png b/org.argeo.cms.ui.theme/icons/close-all.png
deleted file mode 100644 (file)
index 85d4d42..0000000
Binary files a/org.argeo.cms.ui.theme/icons/close-all.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/commit.gif b/org.argeo.cms.ui.theme/icons/commit.gif
deleted file mode 100755 (executable)
index 876f3eb..0000000
Binary files a/org.argeo.cms.ui.theme/icons/commit.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/delete.png b/org.argeo.cms.ui.theme/icons/delete.png
deleted file mode 100644 (file)
index 676a39d..0000000
Binary files a/org.argeo.cms.ui.theme/icons/delete.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/dumpNode.gif b/org.argeo.cms.ui.theme/icons/dumpNode.gif
deleted file mode 100644 (file)
index 14eb1be..0000000
Binary files a/org.argeo.cms.ui.theme/icons/dumpNode.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/file.gif b/org.argeo.cms.ui.theme/icons/file.gif
deleted file mode 100644 (file)
index ef30288..0000000
Binary files a/org.argeo.cms.ui.theme/icons/file.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/folder.gif b/org.argeo.cms.ui.theme/icons/folder.gif
deleted file mode 100644 (file)
index 42e027c..0000000
Binary files a/org.argeo.cms.ui.theme/icons/folder.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/getSize.gif b/org.argeo.cms.ui.theme/icons/getSize.gif
deleted file mode 100644 (file)
index b05bf3e..0000000
Binary files a/org.argeo.cms.ui.theme/icons/getSize.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/group.png b/org.argeo.cms.ui.theme/icons/group.png
deleted file mode 100644 (file)
index cc6683a..0000000
Binary files a/org.argeo.cms.ui.theme/icons/group.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/home.gif b/org.argeo.cms.ui.theme/icons/home.gif
deleted file mode 100644 (file)
index fd0c669..0000000
Binary files a/org.argeo.cms.ui.theme/icons/home.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/home.png b/org.argeo.cms.ui.theme/icons/home.png
deleted file mode 100644 (file)
index 5eb0967..0000000
Binary files a/org.argeo.cms.ui.theme/icons/home.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/import_fs.png b/org.argeo.cms.ui.theme/icons/import_fs.png
deleted file mode 100644 (file)
index d7c890c..0000000
Binary files a/org.argeo.cms.ui.theme/icons/import_fs.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/installed.gif b/org.argeo.cms.ui.theme/icons/installed.gif
deleted file mode 100644 (file)
index 2988716..0000000
Binary files a/org.argeo.cms.ui.theme/icons/installed.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/log.gif b/org.argeo.cms.ui.theme/icons/log.gif
deleted file mode 100644 (file)
index e3ecc55..0000000
Binary files a/org.argeo.cms.ui.theme/icons/log.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/logout.png b/org.argeo.cms.ui.theme/icons/logout.png
deleted file mode 100644 (file)
index f2952fa..0000000
Binary files a/org.argeo.cms.ui.theme/icons/logout.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/maintenance.gif b/org.argeo.cms.ui.theme/icons/maintenance.gif
deleted file mode 100644 (file)
index e5690ec..0000000
Binary files a/org.argeo.cms.ui.theme/icons/maintenance.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/node.gif b/org.argeo.cms.ui.theme/icons/node.gif
deleted file mode 100644 (file)
index 364c0e7..0000000
Binary files a/org.argeo.cms.ui.theme/icons/node.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/nodes.gif b/org.argeo.cms.ui.theme/icons/nodes.gif
deleted file mode 100644 (file)
index bba3dbc..0000000
Binary files a/org.argeo.cms.ui.theme/icons/nodes.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/osgi_explorer.gif b/org.argeo.cms.ui.theme/icons/osgi_explorer.gif
deleted file mode 100644 (file)
index e9a6bd9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/osgi_explorer.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/password.gif b/org.argeo.cms.ui.theme/icons/password.gif
deleted file mode 100644 (file)
index a6b251f..0000000
Binary files a/org.argeo.cms.ui.theme/icons/password.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/person-logged-in.png b/org.argeo.cms.ui.theme/icons/person-logged-in.png
deleted file mode 100644 (file)
index 87acc14..0000000
Binary files a/org.argeo.cms.ui.theme/icons/person-logged-in.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/person.png b/org.argeo.cms.ui.theme/icons/person.png
deleted file mode 100644 (file)
index 7d979a5..0000000
Binary files a/org.argeo.cms.ui.theme/icons/person.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/query.png b/org.argeo.cms.ui.theme/icons/query.png
deleted file mode 100644 (file)
index 54c089d..0000000
Binary files a/org.argeo.cms.ui.theme/icons/query.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/refresh.png b/org.argeo.cms.ui.theme/icons/refresh.png
deleted file mode 100644 (file)
index 71b3481..0000000
Binary files a/org.argeo.cms.ui.theme/icons/refresh.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/remote_connected.gif b/org.argeo.cms.ui.theme/icons/remote_connected.gif
deleted file mode 100644 (file)
index 1492b4e..0000000
Binary files a/org.argeo.cms.ui.theme/icons/remote_connected.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/remote_disconnected.gif b/org.argeo.cms.ui.theme/icons/remote_disconnected.gif
deleted file mode 100644 (file)
index 6c54da9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/remote_disconnected.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/remove.gif b/org.argeo.cms.ui.theme/icons/remove.gif
deleted file mode 100644 (file)
index 0ae6dec..0000000
Binary files a/org.argeo.cms.ui.theme/icons/remove.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/removePrivileges.gif b/org.argeo.cms.ui.theme/icons/removePrivileges.gif
deleted file mode 100644 (file)
index aa78fd2..0000000
Binary files a/org.argeo.cms.ui.theme/icons/removePrivileges.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/rename.gif b/org.argeo.cms.ui.theme/icons/rename.gif
deleted file mode 100644 (file)
index 8048405..0000000
Binary files a/org.argeo.cms.ui.theme/icons/rename.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/repositories.gif b/org.argeo.cms.ui.theme/icons/repositories.gif
deleted file mode 100644 (file)
index c13bea1..0000000
Binary files a/org.argeo.cms.ui.theme/icons/repositories.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/repository_connected.gif b/org.argeo.cms.ui.theme/icons/repository_connected.gif
deleted file mode 100644 (file)
index a15fa55..0000000
Binary files a/org.argeo.cms.ui.theme/icons/repository_connected.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/repository_disconnected.gif b/org.argeo.cms.ui.theme/icons/repository_disconnected.gif
deleted file mode 100644 (file)
index 4576dc5..0000000
Binary files a/org.argeo.cms.ui.theme/icons/repository_disconnected.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/resolved.gif b/org.argeo.cms.ui.theme/icons/resolved.gif
deleted file mode 100644 (file)
index f4a1ea1..0000000
Binary files a/org.argeo.cms.ui.theme/icons/resolved.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/role.gif b/org.argeo.cms.ui.theme/icons/role.gif
deleted file mode 100644 (file)
index 274a850..0000000
Binary files a/org.argeo.cms.ui.theme/icons/role.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/rollback.gif b/org.argeo.cms.ui.theme/icons/rollback.gif
deleted file mode 100755 (executable)
index c753995..0000000
Binary files a/org.argeo.cms.ui.theme/icons/rollback.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/save-all.png b/org.argeo.cms.ui.theme/icons/save-all.png
deleted file mode 100644 (file)
index b68a29b..0000000
Binary files a/org.argeo.cms.ui.theme/icons/save-all.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/save.gif b/org.argeo.cms.ui.theme/icons/save.gif
deleted file mode 100644 (file)
index 654ad7b..0000000
Binary files a/org.argeo.cms.ui.theme/icons/save.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/save.png b/org.argeo.cms.ui.theme/icons/save.png
deleted file mode 100644 (file)
index f27ef2d..0000000
Binary files a/org.argeo.cms.ui.theme/icons/save.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/save_security.png b/org.argeo.cms.ui.theme/icons/save_security.png
deleted file mode 100644 (file)
index ca41dc9..0000000
Binary files a/org.argeo.cms.ui.theme/icons/save_security.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/save_security_disabled.png b/org.argeo.cms.ui.theme/icons/save_security_disabled.png
deleted file mode 100644 (file)
index fb7d08d..0000000
Binary files a/org.argeo.cms.ui.theme/icons/save_security_disabled.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/security.gif b/org.argeo.cms.ui.theme/icons/security.gif
deleted file mode 100644 (file)
index 57fb95e..0000000
Binary files a/org.argeo.cms.ui.theme/icons/security.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/service_published.gif b/org.argeo.cms.ui.theme/icons/service_published.gif
deleted file mode 100644 (file)
index 17f771a..0000000
Binary files a/org.argeo.cms.ui.theme/icons/service_published.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/service_referenced.gif b/org.argeo.cms.ui.theme/icons/service_referenced.gif
deleted file mode 100644 (file)
index c24a95f..0000000
Binary files a/org.argeo.cms.ui.theme/icons/service_referenced.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/sort.gif b/org.argeo.cms.ui.theme/icons/sort.gif
deleted file mode 100644 (file)
index 23c5d0b..0000000
Binary files a/org.argeo.cms.ui.theme/icons/sort.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/starting.gif b/org.argeo.cms.ui.theme/icons/starting.gif
deleted file mode 100644 (file)
index 563743d..0000000
Binary files a/org.argeo.cms.ui.theme/icons/starting.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/sync.gif b/org.argeo.cms.ui.theme/icons/sync.gif
deleted file mode 100644 (file)
index b4fa052..0000000
Binary files a/org.argeo.cms.ui.theme/icons/sync.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/user.gif b/org.argeo.cms.ui.theme/icons/user.gif
deleted file mode 100644 (file)
index 90a0014..0000000
Binary files a/org.argeo.cms.ui.theme/icons/user.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/users.gif b/org.argeo.cms.ui.theme/icons/users.gif
deleted file mode 100644 (file)
index 2de7edd..0000000
Binary files a/org.argeo.cms.ui.theme/icons/users.gif and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/workgroup.png b/org.argeo.cms.ui.theme/icons/workgroup.png
deleted file mode 100644 (file)
index 7fef996..0000000
Binary files a/org.argeo.cms.ui.theme/icons/workgroup.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/workgroup.xcf b/org.argeo.cms.ui.theme/icons/workgroup.xcf
deleted file mode 100644 (file)
index f517c82..0000000
Binary files a/org.argeo.cms.ui.theme/icons/workgroup.xcf and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/workspace_connected.png b/org.argeo.cms.ui.theme/icons/workspace_connected.png
deleted file mode 100644 (file)
index 0430baa..0000000
Binary files a/org.argeo.cms.ui.theme/icons/workspace_connected.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/icons/workspace_disconnected.png b/org.argeo.cms.ui.theme/icons/workspace_disconnected.png
deleted file mode 100644 (file)
index fddcb8c..0000000
Binary files a/org.argeo.cms.ui.theme/icons/workspace_disconnected.png and /dev/null differ
diff --git a/org.argeo.cms.ui.theme/pom.xml b/org.argeo.cms.ui.theme/pom.xml
deleted file mode 100644 (file)
index b30460a..0000000
+++ /dev/null
@@ -1,27 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms.ui.theme</artifactId>
-       <name>CMS UI Theme</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <!-- 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.rap.jface</artifactId>
-                       <scope>provided</scope>
-               </dependency>
-               
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms.ui.theme/rap/argeo-studio.css b/org.argeo.cms.ui.theme/rap/argeo-studio.css
deleted file mode 100644 (file)
index 2d75555..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-Composite.qa {
-       background-color: gray;
-       color: white;
-}
-
-Button[PUSH].qa {
-       color: white;
-       background-color: gray;
-       padding: 0px;
-       spacing: 0px;
-       border: none;
-}
-
-Composite.support {
-       background-color: red;
-}
-
-Label.support {
-       background-color: red;
-       color: white;
-}
diff --git a/org.argeo.cms.ui.theme/src/org/argeo/cms/ui/theme/CmsImages.java b/org.argeo.cms.ui.theme/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.ui.theme/src/org/argeo/cms/ui/theme/package-info.java b/org.argeo.cms.ui.theme/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.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 186bb1e..0000000
+++ /dev/null
@@ -1,64 +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.1-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.1-SNAPSHOT</version>
-               </dependency>
-               <!-- Specific -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-                       <scope>provided</scope>
-               </dependency>
-
-               <!-- Theme -->
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.cms.ui.theme</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </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/AbstractCmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsApp.java
deleted file mode 100644 (file)
index 77cd983..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.eclipse.rap.rwt.RWT;
-
-/** Base class for {@link CmsApp}s. */
-public abstract class AbstractCmsApp implements CmsApp {
-       private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
-
-       private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
-
-       private Repository repository;
-
-       protected abstract String getThemeId(String uiName);
-
-       @Override
-       public CmsTheme getTheme(String uiName) {
-               String themeId = getThemeId(uiName);
-               if (themeId == null)
-                       return null;
-               if (!themes.containsKey(themeId))
-                       throw new IllegalArgumentException("Theme " + themeId + " not found.");
-               return themes.get(themeId);
-       }
-
-       @Override
-       public boolean allThemesAvailable() {
-               boolean themeMissing = false;
-               uiNames: for (String uiName : getUiNames()) {
-                       String themeId = getThemeId(uiName);
-                       if (RWT.DEFAULT_THEME_ID.equals(themeId))
-                               continue uiNames;
-                       if (!themes.containsKey(themeId)) {
-                               themeMissing = true;
-                               break uiNames;
-                       }
-               }
-               return !themeMissing;
-       }
-
-       public void addTheme(CmsTheme theme, Map<String, String> properties) {
-               themes.put(theme.getThemeId(), theme);
-               if (allThemesAvailable())
-                       for (CmsAppListener listener : cmsAppListeners)
-                               listener.themingUpdated();
-       }
-
-       public void removeTheme(CmsTheme theme, Map<String, String> properties) {
-               themes.remove(theme.getThemeId());
-       }
-
-       @Override
-       public void addCmsAppListener(CmsAppListener listener) {
-               cmsAppListeners.add(listener);
-               if (allThemesAvailable())
-                       listener.themingUpdated();
-       }
-
-       @Override
-       public void removeCmsAppListener(CmsAppListener listener) {
-               cmsAppListeners.remove(listener);
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsApp.java
deleted file mode 100644 (file)
index bd7b003..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.util.Set;
-
-import org.eclipse.swt.widgets.Composite;
-
-/** An extensible user interface base on the CMS backend. */
-public interface CmsApp {
-       /**
-        * If {@link Composite#setData(String, Object)} is set with this property, it
-        * indicates a different UI (typically with another theming. The {@link CmsApp}
-        * can use this information, but it doesn't have to be set, in which case a
-        * default UI must be provided. The provided value must belong to the values
-        * returned by {@link CmsApp#getUiNames()}.
-        */
-       final static String UI_NAME_PROPERTY = CmsApp.class.getName() + ".ui.name";
-
-       Set<String> getUiNames();
-
-       Composite initUi(Composite parent);
-
-       void refreshUi(Composite parent, String state);
-
-       void setState(Composite parent, String state);
-
-       CmsTheme getTheme(String uiName);
-
-       boolean allThemesAvailable();
-
-       void addCmsAppListener(CmsAppListener listener);
-
-       void removeCmsAppListener(CmsAppListener listener);
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsAppListener.java
deleted file mode 100644 (file)
index 1fe3678..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.cms.ui;
-
-/** Notifies important events in a {@link CmsApp} life cycle. */
-public interface CmsAppListener {
-       /** Theming has been updated and should be reloaded. */
-       void themingUpdated();
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsConstants.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsConstants.java
deleted file mode 100644 (file)
index ef6fd5f..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.argeo.cms.ui;
-
-import org.eclipse.swt.graphics.Point;
-
-/** Commons constants */
-public interface CmsConstants {
-       // 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 Point NO_IMAGE_SIZE = new Point(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/CmsEditable.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditable.java
deleted file mode 100644 (file)
index 72cc597..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.argeo.cms.ui;
-
-/** Abstraction of a simple edition life cycle. */
-public interface CmsEditable {
-
-       /** Whether the calling thread can edit, the value is immutable */
-       public Boolean canEdit();
-
-       public Boolean isEditing();
-
-       public void startEditing();
-
-       public void stopEditing();
-
-       public static CmsEditable NON_EDITABLE = new CmsEditable() {
-
-               @Override
-               public void stopEditing() {
-               }
-
-               @Override
-               public void startEditing() {
-               }
-
-               @Override
-               public Boolean isEditing() {
-                       return false;
-               }
-
-               @Override
-               public Boolean canEdit() {
-                       return false;
-               }
-       };
-
-       public static CmsEditable ALWAYS_EDITING = new CmsEditable() {
-
-               @Override
-               public void stopEditing() {
-               }
-
-               @Override
-               public void startEditing() {
-               }
-
-               @Override
-               public Boolean isEditing() {
-                       return true;
-               }
-
-               @Override
-               public Boolean canEdit() {
-                       return true;
-               }
-       };
-
-}
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/CmsImageManager.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsImageManager.java
deleted file mode 100644 (file)
index e0cfa31..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.io.InputStream;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.widgets.Control;
-
-/** Read and write access to images. */
-public interface CmsImageManager {
-       /** Load image in control */
-       public Boolean load(Node node, Control control, Point size) throws RepositoryException;
-
-       /** @return (0,0) if not available */
-       public Point getImageSize(Node node) throws RepositoryException;
-
-       /**
-        * The related &lt;img&gt; tag, with src, width and height set.
-        * 
-        * @return null if not available
-        */
-       public String getImageTag(Node node) throws RepositoryException;
-
-       /**
-        * The related &lt;img&gt; tag, with url, width and height set. Caller must
-        * close the tag (or add additional attributes).
-        * 
-        * @return null if not available
-        */
-       public StringBuilder getImageTagBuilder(Node node, Point size) throws RepositoryException;
-
-       /**
-        * Returns the remotely accessible URL of the image (registering it if
-        * needed) @return null if not available
-        */
-       public String getImageUrl(Node node) throws RepositoryException;
-
-       public Binary getImageBinary(Node node) throws RepositoryException;
-
-       public Image getSwtImage(Node node) throws RepositoryException;
-
-       /** @return URL */
-       public String uploadImage(Node context, Node uploadFolder, String fileName, InputStream in, String contentType)
-                       throws RepositoryException;
-
-       @Deprecated
-       default String uploadImage(Node uploadFolder, String fileName, InputStream in) throws RepositoryException {
-               System.err.println("Context must be provided to " + CmsImageManager.class.getName());
-               return uploadImage(null, uploadFolder, fileName, in, null);
-       }
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsStyles.java
deleted file mode 100644 (file)
index 678a497..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.ui;
-
-/** 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.ui/src/org/argeo/cms/ui/CmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsTheme.java
deleted file mode 100644 (file)
index c93991b..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Set;
-
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Shell;
-
-/** A CMS theme which can be applied to web apps as well as desktop apps. */
-public interface CmsTheme {
-       /** Unique ID of this theme. */
-       String getThemeId();
-
-       /**
-        * Load a resource as an input stream, base don its relative path, or
-        * <code>null</code> if not found
-        */
-       InputStream getResourceAsStream(String resourceName) throws IOException;
-
-       /** Relative paths to standard web CSS. */
-       Set<String> getWebCssPaths();
-
-       /** Relative paths to RAP specific CSS. */
-       Set<String> getRapCssPaths();
-
-       /** Relative paths to SWT specific CSS. */
-       Set<String> getSwtCssPaths();
-
-       /** Relative paths to images such as icons. */
-       Set<String> getImagesPaths();
-
-       /** Relative paths to fonts. */
-       Set<String> getFontsPaths();
-
-       /** Tags to be added to the header section of the HTML page. */
-       String getHtmlHeaders();
-
-       /** The HTML body to use. */
-       String getBodyHtml();
-
-       /** The image registered at this path, or <code>null</code> if not found. */
-       Image getImage(String path);
-
-       /** The default icon size (typically the smallest). */
-       Integer getDefaultIconSize();
-
-       /** Loads one of the relative path provided by the other methods. */
-       InputStream loadPath(String path) throws IOException;
-
-       /**
-        * 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);
-
-       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;
-       }
-
-       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);
-       }
-
-}
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 00939e1..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.cms.ui;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-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/CmsView.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsView.java
deleted file mode 100644 (file)
index f8ab3a6..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.security.auth.login.LoginContext;
-
-import org.argeo.cms.auth.CmsSession;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Shell;
-
-/** Provides interaction with the CMS system. */
-public interface CmsView {
-       final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
-       // String KEY = "org.argeo.cms.ui.view";
-
-       String getUid();
-
-       UxContext getUxContext();
-
-       // NAVIGATION
-       void navigateTo(String state);
-
-       // SECURITY
-       void authChange(LoginContext loginContext);
-
-       void logout();
-
-       // void registerCallbackHandler(CallbackHandler callbackHandler);
-
-       // SERVICES
-       void exception(Throwable e);
-
-       CmsImageManager getImageManager();
-
-       boolean isAnonymous();
-
-       /**
-        * Send an event to this topic. Does nothing by default., but if implemented it
-        * MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
-        */
-       default void sendEvent(String topic, Map<String, Object> properties) {
-
-       }
-
-       /**
-        * Convenience methods for when {@link #sendEvent(String, Map)} only requires
-        * one single parameter.
-        */
-       default void sendEvent(String topic, String param, Object value) {
-               Map<String, Object> properties = new HashMap<>();
-               properties.put(param, value);
-               sendEvent(topic, properties);
-       }
-
-       default void applyStyles(Object widget) {
-
-       }
-
-       default <T> T doAs(PrivilegedAction<T> action) {
-               throw new UnsupportedOperationException();
-       }
-
-       default Void runAs(Runnable runnable) {
-               return doAs(new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               if (runnable != null)
-                                       runnable.run();
-                               return null;
-                       }
-               });
-       }
-
-       default void stateChanged(String state, String title) {
-       }
-
-       default CmsSession getCmsSession() {
-               throw new UnsupportedOperationException();
-       }
-
-       default Object getData(String key) {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings("unchecked")
-       default <T> T getUiContext(Class<T> clss) {
-               return (T) getData(clss.getName());
-       }
-
-       default <T> void setUiContext(Class<T> clss, T instance) {
-               setData(clss.getName(), instance);
-       }
-
-       default void setData(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-       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());
-       }
-
-       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);
-       }
-
-}
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/MvcProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/MvcProvider.java
deleted file mode 100644 (file)
index f29d6b7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.cms.ui;
-
-import java.util.function.BiFunction;
-
-/**
- * Stateless UI part creator. Takes a parent view (V) and a model context (M) in
- * order to create a view part (W) which can then be further configured. Such
- * object can be used as services and reference other part of the model which
- * are relevant for all created UI part.
- */
-@FunctionalInterface
-public interface MvcProvider<V, M, W> extends BiFunction<V, M, W> {
-       W createUiPart(V parent, M context);
-       
-       /**
-        * Whether this parent view is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isViewSupported(V parent) {
-               return true;
-       }
-
-       /**
-        * Whether this context is supported.
-        * 
-        * @return true by default.
-        */
-       default boolean isModelSupported(M context) {
-               return true;
-       }
-
-       default W apply(V parent, M context) {
-               if (!isViewSupported(parent))
-                       throw new IllegalArgumentException("Parent view " + parent + "is not supported.");
-               if (!isModelSupported(context))
-                       throw new IllegalArgumentException("Model context " + context + "is not supported.");
-               return createUiPart(parent, context);
-       }
-
-       default W createUiPart(V parent) {
-               return createUiPart(parent, null);
-       }
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/UxContext.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/UxContext.java
deleted file mode 100644 (file)
index 42d7ab3..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cms.ui;
-
-public interface UxContext {
-       boolean isPortrait();
-
-       boolean isLandscape();
-
-       boolean isSquare();
-
-       boolean isSmall();
-
-       /**
-        * Is a production environment (must be false by default, and be explicitly
-        * set during the CMS deployment). When false, it can activate additional UI
-        * capabilities in order to facilitate QA.
-        */
-       boolean isMasterData();
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/ChangePasswordDialog.java
deleted file mode 100644 (file)
index 3728692..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.cms.ui.dialogs;
-
-import java.security.PrivilegedAction;
-import java.util.Arrays;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.CmsUserManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 Log log = LogFactory.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 = CmsView.getCmsView(parentShell);
-       }
-
-       @Override
-       protected Control createInputArea(Composite userSection) {
-               addFormLabel(userSection, CmsMsg.currentPassword.lead());
-               Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               previousPassword.setLayoutData(CmsUiUtils.fillWidth());
-               addFormLabel(userSection, CmsMsg.newPassword.lead());
-               Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               newPassword.setLayoutData(CmsUiUtils.fillWidth());
-               addFormLabel(userSection, CmsMsg.repeatNewPassword.lead());
-               Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD);
-               confirmPassword.setLayoutData(CmsUiUtils.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.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsFeedback.java
deleted file mode 100644 (file)
index 30e2ad3..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.cms.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsMsg;
-import org.argeo.eclipse.ui.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 Log log = LogFactory.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.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsMessageDialog.java
deleted file mode 100644 (file)
index eb881c6..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.argeo.cms.ui.dialogs;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.Selected;
-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);
-                       CmsUiUtils.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.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/CmsWizardDialog.java
deleted file mode 100644 (file)
index e4fe35d..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-package org.argeo.cms.ui.dialogs;
-
-import java.lang.reflect.InvocationTargetException;
-
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.Selected;
-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(CmsUiUtils.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(CmsUiUtils.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.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/LightweightDialog.java
deleted file mode 100644 (file)
index 8f63479..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-package org.argeo.cms.ui.dialogs;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.ui/src/org/argeo/cms/ui/dialogs/SingleValueDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/SingleValueDialog.java
deleted file mode 100644 (file)
index 45a227e..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.ui.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.ui/src/org/argeo/cms/ui/dialogs/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/dialogs/package-info.java
deleted file mode 100644 (file)
index c88ac07..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace dialogs. */
-package org.argeo.cms.ui.dialogs;
\ No newline at end of file
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 2b3f61e..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.ui.util.CmsUiUtils;
-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(CmsUiUtils.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);
-               CmsUiUtils.markup(label);
-               CmsUiUtils.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);
-               CmsUiUtils.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);
-               CmsUiUtils.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);
-                       CmsUiUtils.style(lbl, style);
-                       CmsUiUtils.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() {
-               CmsUiUtils.style(getControl(), FormStyle.propertyText.style());
-//             getControl().setData(STYLE, FormStyle.propertyText.style());
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               CmsUiUtils.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 fd7095f..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.ui.util.CmsUiUtils;
-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())
-               CmsUiUtils.style(getControl(), FormStyle.propertyText);
-//             getControl().setData(STYLE, FormStyle.propertyText.style());
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               if (EclipseUiUtils.isEmpty(dateTxt.getText()))
-                       CmsUiUtils.style(getControl(), FormStyle.propertyMessage);
-//                     getControl().setData(STYLE, FormStyle.propertyMessage.style());
-               else
-                       CmsUiUtils.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(CmsUiUtils.fillWidth());
-               CmsUiUtils.style(lbl, style);
-               CmsUiUtils.markup(lbl);
-               if (mouseListener != null)
-                       lbl.addMouseListener(mouseListener);
-               return lbl;
-       }
-
-       private Control createCustomEditableControl(Composite box, String style) {
-               box.setLayoutData(CmsUiUtils.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);
-               CmsUiUtils.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);
-               CmsUiUtils.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
-                       CmsUiUtils.markup(CalendarPopup.this);
-                       CmsUiUtils.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 c170e8c..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.ui.util.CmsUiUtils;
-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() {
-               CmsUiUtils.style(getControl(), FormStyle.propertyText);
-               super.startEditing();
-       }
-
-       public synchronized void stopEditing() {
-               if (EclipseUiUtils.isEmpty(((Text) getControl()).getText()))
-                       CmsUiUtils.style(getControl(), FormStyle.propertyMessage);
-               else
-                       CmsUiUtils.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 7d46127..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.cms.ui.CmsEditable;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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);
-
-               CmsUiUtils.style(display, FormStyle.header.style());
-               display.setBackgroundMode(SWT.INHERIT_FORCE);
-
-               display.setLayout(CmsUiUtils.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);
-               CmsUiUtils.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 981f6ef..0000000
+++ /dev/null
@@ -1,613 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsEditable;
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 Log log = LogFactory.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 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 imageManager() {
-               if (imageManager == null)
-                       imageManager = CmsView.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(CmsUiUtils.fillWidth());
-                       section.setLayout(CmsUiUtils.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(CmsUiUtils.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(CmsUiUtils.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 + " ");
-               CmsUiUtils.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);
-               CmsUiUtils.style(label, style);
-               return label;
-       }
-
-       protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException {
-               Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
-               bodyRow.setLayoutData(CmsUiUtils.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;
-
-                       try {
-                               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);
-                                               }
-                                       }
-                               });
-                       } catch (RepositoryException re) {
-                               throw new JcrException("unable to upload image " + name + " at " + 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, preferredSize) {
-                       private static final long serialVersionUID = 1297900641952417540L;
-
-                       @Override
-                       protected void setContainerLayoutData(Composite composite) {
-                               composite.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-                       }
-
-                       @Override
-                       protected void setControlLayoutData(Control control) {
-                               control.setLayoutData(CmsUiUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
-                       }
-               };
-               img.setLayoutData(CmsUiUtils.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(CmsUiUtils.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);
-                       CmsUiUtils.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 7a05e2b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.cms.ui.forms;
-
-import org.argeo.cms.ui.util.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 a5475d6..0000000
+++ /dev/null
@@ -1,197 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.CmsView;
-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 Log log = LogFactory.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 4e00675..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.api.NodeUtils;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 = NodeUtils.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 + " >> ");
-               CmsUiUtils.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) {
-               CmsUiUtils.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 d50b8d8..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.cms.ui.fs;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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 f45629b..0000000
+++ /dev/null
@@ -1,384 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 Log log = LogFactory.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());
-               CmsUiUtils.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());
-                       CmsUiUtils.markup(btn);
-                       CmsUiUtils.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 9ffddd8..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import org.argeo.api.NodeState;
-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<NodeState, NodeState> 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, NodeState.class, null);
-               nodeState.open();
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-               if (nodeState != null) {
-                       nodeState.close();
-                       nodeState = null;
-               }
-       }
-
-       public static NodeState 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 c239996..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.cms.ui.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 5602d99..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.cms.ui.internal;
-
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.util.CmsUiUtils;
-import org.argeo.cms.ui.widgets.EditableImage;
-import org.eclipse.swt.graphics.Point;
-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 Point imageSize;
-
-       public SimpleEditableImage(Composite parent, int swtStyle) {
-               super(parent, swtStyle);
-               // load(getControl());
-               getParent().layout();
-       }
-
-       public SimpleEditableImage(Composite parent, int swtStyle, String src,
-                       Point 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());
-               CmsUiUtils.style(text, style);
-               return text;
-       }
-
-       public String getSrc() {
-               return src;
-       }
-
-       public void setSrc(String src) {
-               this.src = src;
-       }
-
-       public Point getImageSize() {
-               return imageSize;
-       }
-
-       public void setImageSize(Point imageSize) {
-               this.imageSize = imageSize;
-       }
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/rwt/UserUi.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/rwt/UserUi.java
deleted file mode 100644 (file)
index 5e21cc4..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.cms.ui.internal.rwt;
-
-import org.argeo.cms.ui.util.LoginEntryPoint;
-import org.eclipse.rap.rwt.application.Application;
-import org.eclipse.rap.rwt.application.Application.OperationMode;
-import org.eclipse.rap.rwt.application.ApplicationConfiguration;
-
-public class UserUi implements ApplicationConfiguration {
-       @Override
-       public void configure(Application application) {
-               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
-               application.addEntryPoint("/login", LoginEntryPoint.class, null);
-       }
-}
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 f8a115a..0000000
+++ /dev/null
@@ -1,76 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-
-public class DefaultRepositoryRegister extends Observable implements RepositoryRegister {
-       /** Key for a JCR repository alias */
-       private final static String CN = NodeConstants.CN;
-       /** Key for a JCR repository URI */
-       // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
-       private final static Log log = LogFactory.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 bfce888..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.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.api.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 = NodeUtils.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(), NodeConstants.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 e4e8004..0000000
+++ /dev/null
@@ -1,114 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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 6b77b37..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.CmsConstants;
-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(CmsConstants.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 27cfbbb..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.api.NodeUtils;
-import org.argeo.api.security.Keyring;
-import org.argeo.cms.ArgeoNames;
-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 = NodeUtils.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 54bb469..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.api.NodeUtils;
-import org.argeo.api.security.Keyring;
-import org.argeo.cms.ArgeoNames;
-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 = NodeUtils.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 5503484..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.NodeConstants;
-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(NodeConstants.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/useradmin/PickUpUserDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/PickUpUserDialog.java
deleted file mode 100644 (file)
index d074254..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-package org.argeo.cms.ui.useradmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.argeo.api.NodeConstants;
-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.naming.LdapAttrs;
-import org.argeo.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 + "=*"
-                                                       + NodeConstants.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.ui/src/org/argeo/cms/ui/useradmin/UserLP.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UserLP.java
deleted file mode 100644 (file)
index 05c6f37..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.ui.useradmin;
-
-import org.argeo.api.NodeConstants;
-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(NodeConstants.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.ui/src/org/argeo/cms/ui/useradmin/UsersImages.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/UsersImages.java
deleted file mode 100644 (file)
index eca4867..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.argeo.cms.ui.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.ui/src/org/argeo/cms/ui/useradmin/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/useradmin/package-info.java
deleted file mode 100644 (file)
index 3fc191c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** SWT/JFace users management components. */
-package org.argeo.cms.ui.useradmin;
\ No newline at end of file
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/AbstractCmsTheme.java
deleted file mode 100644 (file)
index c3fd796..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.argeo.cms.ui.CmsTheme;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.widgets.Display;
-
-/** Centralises some generic {@link CmsTheme} patterns. */
-public abstract class AbstractCmsTheme implements CmsTheme {
-       private Map<String, ImageData> imageCache = new HashMap<>();
-
-       private Map<String, Map<Integer, String>> iconPaths = new HashMap<>();
-
-       private Integer defaultIconSize = 16;
-
-       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;
-       }
-
-       @Override
-       public Image getIcon(String name, Integer preferredSize) {
-               if (preferredSize == null)
-                       preferredSize = defaultIconSize;
-               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;
-       }
-
-       @Override
-       public Integer getDefaultIconSize() {
-               return defaultIconSize;
-       }
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/BundleCmsTheme.java
deleted file mode 100644 (file)
index 6b997e6..0000000
+++ /dev/null
@@ -1,352 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-
-import org.apache.commons.io.IOUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-/**
- * Simplifies the theming of an app (only RAP is supported at this stage).<br>
- * 
- * Additional fonts listed in <code>/fonts.txt</code>.<br>
- * Additional (standard CSS) header in <code>/header.css</code>.<br>
- * RAP specific CSS files in <code>/rap/*.css</code>.<br>
- * All images added as additional resources based on extensions
- * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
- */
-public class BundleCmsTheme extends AbstractCmsTheme {
-       public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
-
-       public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
-       public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
-
-       private final static String HEADER_CSS = "header.css";
-       private final static String FONTS_TXT = "fonts.txt";
-       private final static String BODY_HTML = "body.html";
-
-//     private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
-
-       private String themeId;
-       private Set<String> webCssPaths = new TreeSet<>();
-       private Set<String> rapCssPaths = new TreeSet<>();
-       private Set<String> swtCssPaths = new TreeSet<>();
-       private Set<String> imagesPaths = new TreeSet<>();
-       private Set<String> fontsPaths = new TreeSet<>();
-
-       private String headerCss;
-       private List<String> fonts = new ArrayList<>();
-
-       private String bodyHtml="<body></body>";
-
-       private String basePath;
-       private String styleCssPath;
-//     private String webCssPath;
-//     private String rapCssPath;
-//     private String swtCssPath;
-       private Bundle themeBundle;
-
-       public BundleCmsTheme() {
-
-       }
-
-       public void init(BundleContext bundleContext, Map<String, String> properties) {
-               initResources(bundleContext, null);
-       }
-
-       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
-
-       }
-
-       @Deprecated
-       public BundleCmsTheme(BundleContext bundleContext) {
-               this(bundleContext, null);
-       }
-
-       @Deprecated
-       public BundleCmsTheme(BundleContext bundleContext, String symbolicName) {
-               initResources(bundleContext, symbolicName);
-       }
-
-       private void initResources(BundleContext bundleContext, String symbolicName) {
-               if (symbolicName == null) {
-                       themeBundle = bundleContext.getBundle();
-//                     basePath = "/theme/";
-//                     cssPath = basePath;
-               } else {
-                       themeBundle = findThemeBundle(bundleContext, symbolicName);
-               }
-               basePath = "/";
-               styleCssPath = "/style/";
-//             webCssPath = "/css/";
-//             rapCssPath = "/rap/";
-//             swtCssPath = "/swt/";
-//             this.themeId = RWT.DEFAULT_THEME_ID;
-               this.themeId = themeBundle.getSymbolicName();
-               webCssPaths = addCss(themeBundle, "/css/");
-               rapCssPaths = addCss(themeBundle, "/rap/");
-               swtCssPaths = addCss(themeBundle, "/swt/");
-               addImages("*.png");
-               addImages("*.gif");
-               addImages("*.jpg");
-               addImages("*.jpeg");
-               addImages("*.svg");
-               addImages("*.ico");
-
-               addFonts("*.woff");
-               addFonts("*.woff2");
-
-               // fonts
-               URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT);
-               if (fontsUrl != null) {
-                       loadFontsUrl(fontsUrl);
-               }
-
-               // common CSS header (plain CSS)
-               URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS);
-               if (headerCssUrl != null) {
-                       // added to plain Web CSS
-                       webCssPaths.add(basePath + HEADER_CSS);
-                       // and it will also be used by RAP:
-                       try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
-                               headerCss = buffer.lines().collect(Collectors.joining("\n"));
-                       } catch (IOException e) {
-                               throw new IllegalArgumentException("Cannot read " + headerCssUrl, e);
-                       }
-               }
-
-               // body
-               URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML);
-               if (bodyUrl != null) {
-                       loadBodyHtml(bodyUrl);
-               }
-}
-
-       public String getHtmlHeaders() {
-               StringBuilder sb = new StringBuilder();
-               if (headerCss != null) {
-                       sb.append("<style type='text/css'>\n");
-                       sb.append(headerCss);
-                       sb.append("\n</style>\n");
-               }
-               for (String link : fonts) {
-                       sb.append("<link rel='stylesheet' href='");
-                       sb.append(link);
-                       sb.append("'/>\n");
-               }
-               if (sb.length() == 0)
-                       return null;
-               else
-                       return sb.toString();
-       }
-       
-       
-
-       @Override
-       public String getBodyHtml() {
-               return bodyHtml;
-       }
-
-       Set<String> addCss(Bundle themeBundle, String path) {
-               Set<String> paths = new TreeSet<>();
-
-               // common CSS
-               Enumeration<URL> commonResources = themeBundle.findEntries(styleCssPath, "*.css", true);
-               if (commonResources != null) {
-                       while (commonResources.hasMoreElements()) {
-                               String resource = commonResources.nextElement().getPath();
-                               // remove first '/' so that RWT registers it
-                               resource = resource.substring(1);
-                               if (!resource.endsWith("/")) {
-                                       paths.add(resource);
-                               }
-                       }
-               }
-
-               // specific CSS
-               Enumeration<URL> themeResources = themeBundle.findEntries(path, "*.css", true);
-               if (themeResources != null) {
-                       while (themeResources.hasMoreElements()) {
-                               String resource = themeResources.nextElement().getPath();
-                               // remove first '/' so that RWT registers it
-                               resource = resource.substring(1);
-                               if (!resource.endsWith("/")) {
-                                       paths.add(resource);
-                               }
-                       }
-               }
-               return paths;
-       }
-
-       void loadFontsUrl(URL url) {
-               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
-                       String line = null;
-                       while ((line = in.readLine()) != null) {
-                               line = line.trim();
-                               if (!line.equals("") && !line.startsWith("#")) {
-                                       fonts.add(line);
-                               }
-                       }
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot load URL " + url, e);
-               }
-       }
-
-       void loadBodyHtml(URL url) {
-               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
-               bodyHtml=       IOUtils.toString(url,StandardCharsets.UTF_8);
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot load URL " + url, e);
-               }
-       }
-
-       void addImages(String pattern) {
-               Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
-               if (themeResources == null)
-                       return;
-               while (themeResources.hasMoreElements()) {
-                       String resource = themeResources.nextElement().getPath();
-                       // remove first '/' so that RWT registers it
-                       resource = resource.substring(1);
-                       if (!resource.endsWith("/")) {
-//                             if (resources.containsKey(resource))
-//                                     log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
-//                             resources.put(resource, themeBRL);
-                               imagesPaths.add(resource);
-                       }
-
-               }
-
-       }
-
-       void addFonts(String pattern) {
-               Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
-               if (themeResources == null)
-                       return;
-               while (themeResources.hasMoreElements()) {
-                       String resource = themeResources.nextElement().getPath();
-                       // remove first '/' so that RWT registers it
-                       resource = resource.substring(1);
-                       if (!resource.endsWith("/")) {
-//                             if (resources.containsKey(resource))
-//                                     log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
-//                             resources.put(resource, themeBRL);
-                               fontsPaths.add(resource);
-                       }
-
-               }
-
-       }
-
-       @Override
-       public InputStream getResourceAsStream(String resourceName) throws IOException {
-               URL res = themeBundle.getEntry(resourceName);
-               if (res == null) {
-                       res = themeBundle.getResource(resourceName);
-                       if (res == null)
-                               return null;
-//                             throw new IllegalArgumentException(
-//                                             "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
-               }
-               return res.openStream();
-       }
-
-       public String getThemeId() {
-               return themeId;
-       }
-
-//     public void setThemeId(String themeId) {
-//             this.themeId = themeId;
-//     }
-//
-//     public String getBasePath() {
-//             return basePath;
-//     }
-//
-//     public void setBasePath(String basePath) {
-//             this.basePath = basePath;
-//     }
-//
-//     public String getRapCssPath() {
-//             return rapCssPath;
-//     }
-//
-//     public void setRapCssPath(String cssPath) {
-//             this.rapCssPath = cssPath;
-//     }
-
-       @Override
-       public Set<String> getWebCssPaths() {
-               return webCssPaths;
-       }
-
-       @Override
-       public Set<String> getRapCssPaths() {
-               return rapCssPaths;
-       }
-
-       @Override
-       public Set<String> getSwtCssPaths() {
-               return swtCssPaths;
-       }
-
-       @Override
-       public Set<String> getImagesPaths() {
-               return imagesPaths;
-       }
-
-       @Override
-       public Set<String> getFontsPaths() {
-               return fontsPaths;
-       }
-
-       @Override
-       public InputStream loadPath(String path) throws IOException {
-               URL url = themeBundle.getResource(path);
-               if (url == null)
-                       throw new IllegalArgumentException(
-                                       "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
-               return url.openStream();
-       }
-
-       private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
-               if (themeId == null)
-                       return null;
-               // TODO optimize
-               // TODO deal with multiple versions
-               Bundle themeBundle = null;
-               if (themeId != null) {
-                       for (Bundle bundle : bundleContext.getBundles())
-                               if (themeId.equals(bundle.getSymbolicName())) {
-                                       themeBundle = bundle;
-                                       break;
-                               }
-               }
-               return themeBundle;
-       }
-
-       @Override
-       public int hashCode() {
-               return themeId.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return "Bundle CMS Theme " + themeId;
-       }
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsEvent.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsEvent.java
deleted file mode 100644 (file)
index ca0797b..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.ui.CmsView;
-
-/**
- * Can be applied to {@link Enum}s in order to define events used by
- * {@link CmsView#sendEvent(String, java.util.Map)}.
- */
-public interface CmsEvent {
-       String name();
-
-       default String topic() {
-               return getTopicBase() + "/" + name();
-       }
-
-       default         String getTopicBase() {
-               return "argeo/cms";
-       }
-
-
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsIcon.java
deleted file mode 100644 (file)
index 33f13ad..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.ui.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 theme.getIcon(name(), getSmallIconSize());
-       }
-
-       default Image getBigIcon(CmsTheme theme) {
-               return theme.getIcon(name(), getBigIconSize());
-       }
-
-       default Integer getSmallIconSize() {
-               return 16;
-       }
-
-       default Integer getBigIconSize() {
-               return 32;
-       }
-}
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 4cad1de..0000000
+++ /dev/null
@@ -1,281 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.auth.CurrentUser;
-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 Log log = LogFactory.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(CmsUiUtils.noSpaceGridLayout());
-
-               Label link = new Label(comp, SWT.NONE);
-               CmsUiUtils.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);
-               CmsUiUtils.style(comp, style != null ? style : getDefaultStyle());
-               CmsUiUtils.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 = NodeUtils.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 68f1e0f..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.cms.ui.util;
-
-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(CmsUiUtils.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/CmsStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsStyle.java
deleted file mode 100644 (file)
index ed2244b..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.cms.ui.util;
-
-/** Can be applied to {@link Enum}s in order to generate (CSS) class names. */
-public interface CmsStyle {
-       String name();
-
-       /** @deprecated use {@link #style()} instead. */
-       @Deprecated
-       default String toStyleClass() {
-               return style();
-       }
-
-       default String style() {
-               String classPrefix = getClassPrefix();
-               return "".equals(classPrefix) ? name() : classPrefix + "-" + name();
-       }
-
-       default String getClassPrefix() {
-               return "";
-       }
-
-}
diff --git a/org.argeo.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 b90c017..0000000
+++ /dev/null
@@ -1,403 +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.HashMap;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.ui.CmsConstants;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.eclipse.ui.Selected;
-import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils;
-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.swt.SWT;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.graphics.ImageData;
-import org.eclipse.swt.graphics.Point;
-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.Display;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Table;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities for the CMS framework. */
-public class CmsUiUtils implements CmsConstants {
-       // private final static Log log = LogFactory.getLog(CmsUiUtils.class);
-
-       /*
-        * CMS 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) -> {
-                       CmsView.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);
-       }
-
-       /**
-        * The CMS view related to this display, or null if none is available from this
-        * call.
-        * 
-        * @deprecated Use {@link CmsView#getCmsView(Composite)} instead.
-        */
-       @Deprecated
-       public static CmsView getCmsView() {
-//             return UiContext.getData(CmsView.class.getName());
-               return CmsView.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) throws RepositoryException {
-               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) throws RepositoryException {
-               return getDataPath(NodeConstants.EGO_REPOSITORY, node);
-       }
-
-       public static String getDataPath(String cn, Node node) throws RepositoryException {
-               return NodeUtils.getDataPath(cn, node);
-       }
-
-       /** Clean reserved URL characters for use in HTTP links. */
-       public static String getDataPathForUrl(Node node) throws RepositoryException {
-               return cleanPathForUrl(getDataPath(node));
-       }
-
-       /** Clean reserved URL characters for use in HTTP links. */
-       public static String cleanPathForUrl(String path) throws RepositoryException {
-               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);
-
-       /*
-        * 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 = 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.toStyleClass());
-       }
-
-       /** 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(Widget)
-        */
-       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);
-       }
-
-       @Deprecated
-       public static void setItemHeight(Table table, int height) {
-               table.setData(CmsConstants.ITEM_HEIGHT, height);
-       }
-
-       /** Dispose all children of a Composite */
-       public static void clear(Composite composite) {
-               if (composite.isDisposed())
-                       return;
-               for (Control child : composite.getChildren())
-                       child.dispose();
-       }
-
-       //
-       // 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;
-               try {
-                       src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode);
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot get URL data path for " + fileNode, e);
-               }
-               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, Point size) {
-               return img(src, Integer.toString(size.x), Integer.toString(size.y));
-       }
-
-       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(Point size) {
-               ResourceManager rm = RWT.getResourceManager();
-               return CmsUiUtils.img(rm.getLocation(NO_IMAGE), size);
-       }
-
-       public static String noImg() {
-               return noImg(NO_IMAGE_SIZE);
-       }
-
-       public static Image noImage(Point size) {
-               ResourceManager rm = RWT.getResourceManager();
-               InputStream in = null;
-               try {
-                       in = rm.getRegisteredContent(NO_IMAGE);
-                       ImageData id = new ImageData(in);
-                       ImageData scaled = id.scaledTo(size.x, size.y);
-                       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 6c4a870..0000000
+++ /dev/null
@@ -1,234 +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.CmsConstants.NO_IMAGE_SIZE;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsImageManager;
-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 {
-       private final static Log log = LogFactory.getLog(DefaultImageManager.class);
-//     private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
-
-       public Boolean load(Node node, Control control, Point preferredSize) throws RepositoryException {
-               Point imageSize = getImageSize(node);
-               Point size;
-               String imgTag = null;
-               if (preferredSize == null || imageSize.x == 0 || imageSize.y == 0
-                               || (preferredSize.x == 0 && preferredSize.y == 0)) {
-                       if (imageSize.x != 0 && imageSize.y != 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.x != 0 && preferredSize.y != 0) {
-                       // given size if completely provided
-                       size = preferredSize;
-               } else {
-                       // at this stage :
-                       // image is completely known
-                       assert imageSize.x != 0 && imageSize.y != 0;
-                       // one and only one of the dimension as been specified
-                       assert preferredSize.x == 0 || preferredSize.y == 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(size);
-                       return loaded;
-               } else
-                       loaded = false;
-
-               return loaded;
-       }
-
-       private Point resizeTo(Point orig, Point constraints) {
-               if (constraints.x != 0 && constraints.y != 0) {
-                       return constraints;
-               } else if (constraints.x == 0 && constraints.y == 0) {
-                       return orig;
-               } else if (constraints.y == 0) {// force width
-                       return new Point(constraints.x, scale(orig.y, orig.x, constraints.x));
-               } else if (constraints.x == 0) {// force height
-                       return new Point(scale(orig.x, orig.y, constraints.y), constraints.y);
-               }
-               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 Point getImageSize(Node node) throws RepositoryException {
-               // TODO optimise
-               Image image = getSwtImage(node);
-               return new Point(image.getBounds().width, image.getBounds().height);
-       }
-
-       /** @return null if not available */
-       @Override
-       public String getImageTag(Node node) throws RepositoryException {
-               return getImageTag(node, getImageSize(node));
-       }
-
-       private String getImageTag(Node node, Point size) throws RepositoryException {
-               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, Point size) throws RepositoryException {
-               return getImageTagBuilder(node, Integer.toString(size.x), Integer.toString(size.y));
-       }
-
-       /** @return null if not available */
-       private StringBuilder getImageTagBuilder(Node node, String width, String height) throws RepositoryException {
-               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) throws RepositoryException {
-               return CmsUiUtils.getDataPathForUrl(node);
-       }
-
-       protected String getResourceName(Node node) throws RepositoryException {
-               String workspace = node.getSession().getWorkspace().getName();
-               if (node.hasNode(JCR_CONTENT))
-                       return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier();
-               else
-                       return workspace + '_' + node.getIdentifier();
-       }
-
-       public Binary getImageBinary(Node node) throws RepositoryException {
-               if (node.isNodeType(NT_FILE)) {
-                       return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary();
-               } else {
-                       return null;
-               }
-       }
-
-       public Image getSwtImage(Node node) throws RepositoryException {
-               InputStream inputStream = null;
-               Binary binary = getImageBinary(node);
-               if (binary == null)
-                       return null;
-               try {
-                       inputStream = binary.getStream();
-                       return new Image(Display.getCurrent(), inputStream);
-               } finally {
-                       IOUtils.closeQuietly(inputStream);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       @Override
-       public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType)
-                       throws RepositoryException {
-               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);
-               } 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/LoginEntryPoint.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/LoginEntryPoint.java
deleted file mode 100644 (file)
index 0bbed1d..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import java.util.Locale;
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.UxContext;
-import org.argeo.cms.ui.widgets.auth.CmsLogin;
-import org.argeo.cms.ui.widgets.auth.CmsLoginShell;
-import org.argeo.eclipse.ui.specific.UiContext;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.application.EntryPoint;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-
-public class LoginEntryPoint implements EntryPoint, CmsView {
-       protected final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-       protected final static String HEADER_AUTHORIZATION = "Authorization";
-       private final static Log log = LogFactory.getLog(LoginEntryPoint.class);
-       private LoginContext loginContext;
-       private UxContext uxContext = null;
-       private String uid;
-
-       @Override
-       public int createUI() {
-               uid = UUID.randomUUID().toString();
-               final Display display = createDisplay();
-//             UiContext.setData(CmsView.KEY, this);
-
-               CmsLoginShell loginShell = createCmsLoginShell();
-               CmsView.registerCmsView(loginShell.getShell(), this);
-               try {
-                       // try pre-auth
-                       loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, loginShell);
-                       loginContext.login();
-               } catch (LoginException e) {
-                       loginShell.createUi();
-                       loginShell.open();
-
-                       // HttpServletRequest request = RWT.getRequest();
-                       // String authorization = request.getHeader(HEADER_AUTHORIZATION);
-                       // if (authorization == null ||
-                       // !authorization.startsWith("Negotiate")) {
-                       // HttpServletResponse response = RWT.getResponse();
-                       // response.setStatus(401);
-                       // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
-                       // response.setDateHeader("Date", System.currentTimeMillis());
-                       // response.setDateHeader("Expires", System.currentTimeMillis() +
-                       // (24 * 60 * 60 * 1000));
-                       // response.setHeader("Accept-Ranges", "bytes");
-                       // response.setHeader("Connection", "Keep-Alive");
-                       // response.setHeader("Keep-Alive", "timeout=5, max=97");
-                       // // response.setContentType("text/html; charset=UTF-8");
-                       // }
-
-                       while (!loginShell.getShell().isDisposed()) {
-                               if (!display.readAndDispatch())
-                                       display.sleep();
-                       }
-               }
-
-               if (CurrentUser.getUsername(getSubject()) == null)
-                       return -1;
-               uxContext = new SimpleUxContext();
-               return postLogin();
-       }
-
-       protected Display createDisplay() {
-               return new Display();
-       }
-
-       protected int postLogin() {
-               return 0;
-       }
-
-       protected HttpServletRequest getRequest() {
-               return RWT.getRequest();
-       }
-
-       protected CmsLoginShell createCmsLoginShell() {
-               return new CmsLoginShell(this) {
-
-                       @Override
-                       public void createContents(Composite parent) {
-                               LoginEntryPoint.this.createLoginPage(parent, this);
-                       }
-
-                       @Override
-                       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
-                                       SelectionListener loginSelectionListener) {
-                               LoginEntryPoint.this.extendsCredentialsBlock(credentialsBlock, selectedLocale, loginSelectionListener);
-                       }
-
-               };
-       }
-
-       /**
-        * To be overridden. CmsLogin#createCredentialsBlock() should be called at some
-        * point in order to create the credentials composite. In order to use the
-        * default layout, call CmsLogin#defaultCreateContents() but <b>not</b>
-        * CmsLogin#createContent(), since it would lead to a stack overflow.
-        */
-       protected void createLoginPage(Composite parent, CmsLogin login) {
-               login.defaultCreateContents(parent);
-       }
-
-       protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
-                       SelectionListener loginSelectionListener) {
-
-       }
-
-       @Override
-       public String getUid() {
-               return uid;
-       }
-
-       @Override
-       public void navigateTo(String state) {
-               // TODO Auto-generated method stub
-
-       }
-
-       @Override
-       public void authChange(LoginContext loginContext) {
-               if (loginContext == null)
-                       throw new CmsException("Login context cannot be null");
-               // logout previous login context
-               if (this.loginContext != null)
-                       try {
-                               this.loginContext.logout();
-                       } catch (LoginException e1) {
-                               log.warn("Could not log out: " + e1);
-                       }
-               this.loginContext = loginContext;
-       }
-
-       @Override
-       public void logout() {
-               if (loginContext == null)
-                       throw new CmsException("Login context should not bet null");
-               try {
-                       CurrentUser.logoutCmsSession(loginContext.getSubject());
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       throw new CmsException("Cannot log out", e);
-               }
-       }
-
-       @Override
-       public void exception(Throwable e) {
-               // TODO Auto-generated method stub
-
-       }
-
-       // @Override
-       // public LoginContext getLoginContext() {
-       // return loginContext;
-       // }
-
-       protected Subject getSubject() {
-               return loginContext.getSubject();
-       }
-
-       @Override
-       public boolean isAnonymous() {
-               return CurrentUser.isAnonymous(getSubject());
-       }
-
-       @Override
-       public CmsImageManager getImageManager() {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public UxContext getUxContext() {
-               return uxContext;
-       }
-}
\ No newline at end of file
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 d581436..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.ui.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 04ce5a2..0000000
+++ /dev/null
@@ -1,96 +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.ui.CmsStyles;
-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(CmsUiUtils.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(CmsUiUtils.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 d394f23..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.ui.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 4bbd19c..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.argeo.cms.ui.util;
-
-/** 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/SimpleUxContext.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleUxContext.java
deleted file mode 100644 (file)
index f85a1ab..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.argeo.cms.ui.util;
-
-import org.argeo.cms.ui.UxContext;
-import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Display;
-
-public class SimpleUxContext implements UxContext {
-       private Point size;
-       private Point small = new Point(400, 400);
-
-       public SimpleUxContext() {
-               this(Display.getCurrent().getBounds());
-       }
-
-       public SimpleUxContext(Rectangle rect) {
-               this.size = new Point(rect.width, rect.height);
-       }
-
-       public SimpleUxContext(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.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 f53e552..0000000
+++ /dev/null
@@ -1,128 +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.ui.CmsStyles;
-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);
-               CmsUiUtils.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 07b6069..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.ui.widgets.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 cc470e4..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.ui.CmsStyles;
-import org.argeo.cms.ui.widgets.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 e7dfb4b..0000000
+++ /dev/null
@@ -1,43 +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.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(CmsUiUtils.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 e5ae67a..0000000
+++ /dev/null
@@ -1,351 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.ui.CmsEditable;
-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 Log log = LogFactory.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 dfd1438..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.cms.CmsException;
-import org.argeo.cms.ui.CmsEditable;
-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 88585e1..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.ui.util.CmsUiUtils;
-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(CmsUiUtils.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(CmsUiUtils.fillWidth());
-               sectionHeader.setLayout(CmsUiUtils.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 f8ca531..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import org.argeo.cms.ui.util.CmsUiUtils;
-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(CmsUiUtils.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(CmsUiUtils.noSpaceGridLayout());
-               Composite placeholder = new Composite(shell, SWT.BORDER);
-               placeholder.setLayoutData(CmsUiUtils.fillAll());
-               placeholder.setLayout(CmsUiUtils.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 0a327cd..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.getLog(EditableImage.class);
-
-       private Point preferredImageSize;
-       private Boolean loaded = false;
-
-       public EditableImage(Composite parent, int swtStyle) {
-               super(parent, swtStyle);
-       }
-
-       public EditableImage(Composite parent, int swtStyle,
-                       Point preferredImageSize) {
-               super(parent, swtStyle);
-               this.preferredImageSize = preferredImageSize;
-       }
-
-       public EditableImage(Composite parent, int style, Node node,
-                       boolean cacheImmediately, Point 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
-                               : getSize());
-       }
-
-       protected Label createLabel(Composite box, String style) {
-               Label lbl = new Label(box, getStyle());
-               // lbl.setLayoutData(CmsUiUtils.fillWidth());
-               CmsUiUtils.markup(lbl);
-               CmsUiUtils.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 ? preferredImageSize
-                                       : getSize());
-               } else {
-                       loaded = false;
-               }
-               getParent().layout();
-               return loaded;
-       }
-
-       public void setPreferredSize(Point size) {
-               this.preferredImageSize = size;
-               if (!loaded) {
-                       load((Label) getControl());
-               }
-       }
-
-       protected Text createText(Composite box, String style) {
-               Text text = new Text(box, getStyle());
-               CmsUiUtils.style(text, style);
-               return text;
-       }
-
-       public Point 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 27b7c9b..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.ui.util.CmsUiUtils;
-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(CmsUiUtils.fillWidth());
-               if (style != null)
-                       CmsUiUtils.style(lbl, style);
-               CmsUiUtils.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(CmsUiUtils.fillWidth());
-               if (style != null)
-                       CmsUiUtils.style(lbl, style);
-               CmsUiUtils.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 = CmsUiUtils.fillWidth();
-               // textLayoutData.heightHint = preferredHeight;
-               text.setLayoutData(textLayoutData);
-               if (style != null)
-                       CmsUiUtils.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 fb1091a..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.argeo.cms.ui.CmsImageManager;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.internal.JcrFileUploadReceiver;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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.graphics.Point;
-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 imageManager;
-       private FileUploadHandler currentUploadHandler = null;
-       private FileUploadListener fileUploadListener;
-
-       public Img(Composite parent, int swtStyle, Node imgNode, Point 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 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, Point preferredImageSize,
-                       CmsImageManager imageManager) throws RepositoryException {
-               super(parent, swtStyle, imgNode, false, preferredImageSize);
-               this.section = section;
-               this.imageManager = imageManager != null ? imageManager : CmsView.getCmsView(section).getImageManager();
-               CmsUiUtils.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) {
-               try {
-                       Node imgNode = getNode();
-                       boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize());
-                       // getParent().layout();
-                       return loaded;
-               } catch (RepositoryException e) {
-                       throw new JcrException("Cannot load " + getNodeId() + " from image manager", e);
-               }
-       }
-
-       protected Node getUploadFolder() {
-               return Jcr.getParent(getNode());
-       }
-
-       protected String getUploadName() {
-               Node node = getNode();
-               return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']';
-       }
-
-       protected CmsImageManager 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);
-               CmsUiUtils.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 2d394c6..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.ui.util.CmsUiUtils;
-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(CmsUiUtils.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 b413faf..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-package org.argeo.cms.ui.widgets;
-
-import javax.jcr.Item;
-
-import org.argeo.cms.ui.CmsConstants;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 CmsConstants {
-       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(CmsUiUtils.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(CmsUiUtils.noSpaceGridLayout(3));
-               return box;
-       }
-
-       protected Composite createContainer() {
-               Composite container = new Composite(this, SWT.INHERIT_DEFAULT);
-               setContainerLayoutData(container);
-               container.setLayout(CmsUiUtils.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) {
-                       CmsUiUtils.style(box, style + "_box");
-                       CmsUiUtils.style(container, style + "_container");
-               }
-       }
-
-       /** To be overridden */
-       protected void setControlLayoutData(Control control) {
-               control.setLayoutData(CmsUiUtils.fillWidth());
-       }
-
-       /** To be overridden */
-       protected void setContainerLayoutData(Composite composite) {
-               composite.setLayoutData(CmsUiUtils.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/auth/AbstractLoginDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/AbstractLoginDialog.java
deleted file mode 100644 (file)
index e7998cf..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-package org.argeo.cms.ui.widgets.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 org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jface.dialogs.IDialogConstants;
-import org.eclipse.jface.dialogs.TrayDialog;
-import org.eclipse.jface.operation.IRunnableWithProgress;
-import org.eclipse.jface.operation.ModalContext;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
-import org.osgi.framework.FrameworkUtil;
-
-/** Base for login dialogs */
-public abstract class AbstractLoginDialog extends TrayDialog implements CallbackHandler {
-       private static final long serialVersionUID = -8046708963512717709L;
-
-       private final static Log log = LogFactory.getLog(AbstractLoginDialog.class);
-
-       private Thread modalContextThread = null;
-       boolean processCallbacks = false;
-       boolean isCancelled = false;
-       Callback[] callbackArray;
-
-       protected final Callback[] getCallbacks() {
-               return this.callbackArray;
-       }
-
-       public abstract void internalHandle();
-
-       public boolean isCancelled() {
-               return isCancelled;
-       }
-
-       protected AbstractLoginDialog(Shell parentShell) {
-               super(parentShell);
-       }
-
-       /*
-        * (non-Javadoc)
-        * 
-        * @see
-        * javax.security.auth.callback.CallbackHandler#handle(javax.security.auth
-        * .callback.Callback[])
-        */
-       public void handle(final Callback[] callbacks) throws IOException {
-               // clean previous usage
-               if (processCallbacks) {
-                       // this handler was already used
-                       processCallbacks = false;
-               }
-
-               if (modalContextThread != null) {
-                       try {
-                               modalContextThread.join(1000);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       modalContextThread = null;
-               }
-
-               // initialize
-               this.callbackArray = callbacks;
-               final Display display = Display.getDefault();
-               display.syncExec(new Runnable() {
-
-                       public void run() {
-                               isCancelled = false;
-                               setBlockOnOpen(false);
-                               open();
-
-                               final Button okButton = getButton(IDialogConstants.OK_ID);
-                               okButton.setText("Login");
-                               okButton.addSelectionListener(new SelectionListener() {
-                                       private static final long serialVersionUID = -200281625679096775L;
-
-                                       public void widgetSelected(final SelectionEvent event) {
-                                               processCallbacks = true;
-                                       }
-
-                                       public void widgetDefaultSelected(final SelectionEvent event) {
-                                               // nothing to do
-                                       }
-                               });
-                               final Button cancel = getButton(IDialogConstants.CANCEL_ID);
-                               cancel.addSelectionListener(new SelectionListener() {
-                                       private static final long serialVersionUID = -3826030278084915815L;
-
-                                       public void widgetSelected(final SelectionEvent event) {
-                                               isCancelled = true;
-                                               processCallbacks = true;
-                                       }
-
-                                       public void widgetDefaultSelected(final SelectionEvent event) {
-                                               // nothing to do
-                                       }
-                               });
-                       }
-               });
-               try {
-                       ModalContext.setAllowReadAndDispatch(true); // Works for now.
-                       ModalContext.run(new IRunnableWithProgress() {
-
-                               public void run(final IProgressMonitor monitor) {
-                                       modalContextThread = Thread.currentThread();
-                                       // Wait here until OK or cancel is pressed, then let it rip.
-                                       // The event
-                                       // listener
-                                       // is responsible for closing the dialog (in the
-                                       // loginSucceeded
-                                       // event).
-                                       while (!processCallbacks && (modalContextThread != null)
-                                                       && (modalContextThread == Thread.currentThread())
-                                                       && FrameworkUtil.getBundle(AbstractLoginDialog.class).getBundleContext() != null) {
-                                               // Note: SecurityUiPlugin.getDefault() != null is false
-                                               // when the OSGi runtime is shut down
-                                               try {
-                                                       Thread.sleep(100);
-                                                       // if (display.isDisposed()) {
-                                                       // log.warn("Display is disposed, killing login
-                                                       // dialog thread");
-                                                       // throw new ThreadDeath();
-                                                       // }
-                                               } catch (final Exception e) {
-                                                       // do nothing
-                                               }
-                                       }
-                                       processCallbacks = false;
-                                       // Call the adapter to handle the callbacks
-                                       if (!isCancelled())
-                                               internalHandle();
-                                       else
-                                               // clear callbacks are when cancelling
-                                               for (Callback callback : callbacks)
-                                                       if (callback instanceof PasswordCallback) {
-                                                               char[] arr = ((PasswordCallback) callback).getPassword();
-                                                               if (arr != null) {
-                                                                       Arrays.fill(arr, '*');
-                                                                       ((PasswordCallback) callback).setPassword(null);
-                                                               }
-                                                       } else if (callback instanceof NameCallback)
-                                                               ((NameCallback) callback).setName(null);
-                               }
-                       }, true, new NullProgressMonitor(), Display.getDefault());
-               } catch (ThreadDeath e) {
-                       isCancelled = true;
-                       log.debug("Thread " + Thread.currentThread().getId() + " died");
-                       throw e;
-               } catch (Exception e) {
-                       isCancelled = true;
-                       IOException ioe = new IOException("Unexpected issue in login dialog, see root cause for more details");
-                       ioe.initCause(e);
-                       throw ioe;
-               } finally {
-                       // so that the modal thread dies
-                       processCallbacks = true;
-                       // try {
-                       // // wait for the modal context thread to gracefully exit
-                       // modalContextThread.join();
-                       // } catch (InterruptedException ie) {
-                       // // silent
-                       // }
-                       modalContextThread = null;
-               }
-       }
-
-       protected void configureShell(Shell shell) {
-               super.configureShell(shell);
-               shell.setText("Authentication");
-       }
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLogin.java
deleted file mode 100644 (file)
index 51c0ab4..0000000
+++ /dev/null
@@ -1,335 +0,0 @@
-package org.argeo.cms.ui.widgets.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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeState;
-import org.argeo.cms.CmsMsg;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.cms.auth.HttpRequestCallback;
-import org.argeo.cms.ui.CmsStyles;
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.internal.Activator;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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 Log log = LogFactory.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;
-               NodeState nodeState = Activator.getNodeState();
-               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(CmsUiUtils.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);
-               CmsUiUtils.style(l, CMS_USER_MENU_ITEM);
-               l.setText(CmsMsg.logout.lead(locale));
-               GridData lData = CmsUiUtils.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());
-               CmsUiUtils.style(credentialsBlock, CMS_LOGIN_DIALOG);
-
-               Integer textWidth = 120;
-               if (parent instanceof Shell)
-                       CmsUiUtils.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));
-               CmsUiUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME);
-               GridData gd = CmsUiUtils.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));
-               CmsUiUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD);
-               gd = CmsUiUtils.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(CmsUiUtils.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);
-               CmsUiUtils.style(c, CMS_USER_MENU_ITEM);
-               c.setLayout(CmsUiUtils.noSpaceGridLayout());
-               c.setLayoutData(CmsUiUtils.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);
-                       CmsUiUtils.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(NodeConstants.LOGIN_CONTEXT_USER, this);
-                       else
-                               loginContext = new LoginContext(NodeConstants.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 HttpRequestCallback) {
-                               ((HttpRequestCallback) callback).setRequest(UiContext.getHttpRequest());
-                               ((HttpRequestCallback) callback).setResponse(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.ui/src/org/argeo/cms/ui/widgets/auth/CmsLoginShell.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CmsLoginShell.java
deleted file mode 100644 (file)
index f260f83..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.ui.widgets.auth;
-
-import org.argeo.cms.ui.CmsView;
-import org.argeo.cms.ui.util.CmsUiUtils;
-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() {
-               CmsUiUtils.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.ui/src/org/argeo/cms/ui/widgets/auth/CompositeCallbackHandler.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/CompositeCallbackHandler.java
deleted file mode 100644 (file)
index 974b676..0000000
+++ /dev/null
@@ -1,273 +0,0 @@
-package org.argeo.cms.ui.widgets.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.ui/src/org/argeo/cms/ui/widgets/auth/DefaultLoginDialog.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/DefaultLoginDialog.java
deleted file mode 100644 (file)
index 6ffc2ef..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.cms.ui.widgets.auth;
-
-import javax.security.auth.callback.CallbackHandler;
-
-import org.eclipse.swt.SWT;
-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;
-
-/** Default authentication dialog, to be used as {@link CallbackHandler}. */
-public class DefaultLoginDialog extends AbstractLoginDialog {
-       private static final long serialVersionUID = -8551827590693035734L;
-
-       public DefaultLoginDialog() {
-               this(Display.getCurrent().getActiveShell());
-       }
-
-       public DefaultLoginDialog(Shell parentShell) {
-               super(parentShell);
-       }
-
-       protected Point getInitialSize() {
-               return new Point(350, 180);
-       }
-
-       @Override
-       protected Control createContents(Composite parent) {
-               Control control = super.createContents(parent);
-               parent.pack();
-
-               // Move the dialog to the center of the top level shell.
-               Rectangle shellBounds;
-               if (Display.getCurrent().getActiveShell() != null) // RCP
-                       shellBounds = Display.getCurrent().getActiveShell().getBounds();
-               else
-                       shellBounds = Display.getCurrent().getBounds();// RAP
-               Point dialogSize = parent.getSize();
-               int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2;
-               int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2;
-               parent.setLocation(x, y);
-               return control;
-       }
-
-       protected Control createDialogArea(Composite parent) {
-               Composite dialogarea = (Composite) super.createDialogArea(parent);
-               CompositeCallbackHandler composite = new CompositeCallbackHandler(
-                               dialogarea, SWT.NONE);
-               composite.setLayout(new GridLayout(2, false));
-               composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
-               composite.createCallbackHandlers(getCallbacks());
-               return composite;
-       }
-
-       public void internalHandle() {
-       }
-}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/DynamicCallbackHandler.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/DynamicCallbackHandler.java
deleted file mode 100644 (file)
index e470bda..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.cms.ui.widgets.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.ui/src/org/argeo/cms/ui/widgets/auth/LocaleChoice.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/LocaleChoice.java
deleted file mode 100644 (file)
index 677c879..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.argeo.cms.ui.widgets.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.ui/src/org/argeo/cms/ui/widgets/auth/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/auth/package-info.java
deleted file mode 100644 (file)
index 9a37a58..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo CMS authentication widgets, based on SWT. */
-package org.argeo.cms.ui.widgets.auth;
\ No newline at end of file
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
index 9a0a269a24f6260798528659233524a7c323c9c7..4a00becd81df57ed094ab0d2958c974510456391 100644 (file)
@@ -1,8 +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"/>
+       <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="src" path="ext/test"/>
        <classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/org.argeo.cms/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644 (file)
index ce5f464..0000000
+++ /dev/null
@@ -1,102 +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.suppressWarningsNotFullyAnalysed=info
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
diff --git a/org.argeo.cms/OSGI-INF/cmsContext.xml b/org.argeo.cms/OSGI-INF/cmsContext.xml
new file mode 100644 (file)
index 0000000..63d4319
--- /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" activate="start" deactivate="stop" immediate="true" name="CMS Context">
+   <implementation class="org.argeo.cms.internal.runtime.CmsContextImpl"/>
+   <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" name="CmsDeployment" policy="static"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsContext"/>
+   </service>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsDeployment.xml b/org.argeo.cms/OSGI-INF/cmsDeployment.xml
new file mode 100644 (file)
index 0000000..d36a911
--- /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="start" deactivate="stop" immediate="false" name="CMS Deployment">
+   <reference bind="setDeployConfig" cardinality="1..1" interface="org.argeo.cms.internal.osgi.DeployConfig" name="DeployConfig" policy="static"/>
+   <implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
+   <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsDeployment"/>
+   </service>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/cmsState.xml b/org.argeo.cms/OSGI-INF/cmsState.xml
new file mode 100644 (file)
index 0000000..a81e9f0
--- /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" activate="start" deactivate="stop" immediate="false" name="CMS State">
+   <implementation class="org.argeo.cms.internal.runtime.CmsStateImpl"/>
+   <service>
+      <provide interface="org.argeo.api.cms.CmsState"/>
+   </service>
+</scr:component>
index 045c9609a2a6bf7b2e7b4441ce683cbeac261213..524c054eccf733b1a3a295cce1d9e64dc918d7d9 100644 (file)
@@ -5,6 +5,6 @@
       <provide interface="org.argeo.cms.CmsUserManager"/>
    </service>
    <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
-   <reference bind="setUserTransaction" cardinality="1..1" interface="javax.transaction.UserTransaction" name="UserTransaction" policy="static"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="UserTransaction" policy="static"/>
    <reference bind="addUserDirectory" cardinality="0..n" interface="org.argeo.osgi.useradmin.UserDirectory" name="UserDirectory" policy="static" unbind="removeUserDirectory"/>
-</scr:component>
\ No newline at end of file
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/dataServletContext.xml b/org.argeo.cms/OSGI-INF/dataServletContext.xml
deleted file mode 100644 (file)
index ffd8804..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.servlet.CmsServletContext"/>
-   <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/OSGI-INF/deployConfig.xml b/org.argeo.cms/OSGI-INF/deployConfig.xml
new file mode 100644 (file)
index 0000000..0309434
--- /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="start" deactivate="stop" name="Deploy Config">
+   <implementation class="org.argeo.cms.internal.osgi.DeployConfig"/>
+   <service>
+      <provide interface="org.argeo.cms.internal.osgi.DeployConfig"/>
+   </service>
+   <reference bind="setConfigurationAdmin" cardinality="1..1" interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" policy="static"/>
+   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/filesServlet.xml b/org.argeo.cms/OSGI-INF/filesServlet.xml
deleted file mode 100644 (file)
index ec161fa..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.internal.http.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/internal/http/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/OSGI-INF/filesServletContext.xml b/org.argeo.cms/OSGI-INF/filesServletContext.xml
deleted file mode 100644 (file)
index 049270c..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.servlet.PrivateWwwAuthServletContext"/>
-   <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/OSGI-INF/jcrServletContext.xml b/org.argeo.cms/OSGI-INF/jcrServletContext.xml
deleted file mode 100644 (file)
index dd83c1d..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.servlet.PrivateWwwAuthServletContext"/>
-   <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/OSGI-INF/nodeDeployment.xml b/org.argeo.cms/OSGI-INF/nodeDeployment.xml
deleted file mode 100644 (file)
index 065f09a..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="CMS Deployment">
-   <implementation class="org.argeo.cms.internal.kernel.CmsDeployment"/>
-   <service>
-      <provide interface="org.argeo.api.NodeDeployment"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/nodeInstance.xml b/org.argeo.cms/OSGI-INF/nodeInstance.xml
deleted file mode 100644 (file)
index cb70231..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="CMS Instance">
-   <implementation class="org.argeo.cms.internal.kernel.CmsInstance"/>
-   <service>
-      <provide interface="org.argeo.api.NodeInstance"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/nodeState.xml b/org.argeo.cms/OSGI-INF/nodeState.xml
deleted file mode 100644 (file)
index 565c442..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" activate="initServices" name="CMS State">
-   <implementation class="org.argeo.cms.internal.kernel.CmsState"/>
-   <service>
-      <provide interface="org.argeo.api.NodeState"/>
-   </service>
-</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml b/org.argeo.cms/OSGI-INF/nodeUserAdmin.xml
new file mode 100644 (file)
index 0000000..eb048d9
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="Node User Admin">
+   <implementation class="org.argeo.cms.internal.osgi.NodeUserAdmin"/>
+   <property name="service.pid" type="String" value="org.argeo.api.userAdmin"/>
+   <service>
+      <provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
+   </service>
+   <reference bind="setTransactionManager" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkControl" name="WorkControl" policy="static"/>
+   <reference bind="setUserTransaction" cardinality="1..1" interface="org.argeo.osgi.transaction.WorkTransaction" name="WorkTransaction" policy="static"/>
+   <reference cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+</scr:component>
diff --git a/org.argeo.cms/OSGI-INF/pkgServlet.xml b/org.argeo.cms/OSGI-INF/pkgServlet.xml
deleted file mode 100644 (file)
index 30e5231..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.internal.http.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/OSGI-INF/pkgServletContext.xml b/org.argeo.cms/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/OSGI-INF/simpleTransactionManager.xml b/org.argeo.cms/OSGI-INF/simpleTransactionManager.xml
new file mode 100644 (file)
index 0000000..c331aa4
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Simple Transaction Manager">
+   <implementation class="org.argeo.osgi.transaction.SimpleTransactionManager"/>
+   <service>
+      <provide interface="org.argeo.osgi.transaction.WorkControl"/>
+      <provide interface="org.argeo.osgi.transaction.WorkTransaction"/>
+   </service>
+</scr:component>
index 5af544206695c76471f8b2b2095479196d02da00..e75adcdc3b49afd28a98c73422944d81afce5736 100644 (file)
@@ -1,25 +1,18 @@
-Bundle-Activator: org.argeo.cms.internal.kernel.Activator
+Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
-Import-Package: javax.jcr.security,\
-org.h2;resolution:=optional,\
-org.postgresql;resolution:=optional,\
-org.apache.jackrabbit.webdav.server,\
-org.apache.jackrabbit.webdav.jcr,\
+Import-Package: \
+org.argeo.osgi.transaction, \
 org.apache.commons.httpclient.cookie;resolution:=optional,\
 !com.sun.security.jgss,\
-org.osgi.framework.namespace;version=0.0.0,\
 org.osgi.*;version=0.0.0,\
-org.osgi.service.http.whiteboard,\
 *
 
 Service-Component:\
+OSGI-INF/cmsState.xml,\
+OSGI-INF/simpleTransactionManager.xml,\
+OSGI-INF/nodeUserAdmin.xml,\
 OSGI-INF/cmsUserManager.xml,\
-OSGI-INF/pkgServletContext.xml,\
-OSGI-INF/pkgServlet.xml,\
-OSGI-INF/jcrServletContext.xml,\
-OSGI-INF/dataServletContext.xml,\
-OSGI-INF/filesServletContext.xml,\
-OSGI-INF/filesServlet.xml
+OSGI-INF/deployConfig.xml,\
+OSGI-INF/cmsDeployment.xml,\
+OSGI-INF/cmsContext.xml,\
 
-Provide-Capability: cms.datamodel;name=argeo;cnd=/org/argeo/cms/argeo.cnd;abstract=true,\
-osgi.service;objectClass="javax.jcr.Repository"
index 55f4d9ae5d9d05975c4df6aac6326e89f49e2d63..db86d95a50099a1a879c9b09b154cee6fd72227b 100644 (file)
@@ -2,9 +2,11 @@ output.. = bin/
 bin.includes = META-INF/,\
                .,\
                bin/,\
-               OSGI-INF/
-source.. = src/,\
-           ext/test/
-additional.bundles = org.apache.jackrabbit.data,\
-                     org.argeo.jcr,\
-                     org.junit
+               OSGI-INF/,\
+               OSGI-INF/simpleTransactionManager.xml,\
+               OSGI-INF/cmsState.xml,\
+               OSGI-INF/nodeUserAdmin.xml,\
+               OSGI-INF/deployConfig.xml,\
+               OSGI-INF/cmsDeployment.xml,\
+               OSGI-INF/cmsContext.xml
+source.. = src/
diff --git a/org.argeo.cms/ext/test/org/argeo/cms/security/PasswordBasedEncryptionTest.java b/org.argeo.cms/ext/test/org/argeo/cms/security/PasswordBasedEncryptionTest.java
deleted file mode 100644 (file)
index 62f9793..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.argeo.cms.security;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.util.Base64;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.PBEParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.util.PasswordEncryption;
-
-public class PasswordBasedEncryptionTest extends TestCase {
-       private final static Log log = LogFactory.getLog(PasswordBasedEncryptionTest.class);
-
-       public void testEncryptDecrypt() {
-               final String password = "test long password since they are safer";
-               PasswordEncryption pbeEnc = new PasswordEncryption(password.toCharArray());
-               String message = "Hello World!";
-               log.info("Password:\t'" + password + "'");
-               log.info("Message:\t'" + message + "'");
-               byte[] encrypted = pbeEnc.encryptString(message);
-               log.info("Encrypted:\t'" + Base64.getEncoder().encode(encrypted) + "'");
-               PasswordEncryption pbeDec = new PasswordEncryption(password.toCharArray());
-               InputStream in = null;
-               in = new ByteArrayInputStream(encrypted);
-               String decrypted = pbeDec.decryptAsString(in);
-               log.info("Decrypted:\t'" + decrypted + "'");
-               IOUtils.closeQuietly(in);
-               assertEquals(message, decrypted);
-       }
-
-       public void testPBEWithMD5AndDES() throws Exception {
-               String password = "test";
-               String message = "Hello World!";
-
-               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee,
-                               (byte) 0x99 };
-
-               int count = 1024;
-
-               String cipherAlgorithm = "PBEWithMD5AndDES";
-               String secretKeyAlgorithm = "PBEWithMD5AndDES";
-               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm);
-               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
-               PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
-               SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
-               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
-               ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
-               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
-               dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
-
-               byte[] encrypted = ecipher.doFinal(message.getBytes());
-               byte[] decrypted = dcipher.doFinal(encrypted);
-               assertEquals(message, new String(decrypted));
-
-       }
-
-       public void testPBEWithSHA1AndAES() throws Exception {
-               String password = "test";
-               String message = "Hello World!";
-
-               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee,
-                               (byte) 0x99 };
-               byte[] iv = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee,
-                               (byte) 0x99, (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c, (byte) 0x7e, (byte) 0xc8, (byte) 0xee,
-                               (byte) 0x99 };
-
-               int count = 1024;
-               // int keyLength = 256;
-               int keyLength = 128;
-
-               String cipherAlgorithm = "AES/CBC/PKCS5Padding";
-               String secretKeyAlgorithm = "PBKDF2WithHmacSHA1";
-               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm);
-               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count, keyLength);
-               SecretKey tmp = keyFac.generateSecret(pbeKeySpec);
-               SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
-               ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
-
-               // decrypt
-               keyFac = SecretKeyFactory.getInstance(secretKeyAlgorithm);
-               pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt, count, keyLength);
-               tmp = keyFac.generateSecret(pbeKeySpec);
-               secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-               // AlgorithmParameters params = ecipher.getParameters();
-               // byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
-               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
-               dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
-
-               byte[] encrypted = ecipher.doFinal(message.getBytes());
-               byte[] decrypted = dcipher.doFinal(encrypted);
-               assertEquals(message, new String(decrypted));
-
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher);
-               cipherOut.write(message.getBytes());
-               IOUtils.closeQuietly(cipherOut);
-               byte[] enc = out.toByteArray();
-
-               ByteArrayInputStream in = new ByteArrayInputStream(enc);
-               CipherInputStream cipherIn = new CipherInputStream(in, dcipher);
-               ByteArrayOutputStream dec = new ByteArrayOutputStream();
-               IOUtils.copy(cipherIn, dec);
-               assertEquals(message, new String(dec.toByteArray()));
-       }
-}
diff --git a/org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java b/org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java
deleted file mode 100644 (file)
index f090ac4..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cms.security;
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.Authenticator;
-import java.net.PasswordAuthentication;
-import java.net.URL;
-
-public class RunHttpSpnego {
-
-    static final String kuser = "mbaudier@ARGEO.EU"; // your account name
-    static final String kpass = "test"; // retrieve password for your account 
-
-    static class MyAuthenticator extends Authenticator {
-        public PasswordAuthentication getPasswordAuthentication() {
-            // I haven't checked getRequestingScheme() here, since for NTLM
-            // and Negotiate, the usrname and password are all the same.
-            System.err.println("Feeding username and password for " + getRequestingScheme());
-            return (new PasswordAuthentication(kuser, kpass.toCharArray()));
-        }
-    }
-
-    public static void main(String[] args) throws Exception {
-        Authenticator.setDefault(new MyAuthenticator());
-        URL url = new URL(args[0]);
-        InputStream ins = url.openConnection().getInputStream();
-        BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
-        String str;
-        while((str = reader.readLine()) != null)
-            System.out.println(str);
-    }
-}
diff --git a/org.argeo.cms/ext/test/org/argeo/cms/tabular/JcrTabularTest.java b/org.argeo.cms/ext/test/org/argeo/cms/tabular/JcrTabularTest.java
deleted file mode 100644 (file)
index 8da6148..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.argeo.cms.tabular;
-
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.argeo.api.tabular.TabularColumn;
-import org.argeo.api.tabular.TabularRow;
-import org.argeo.api.tabular.TabularRowIterator;
-import org.argeo.api.tabular.TabularWriter;
-import org.argeo.cms.ArgeoTypes;
-import org.argeo.jackrabbit.unit.AbstractJackrabbitTestCase;
-
-public class JcrTabularTest extends AbstractJackrabbitTestCase {
-       private final static Log log = LogFactory.getLog(JcrTabularTest.class);
-
-       public void testWriteReadCsv() throws Exception {
-               // session().setNamespacePrefix("argeo", ArgeoNames.ARGEO_NAMESPACE);
-               InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/api/ldap.cnd"));
-               CndImporter.registerNodeTypes(reader, session());
-               reader.close();
-               reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/cms/argeo.cnd"));
-               CndImporter.registerNodeTypes(reader, session());
-               reader.close();
-//             reader = new InputStreamReader(getClass().getResourceAsStream("/org/argeo/cms/cms.cnd"));
-//             CndImporter.registerNodeTypes(reader, session());
-//             reader.close();
-
-               // write
-               Integer columnCount = 15;
-               Long rowCount = 1000l;
-               String stringValue = "test, \ntest";
-
-               List<TabularColumn> header = new ArrayList<TabularColumn>();
-               for (int i = 0; i < columnCount; i++) {
-                       header.add(new TabularColumn("col" + i, PropertyType.STRING));
-               }
-               Node tableNode = session().getRootNode().addNode("table", ArgeoTypes.ARGEO_TABLE);
-               TabularWriter writer = new JcrTabularWriter(tableNode, header, ArgeoTypes.ARGEO_CSV);
-               for (int i = 0; i < rowCount; i++) {
-                       List<Object> objs = new ArrayList<Object>();
-                       for (int j = 0; j < columnCount; j++) {
-                               objs.add(stringValue);
-                       }
-                       writer.appendRow(objs.toArray());
-               }
-               writer.close();
-               session().save();
-
-               if (log.isDebugEnabled())
-                       log.debug("Wrote tabular content " + rowCount + " rows, " + columnCount + " columns");
-               // read
-               TabularRowIterator rowIt = new JcrTabularRowIterator(tableNode);
-               Long count = 0l;
-               while (rowIt.hasNext()) {
-                       TabularRow tr = rowIt.next();
-                       assertEquals(header.size(), tr.size());
-                       count++;
-               }
-               assertEquals(rowCount, count);
-               if (log.isDebugEnabled())
-                       log.debug("Read tabular content " + rowCount + " rows, " + columnCount + " columns");
-       }
-}
diff --git a/org.argeo.cms/pom.xml b/org.argeo.cms/pom.xml
deleted file mode 100644 (file)
index 45aaa91..0000000
+++ /dev/null
@@ -1,40 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.cms</artifactId>
-       <name>CMS</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.jcr</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.enterprise</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.core</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.maintenance</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java b/org.argeo.cms/src/org/argeo/cms/AbstractCmsApp.java
new file mode 100644 (file)
index 0000000..a7049a4
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.cms;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAppListener;
+import org.argeo.api.cms.CmsTheme;
+
+/** Base class for {@link CmsApp}s. */
+public abstract class AbstractCmsApp implements CmsApp {
+       private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
+
+       private List<CmsAppListener> cmsAppListeners = new ArrayList<>();
+
+       protected abstract String getThemeId(String uiName);
+
+       @Override
+       public CmsTheme getTheme(String uiName) {
+               String themeId = getThemeId(uiName);
+               if (themeId == null)
+                       return null;
+               if (!themes.containsKey(themeId))
+                       throw new IllegalArgumentException("Theme " + themeId + " not found.");
+               return themes.get(themeId);
+       }
+
+       @Override
+       public boolean allThemesAvailable() {
+               boolean themeMissing = false;
+               uiNames: for (String uiName : getUiNames()) {
+                       String themeId = getThemeId(uiName);
+                       if ("org.eclipse.rap.rwt.theme.Default".equals(themeId))
+                               continue uiNames;
+                       if (!themes.containsKey(themeId)) {
+                               themeMissing = true;
+                               break uiNames;
+                       }
+               }
+               return !themeMissing;
+       }
+
+       public void addTheme(CmsTheme theme, Map<String, String> properties) {
+               themes.put(theme.getThemeId(), theme);
+               if (allThemesAvailable())
+                       for (CmsAppListener listener : cmsAppListeners)
+                               listener.themingUpdated();
+       }
+
+       public void removeTheme(CmsTheme theme, Map<String, String> properties) {
+               themes.remove(theme.getThemeId());
+       }
+
+       @Override
+       public void addCmsAppListener(CmsAppListener listener) {
+               cmsAppListeners.add(listener);
+               if (allThemesAvailable())
+                       listener.themingUpdated();
+       }
+
+       @Override
+       public void removeCmsAppListener(CmsAppListener listener) {
+               cmsAppListeners.remove(listener);
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogListener.java
new file mode 100644 (file)
index 0000000..a01858a
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms;
+
+/** Framework agnostic interface for log notifications */
+@Deprecated
+public interface ArgeoLogListener {
+       /**
+        * Appends a log
+        * 
+        * @param username
+        *            authentified user, null for anonymous
+        * @param level
+        *            INFO, DEBUG, WARN, etc. (logging framework specific)
+        * @param category
+        *            hierarchy (logging framework specific)
+        * @param thread
+        *            name of the thread which logged this message
+        * @param msg
+        *            any object as long as its toString() method returns the
+        *            message
+        * @param exception
+        *            exception in log4j ThrowableStrRep format
+        */
+       public void appendLog(String username, Long timestamp, String level,
+                       String category, String thread, Object msg, String[] exception);
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java b/org.argeo.cms/src/org/argeo/cms/ArgeoLogger.java
new file mode 100644 (file)
index 0000000..71c5039
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms;
+
+/**
+ * Logging framework agnostic identifying a logging service, to which one can
+ * register
+ */
+@Deprecated
+public interface ArgeoLogger {
+       /**
+        * Register for events by threads with the same authentication (or all
+        * threads if admin)
+        */
+       public void register(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents);
+
+       /**
+        * For admin use only: register for all users
+        * 
+        * @param listener
+        *            the log listener
+        * @param numberOfPreviousEvents
+        *            the number of previous events to notify
+        * @param everything
+        *            if true even anonymous is logged
+        */
+       public void registerForAll(ArgeoLogListener listener,
+                       Integer numberOfPreviousEvents, boolean everything);
+
+       public void unregister(ArgeoLogListener listener);
+
+       public void unregisterForAll(ArgeoLogListener listener);
+}
index 1dafe7c810636308b7b9482b69a35aea44f51807..cd76d65ef05618372e1e11c2b3159a2a9b412832 100644 (file)
@@ -4,14 +4,11 @@ import java.time.ZonedDateTime;
 import java.util.List;
 import java.util.Set;
 
-import javax.jcr.Node;
 import javax.security.auth.Subject;
-import javax.transaction.UserTransaction;
 
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
 
 /**
  * Provide method interfaces to manage user concepts without accessing directly
@@ -80,11 +77,11 @@ public interface CmsUserManager {
 
        void expireAuthTokens(Subject subject);
 
-       User createUserFromPerson(Node person);
+//     User createUserFromPerson(Node person);
 
-       @Deprecated
-       public UserAdmin getUserAdmin();
-
-       @Deprecated
-       public UserTransaction getUserTransaction();
+//     @Deprecated
+//     public UserAdmin getUserAdmin();
+//
+//     @Deprecated
+//     public UserTransaction getUserTransaction();
 }
\ No newline at end of file
index ec8c97f79e58a7551db78c479da1e542530bac2c..f02e6a2b4439f57a3e5a23f69aaecbda3dcf1c46 100644 (file)
@@ -8,15 +8,14 @@ import java.util.ResourceBundle;
 
 import javax.security.auth.Subject;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.auth.CurrentUser;
 
 /** Utilities simplifying the development of localization enums. */
 public class LocaleUtils {
        final static String DEFAULT_OSGI_l10N_BUNDLE = "/OSGI-INF/l10n/bundle";
 
-       private final static Log log = LogFactory.getLog(LocaleUtils.class);
+       private final static CmsLog log = CmsLog.getLog(LocaleUtils.class);
 
        private final static ThreadLocal<Locale> threadLocale = new ThreadLocal<>();
 
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentRepository.java
new file mode 100644 (file)
index 0000000..9cd8bd2
--- /dev/null
@@ -0,0 +1,139 @@
+package org.argeo.cms.acr;
+
+import java.security.AccessController;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+
+public class CmsContentRepository implements ProvidedRepository {
+       private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+
+       // TODO synchronize ?
+       private NavigableMap<String, String> prefixes = new TreeMap<>();
+
+       public CmsContentRepository() {
+               prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
+               prefixes.put("basic", CrName.CR_NAMESPACE_URI);
+               prefixes.put("owner", CrName.CR_NAMESPACE_URI);
+               prefixes.put("posix", CrName.CR_NAMESPACE_URI);
+       }
+
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+
+       /*
+        * REPOSITORY
+        */
+
+       @Override
+       public ContentSession get() {
+               return get(CmsContextImpl.getCmsContext().getDefaultLocale());
+       }
+
+       @Override
+       public ContentSession get(Locale locale) {
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               return new CmsContentSession(subject, locale);
+       }
+
+       public void addProvider(String base, ContentProvider provider) {
+               partitions.put(base, provider);
+       }
+
+       public void registerPrefix(String prefix, String namespaceURI) {
+               String registeredUri = prefixes.get(prefix);
+               if (registeredUri == null) {
+                       prefixes.put(prefix, namespaceURI);
+                       return;
+               }
+               if (!registeredUri.equals(namespaceURI))
+                       throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
+               // do nothing if same namespace is already registered
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+
+       /*
+        * SESSION
+        */
+
+       class CmsContentSession implements ProvidedSession {
+               private Subject subject;
+               private Locale locale;
+
+               public CmsContentSession(Subject subject, Locale locale) {
+                       this.subject = subject;
+                       this.locale = locale;
+               }
+
+               @Override
+               public Content get(String path) {
+                       Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
+                       String mountPath = entry.getKey();
+                       ContentProvider provider = entry.getValue();
+                       String relativePath = path.substring(mountPath.length());
+                       return provider.get(CmsContentSession.this, mountPath, relativePath);
+               }
+
+               @Override
+               public Subject getSubject() {
+                       return subject;
+               }
+
+               @Override
+               public Locale getLocale() {
+                       return locale;
+               }
+
+               @Override
+               public ProvidedRepository getRepository() {
+                       return CmsContentRepository.this;
+               }
+
+               /*
+                * NAMESPACE CONTEXT
+                */
+
+               @Override
+               public String findNamespace(String prefix) {
+                       return prefixes.get(prefix);
+               }
+
+               @Override
+               public Set<String> findPrefixes(String namespaceURI) {
+                       Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
+                                       .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
+
+                       return res;
+               }
+
+               @Override
+               public String findPrefix(String namespaceURI) {
+                       if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
+                               return CrName.CR_DEFAULT_PREFIX;
+                       return ProvidedSession.super.findPrefix(namespaceURI);
+               }
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsUuidFactory.java
new file mode 100644 (file)
index 0000000..7270c08
--- /dev/null
@@ -0,0 +1,86 @@
+package org.argeo.cms.acr;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.BitSet;
+import java.util.Enumeration;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.uuid.ConcurrentUuidFactory;
+import org.argeo.api.uuid.UuidBinaryUtils;
+
+public class CmsUuidFactory extends ConcurrentUuidFactory {
+       private final static CmsLog log = CmsLog.getLog(CmsUuidFactory.class);
+
+       public CmsUuidFactory(byte[] nodeId) {
+               super(0, nodeId);
+               assert createTimeUUID().node() == BitSet.valueOf(toNodeIdBytes(nodeId, 0)).toLongArray()[0];
+       }
+
+       public CmsUuidFactory() {
+               this(getIpBytes());
+       }
+
+       /** Returns an SHA1 digest of one of the IP addresses. */
+       protected static byte[] getIpBytes() {
+               Enumeration<NetworkInterface> netInterfaces = null;
+               try {
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+               } catch (SocketException e) {
+                       throw new IllegalStateException(e);
+               }
+               if (netInterfaces == null)
+                       throw new IllegalStateException("No interfaces");
+
+               InetAddress selectedIpv6 = null;
+               InetAddress selectedIpv4 = null;
+               netInterfaces: while (netInterfaces.hasMoreElements()) {
+                       NetworkInterface netInterface = netInterfaces.nextElement();
+                       byte[] hardwareAddress = null;
+                       try {
+                               hardwareAddress = netInterface.getHardwareAddress();
+                               if (hardwareAddress != null) {
+                                       // first IPv6
+                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                               InetAddress ip = addr.getAddress();
+                                               if (ip instanceof Inet6Address) {
+                                                       Inet6Address ipv6 = (Inet6Address) ip;
+                                                       if (ipv6.isAnyLocalAddress() || ipv6.isLinkLocalAddress() || ipv6.isLoopbackAddress())
+                                                               continue addr;
+                                                       selectedIpv6 = ipv6;
+                                                       break netInterfaces;
+                                               }
+
+                                       }
+                                       // then IPv4
+                                       addr: for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                               InetAddress ip = addr.getAddress();
+                                               if (ip instanceof Inet4Address) {
+                                                       Inet4Address ipv4 = (Inet4Address) ip;
+                                                       if (ipv4.isAnyLocalAddress() || ipv4.isLinkLocalAddress() || ipv4.isLoopbackAddress())
+                                                               continue addr;
+                                                       selectedIpv4 = ipv4;
+                                               }
+
+                                       }
+                               }
+                       } catch (SocketException e) {
+                               throw new IllegalStateException(e);
+                       }
+               }
+               InetAddress selectedIp = selectedIpv6 != null ? selectedIpv6 : selectedIpv4;
+               if (selectedIp == null)
+                       throw new IllegalStateException("No IP address found");
+               byte[] digest = sha1(selectedIp.getAddress());
+               log.info("Use IP " + selectedIp + " hashed as " + UuidBinaryUtils.toHexString(digest) + " as node id");
+               byte[] nodeId = toNodeIdBytes(digest, 0);
+               // marks that this is not based on MAC address
+               forceToNoMacAddress(nodeId, 0);
+               return nodeId;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContent.java
new file mode 100644 (file)
index 0000000..bfcd011
--- /dev/null
@@ -0,0 +1,219 @@
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.util.FsUtils;
+
+public class FsContent extends AbstractContent implements ProvidedContent {
+       private final static String USER_ = "user:";
+
+       private static final Map<QName, String> BASIC_KEYS;
+       private static final Map<QName, String> POSIX_KEYS;
+       static {
+               BASIC_KEYS = new HashMap<>();
+               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
+               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
+               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
+               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+
+               POSIX_KEYS = new HashMap<>(BASIC_KEYS);
+               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
+               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
+               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+       }
+
+       private final ProvidedSession session;
+       private final FsContentProvider provider;
+       private final Path path;
+       private final boolean isRoot;
+       private final QName name;
+
+       protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
+               this.session = session;
+               this.provider = contentProvider;
+               this.path = path;
+               this.isRoot = contentProvider.isRoot(path);
+               // TODO check file names with ':' ?
+               if (isRoot)
+                       this.name = CrName.ROOT.get();
+               else
+                       this.name = session.parsePrefixedName(path.getFileName().toString());
+       }
+
+       protected FsContent(FsContent context, Path path) {
+               this(context.getSession(), context.getProvider(), path);
+       }
+
+       private boolean isPosix() {
+               return path.getFileSystem().supportedFileAttributeViews().contains("posix");
+       }
+
+       @Override
+       public QName getName() {
+               return name;
+       }
+
+       /*
+        * ATTRIBUTES
+        */
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               Object value;
+               try {
+                       // We need to add user: when accessing via Files#getAttribute
+                       value = Files.getAttribute(path, toFsAttributeKey(key));
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
+               }
+               A res = null;
+               if (value instanceof FileTime) {
+                       if (clss.isAssignableFrom(FileTime.class))
+                               res = (A) value;
+                       Instant instant = ((FileTime) value).toInstant();
+                       if (Object.class.isAssignableFrom(clss)) {// plain object requested
+                               res = (A) instant;
+                       }
+                       // TODO perform trivial file conversion to other formats
+               }
+               if (value instanceof byte[]) {
+                       res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
+               }
+               if (res == null)
+                       try {
+                               res = (A) value;
+                       } catch (ClassCastException e) {
+                               return Optional.empty();
+                       }
+               return Optional.of(res);
+       }
+
+       @Override
+       protected Iterable<QName> keys() {
+               Set<QName> result = new HashSet<>(isPosix() ? POSIX_KEYS.keySet() : BASIC_KEYS.keySet());
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               if (udfav != null) {
+                       try {
+                               for (String name : udfav.list()) {
+                                       result.add(session.parsePrefixedName(name));
+                               }
+                       } catch (IOException e) {
+                               throw new ContentResourceException("Cannot list attributes for " + path, e);
+                       }
+               }
+               return result;
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               try {
+                       udfav.delete(session.toPrefixedName(key));
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+               ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+               try {
+                       int size = udfav.write(session.toPrefixedName(key), bb);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+               }
+               return previous;
+       }
+
+       protected String toFsAttributeKey(QName key) {
+               if (POSIX_KEYS.containsKey(key))
+                       return POSIX_KEYS.get(key);
+               else
+                       return USER_ + session.toPrefixedName(key);
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+       @Override
+       public Iterator<Content> iterator() {
+               if (Files.isDirectory(path)) {
+                       try {
+                               return Files.list(path).map((p) -> (Content) new FsContent(this, p)).iterator();
+                       } catch (IOException e) {
+                               throw new ContentResourceException("Cannot list " + path, e);
+                       }
+               } else {
+                       return Collections.emptyIterator();
+               }
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               try {
+                       Path newPath = path.resolve(session.toPrefixedName(name));
+                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
+                               Files.createDirectory(newPath);
+                       else
+                               Files.createFile(newPath);
+
+//             for(ContentClass clss:classes) {
+//                     Files.setAttribute(newPath, name, newPath, null)
+//             }
+                       return new FsContent(this, newPath);
+               } catch (IOException e) {
+                       throw new ContentResourceException("Cannot create new content", e);
+               }
+       }
+
+       @Override
+       public void remove() {
+               FsUtils.delete(path);
+       }
+
+       @Override
+       public Content getParent() {
+               if (isRoot)
+                       return null;// TODO deal with mounts
+               return new FsContent(this, path.getParent());
+       }
+
+       /*
+        * ACCESSORS
+        */
+       @Override
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       @Override
+       public FsContentProvider getProvider() {
+               return provider;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/fs/FsContentProvider.java
new file mode 100644 (file)
index 0000000..99ed3a8
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+
+public class FsContentProvider implements ContentProvider {
+       private final Path rootPath;
+
+       public FsContentProvider(Path rootPath) {
+               super();
+               this.rootPath = rootPath;
+       }
+
+       boolean isRoot(Path path) {
+               try {
+                       return Files.isSameFile(rootPath, path);
+               } catch (IOException e) {
+                       throw new ContentResourceException(e);
+               }
+       }
+
+       @Override
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               return new FsContent(session, this, rootPath.resolve(relativePath));
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContent.java
new file mode 100644 (file)
index 0000000..626f582
--- /dev/null
@@ -0,0 +1,221 @@
+package org.argeo.cms.acr.xml;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.spi.AbstractContent;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+
+public class DomContent extends AbstractContent implements ProvidedContent {
+
+       private final ProvidedSession session;
+       private final DomContentProvider provider;
+       private final Element element;
+
+//     private String text = null;
+       private Boolean hasText = null;
+
+       public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
+               this.session = session;
+               this.provider = contentProvider;
+               this.element = element;
+       }
+
+       public DomContent(DomContent context, Element element) {
+               this(context.getSession(), context.getProvider(), element);
+       }
+
+       @Override
+       public QName getName() {
+               return toQName(this.element);
+       }
+
+       protected QName toQName(Node node) {
+               String prefix = node.getPrefix();
+               if (prefix == null) {
+                       String namespaceURI = node.getNamespaceURI();
+                       if (namespaceURI == null)
+                               namespaceURI = node.getOwnerDocument().lookupNamespaceURI(null);
+                       if (namespaceURI == null) {
+                               return toQName(node, node.getLocalName());
+                       } else {
+                               String contextPrefix = session.getPrefix(namespaceURI);
+                               if (contextPrefix == null)
+                                       throw new IllegalStateException("Namespace " + namespaceURI + " is unbound");
+                               return toQName(node, namespaceURI, node.getLocalName(), session);
+                       }
+               } else {
+                       String namespaceURI = node.getNamespaceURI();
+                       if (namespaceURI == null)
+                               namespaceURI = node.getOwnerDocument().lookupNamespaceURI(prefix);
+                       if (namespaceURI == null) {
+                               namespaceURI = session.getNamespaceURI(prefix);
+                               if (XMLConstants.NULL_NS_URI.equals(namespaceURI))
+                                       throw new IllegalStateException("Prefix " + prefix + " is unbound");
+                               // TODO bind the prefix in the document?
+                       }
+                       return toQName(node, namespaceURI, node.getLocalName(), session);
+               }
+       }
+
+       protected QName toQName(Node source, String namespaceURI, String localName, NamespaceContext namespaceContext) {
+               return new ContentName(namespaceURI, localName, session);
+       }
+
+       protected QName toQName(Node source, String localName) {
+               return new ContentName(localName);
+       }
+       /*
+        * ATTRIBUTES OPERATIONS
+        */
+
+       @Override
+       public Iterable<QName> keys() {
+               // TODO implement an iterator?
+               Set<QName> result = new HashSet<>();
+               NamedNodeMap attributes = element.getAttributes();
+               for (int i = 0; i < attributes.getLength(); i++) {
+                       Attr attr = (Attr) attributes.item(i);
+                       QName key = toQName(attr);
+                       result.add(key);
+               }
+               return result;
+       }
+
+       @Override
+       public <A> Optional<A> get(QName key, Class<A> clss) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               if (element.hasAttributeNS(namespaceUriOrNull, key.getLocalPart())) {
+                       String value = element.getAttributeNS(namespaceUriOrNull, key.getLocalPart());
+                       if (clss.isAssignableFrom(String.class))
+                               return Optional.of((A) value);
+                       else
+                               return Optional.empty();
+               } else
+                       return null;
+       }
+
+       @Override
+       public Object put(QName key, Object value) {
+               Object previous = get(key);
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.setAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart(),
+                               value.toString());
+               return previous;
+       }
+       
+       
+
+       @Override
+       public boolean hasText() {
+//             return element instanceof Text;
+               if (hasText != null)
+                       return hasText;
+               NodeList nodeList = element.getChildNodes();
+               if (nodeList.getLength() > 1) {
+                       hasText = false;
+                       return hasText;
+               }
+               nodes: for (int i = 0; i < nodeList.getLength(); i++) {
+                       Node node = nodeList.item(i);
+                       if (node instanceof Text) {
+                               Text text = (Text) node;
+                               if (!text.isElementContentWhitespace()) {
+                                       hasText = true;
+                                       break nodes;
+                               }
+                       }
+               }
+               if (hasText == null)
+                       hasText = false;
+               return hasText;
+//             if (text != null)
+//                     return true;
+//             text = element.getTextContent();
+//             return text != null;
+       }
+
+       @Override
+       public String getText() {
+               if (hasText())
+                       return element.getTextContent();
+               else
+                       return null;
+       }
+
+       /*
+        * CONTENT OPERATIONS
+        */
+
+       @Override
+       public Iterator<Content> iterator() {
+               NodeList nodeList = element.getChildNodes();
+               return new ElementIterator(session, provider, nodeList);
+       }
+
+       @Override
+       public Content getParent() {
+               Node parent = element.getParentNode();
+               if (parent == null)
+                       return null;
+               if (!(parent instanceof Element))
+                       throw new IllegalStateException("Parent is not an element");
+               return new DomContent(this, (Element) parent);
+       }
+
+       @Override
+       public Content add(QName name, QName... classes) {
+               // TODO consider classes
+               Document document = this.element.getOwnerDocument();
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI()) ? null
+                               : name.getNamespaceURI();
+               Element child = document.createElementNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? name.getLocalPart() : name.getPrefix() + ":" + name.getLocalPart());
+               element.appendChild(child);
+               return new DomContent(this, child);
+       }
+
+       @Override
+       public void remove() {
+               // TODO make it more robust
+               element.getParentNode().removeChild(element);
+
+       }
+
+       @Override
+       protected void removeAttr(QName key) {
+               String namespaceUriOrNull = XMLConstants.NULL_NS_URI.equals(key.getNamespaceURI()) ? null
+                               : key.getNamespaceURI();
+               element.removeAttributeNS(namespaceUriOrNull,
+                               namespaceUriOrNull == null ? key.getLocalPart() : key.getPrefix() + ":" + key.getLocalPart());
+
+       }
+
+       public ProvidedSession getSession() {
+               return session;
+       }
+
+       public DomContentProvider getProvider() {
+               return provider;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/DomContentProvider.java
new file mode 100644 (file)
index 0000000..c5fde8d
--- /dev/null
@@ -0,0 +1,99 @@
+package org.argeo.cms.acr.xml;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class DomContentProvider implements ContentProvider, NamespaceContext {
+       private Document document;
+
+       // XPath
+       // TODO centralise in some executor?
+       private final ThreadLocal<XPath> xPath;
+
+       public DomContentProvider(Document document) {
+               this.document = document;
+               this.document.normalizeDocument();
+               XPathFactory xPathFactory = XPathFactory.newInstance();
+               xPath = new ThreadLocal<>() {
+
+                       @Override
+                       protected XPath initialValue() {
+                               // TODO set the document as namespace context?
+                               XPath res= xPathFactory.newXPath();
+                               res.setNamespaceContext(DomContentProvider.this);
+                               return res;
+                       }
+               };
+       }
+
+//     @Override
+//     public Content get() {
+//             return new DomContent(this, document.getDocumentElement());
+//     }
+
+//     public Element createElement(String name) {
+//             return document.createElementNS(null, name);
+//
+//     }
+
+       @Override
+       public Content get(ProvidedSession session, String mountPath, String relativePath) {
+               if ("".equals(relativePath))
+                       return new DomContent(session, this, document.getDocumentElement());
+               if (relativePath.startsWith("/"))
+                       throw new IllegalArgumentException("Relative path cannot start with /");
+
+               String xPathExpression = '/' + relativePath;
+               if ("/".equals(mountPath))
+                       xPathExpression = "/cr:root" + xPathExpression;
+               try {
+                       NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
+                       if (nodes.getLength() > 1)
+                               throw new IllegalArgumentException(
+                                               "Multiple content found for " + relativePath + " under " + mountPath);
+                       if (nodes.getLength() == 0)
+                               throw new ContentNotFoundException("Path " + relativePath + " under " + mountPath + " was not found");
+                       Element element = (Element) nodes.item(0);
+                       return new DomContent(session, this, element);
+               } catch (XPathExpressionException e) {
+                       throw new IllegalArgumentException("XPath expression " + xPathExpression + " cannot be evaluated", e);
+               }
+       }
+
+       /*
+        * NAMESPACE CONTEXT
+        */
+       @Override
+       public String getNamespaceURI(String prefix) {
+               return document.lookupNamespaceURI(prefix);
+       }
+
+       @Override
+       public String getPrefix(String namespaceURI) {
+               return document.lookupPrefix(namespaceURI);
+       }
+
+       @Override
+       public Iterator<String> getPrefixes(String namespaceURI) {
+               List<String> res = new ArrayList<>();
+               res.add(getPrefix(namespaceURI));
+               return Collections.unmodifiableList(res).iterator();
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java b/org.argeo.cms/src/org/argeo/cms/acr/xml/ElementIterator.java
new file mode 100644 (file)
index 0000000..3b07081
--- /dev/null
@@ -0,0 +1,57 @@
+package org.argeo.cms.acr.xml;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+class ElementIterator implements Iterator<Content> {
+       private final ProvidedSession session;
+       private final DomContentProvider provider;
+       private final NodeList nodeList;
+
+       private int currentIndex;
+       private final int length;
+       private Element nextElement = null;
+
+       public ElementIterator(ProvidedSession session, DomContentProvider provider, NodeList nodeList) {
+               this.session = session;
+               this.provider = provider;
+               this.nodeList = nodeList;
+
+               this.length = nodeList.getLength();
+               this.currentIndex = 0;
+               this.nextElement = findNext();
+       }
+
+       private Element findNext() {
+               while (currentIndex < length) {
+                       Node node = nodeList.item(currentIndex);
+                       if (node instanceof Element) {
+                               return (Element) node;
+                       }
+                       currentIndex++;
+               }
+               return null;
+       }
+
+       @Override
+       public boolean hasNext() {
+               return nextElement != null;
+       }
+
+       @Override
+       public Content next() {
+               if (nextElement == null)
+                       throw new NoSuchElementException();
+               DomContent result = new DomContent(session, provider, nextElement);
+               currentIndex++;
+               nextElement = findNext();
+               return result;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/argeo.cnd b/org.argeo.cms/src/org/argeo/cms/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)
index 1d24be7ade914c45b4e862ca9fe310844c83cb3c..de3a3027012b973b0ffbfff3b57d3e187b30035b 100644 (file)
@@ -7,10 +7,8 @@ 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.servlet.http.HttpServletRequest;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import org.argeo.api.cms.CmsLog;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.service.useradmin.Authorization;
@@ -18,7 +16,7 @@ import org.osgi.service.useradmin.UserAdmin;
 
 /** Anonymous CMS user */
 public class AnonymousLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(AnonymousLoginModule.class);
+       private final static CmsLog log = CmsLog.getLog(AnonymousLoginModule.class);
 
        private Subject subject;
        private Map<String, Object> sharedState = null;
@@ -49,7 +47,7 @@ public class AnonymousLoginModule implements LoginModule {
        public boolean commit() throws LoginException {
                UserAdmin userAdmin = bc.getService(bc.getServiceReference(UserAdmin.class));
                Authorization authorization = userAdmin.getAuthorization(null);
-               HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+               RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
                Locale locale = Locale.getDefault();
                if (request != null)
                        locale = request.getLocale();
index 4c09650d4b0546bdc5c6220a23f23de99903cf95..72676611e4e43f631429c52e8b31a6a0893e4529 100644 (file)
@@ -10,16 +10,16 @@ import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
 
-import org.argeo.api.NodeConstants;
-import org.argeo.api.security.AnonymousPrincipal;
-import org.argeo.api.security.DataAdminPrincipal;
-import org.argeo.api.security.NodeSecurityUtils;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
+import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.cms.AnonymousPrincipal;
+import org.argeo.api.cms.CmsConstants;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.http.WebCmsSessionImpl;
+import org.argeo.cms.security.NodeSecurityUtils;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
@@ -122,16 +122,16 @@ class CmsAuthUtils {
        }
 
        @SuppressWarnings("unused")
-       synchronized static void registerSessionAuthorization(HttpServletRequest request, Subject subject,
+       synchronized static void registerSessionAuthorization(RemoteAuthRequest request, Subject subject,
                        Authorization authorization, Locale locale) {
                // synchronized in order to avoid multiple registrations
                // TODO move it to a service in order to avoid static synchronization
                if (request != null) {
-                       HttpSession httpSession = request.getSession(false);
+                       RemoteAuthSession httpSession = request.getSession();
                        assert httpSession != null;
                        String httpSessId = httpSession.getId();
                        boolean anonymous = authorization.getName() == null;
-                       String remoteUser = !anonymous ? authorization.getName() : NodeConstants.ROLE_ANONYMOUS;
+                       String remoteUser = !anonymous ? authorization.getName() : CmsConstants.ROLE_ANONYMOUS;
                        request.setAttribute(HttpContext.REMOTE_USER, remoteUser);
                        request.setAttribute(HttpContext.AUTHORIZATION, authorization);
 
@@ -186,7 +186,7 @@ class CmsAuthUtils {
                }
        }
 
-       public static CmsSession cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
+       public static CmsSessionImpl cmsSessionFromHttpSession(BundleContext bc, String httpSessionId) {
                Authorization authorization = null;
                Collection<ServiceReference<CmsSession>> sr;
                try {
@@ -195,9 +195,9 @@ class CmsAuthUtils {
                } catch (InvalidSyntaxException e) {
                        throw new IllegalArgumentException("Cannot get CMS session for id " + httpSessionId, e);
                }
-               CmsSession cmsSession;
+               CmsSessionImpl cmsSession;
                if (sr.size() == 1) {
-                       cmsSession = bc.getService(sr.iterator().next());
+                       cmsSession = (CmsSessionImpl) bc.getService(sr.iterator().next());
 //                     locale = cmsSession.getLocale();
                        authorization = cmsSession.getAuthorization();
                        if (authorization.getName() == null)
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsSession.java
deleted file mode 100644 (file)
index a0ea6a6..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.time.ZonedDateTime;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.UUID;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.naming.LdapAttrs;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.useradmin.Authorization;
-
-/** An authenticated user session. */
-public interface CmsSession {
-       final static String USER_DN = LdapAttrs.DN;
-       final static String SESSION_UUID = LdapAttrs.entryUUID.name();
-       final static String SESSION_LOCAL_ID = LdapAttrs.uniqueIdentifier.name();
-
-       UUID getUuid();
-
-       String getUserRole();
-       
-       LdapName getUserDn();
-
-       String getLocalId();
-
-       Authorization getAuthorization();
-
-       boolean isAnonymous();
-
-       ZonedDateTime getCreationTime();
-
-       ZonedDateTime getEnd();
-
-       Locale getLocale();
-
-       boolean isValid();
-
-       void registerView(String uid, Object view);
-
-       /** @return The {@link CmsSession} for this {@link Subject} or null. */
-       static CmsSession getCmsSession(BundleContext bc, Subject subject) {
-               if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
-                       return null;
-               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
-               String uuid = cmsSessionId.getUuid().toString();
-               Collection<ServiceReference<CmsSession>> sr;
-               try {
-                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
-               } catch (InvalidSyntaxException e) {
-                       throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
-               }
-               ServiceReference<CmsSession> cmsSessionRef;
-               if (sr.size() == 1) {
-                       cmsSessionRef = sr.iterator().next();
-                       return bc.getService(cmsSessionRef);
-               } else if (sr.size() == 0) {
-                       return null;
-               } else
-                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsSessionId.java
deleted file mode 100644 (file)
index cc435ae..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.util.UUID;
-
-import javax.security.auth.Subject;
-
-/**
- * The ID of a {@link CmsSession}, which must be available in the private
- * credentials of an authenticated {@link Subject}.
- */
-public class CmsSessionId {
-       private final UUID uuid;
-
-       public CmsSessionId(UUID value) {
-               if (value == null)
-                       throw new IllegalArgumentException("Value cannot be null");
-               this.uuid = value;
-       }
-
-       public UUID getUuid() {
-               return uuid;
-       }
-
-       @Override
-       public int hashCode() {
-               return uuid.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               return obj instanceof CmsSessionId && ((CmsSessionId) obj).getUuid().equals(uuid);
-       }
-
-       @Override
-       public String toString() {
-               return "Node Session " + uuid;
-       }
-
-}
index eaaf41ab72a458ee7acacb0778315922c9c52ba5..85a4824646bec37124c8cefc79627240165a435a 100644 (file)
@@ -13,10 +13,12 @@ import java.util.UUID;
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
 
-import org.argeo.api.NodeConstants;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.osgi.service.useradmin.Authorization;
 
 /**
@@ -84,7 +86,7 @@ public final class CurrentUser {
                if (subject == null)
                        throw new IllegalArgumentException("Subject cannot be null");
                if (subject.getPrincipals(X500Principal.class).size() != 1)
-                       return NodeConstants.ROLE_ANONYMOUS;
+                       return CmsConstants.ROLE_ANONYMOUS;
                Principal principal = subject.getPrincipals(X500Principal.class).iterator().next();
                return principal.getName();
        }
@@ -105,7 +107,7 @@ public final class CurrentUser {
        public final static Locale locale(Subject subject) {
                Set<Locale> locales = subject.getPublicCredentials(Locale.class);
                if (locales.isEmpty()) {
-                       Locale defaultLocale = Activator.getNodeState().getDefaultLocale();
+                       Locale defaultLocale = CmsContextImpl.getCmsContext().getDefaultLocale();
                        return defaultLocale;
                } else
                        return locales.iterator().next();
@@ -116,7 +118,7 @@ public final class CurrentUser {
                if (subject == null)
                        return true;
                String username = getUsername(subject);
-               return username == null || username.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS);
+               return username == null || username.equalsIgnoreCase(CmsConstants.ROLE_ANONYMOUS);
        }
 
        public CmsSession getCmsSession() {
@@ -150,7 +152,7 @@ public final class CurrentUser {
                else
                        return false;
                CmsSessionImpl cmsSession = CmsSessionImpl.getByUuid(nodeSessionId.toString());
-               
+
                // FIXME logout all views
                // TODO check why it is sometimes null
                if (cmsSession != null)
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/DataAdminLoginModule.java
new file mode 100644 (file)
index 0000000..ea1046b
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.auth;
+
+import java.util.Map;
+
+import javax.security.auth.AuthPermission;
+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.argeo.api.cms.DataAdminPrincipal;
+
+/**
+ * Log-in a system process as data admin. Protection is via
+ * {@link AuthPermission} on this login module, so if it can be accessed it will
+ * always succeed.
+ */
+public class DataAdminLoginModule 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 {
+               subject.getPrincipals().add(new DataAdminPrincipal());
+               return true;
+       }
+
+       @Override
+       public boolean abort() throws LoginException {
+               return true;
+       }
+
+       @Override
+       public boolean logout() throws LoginException {
+               subject.getPrincipals().removeAll(subject.getPrincipals(DataAdminPrincipal.class));
+               return true;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java
deleted file mode 100644 (file)
index 920f04e..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.cms.auth;
-
-import javax.security.auth.callback.Callback;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-/** Retrieves credentials from an HTTP request. */
-public class HttpRequestCallback implements Callback {
-       private HttpServletRequest request;
-       private HttpServletResponse response;
-       private HttpSession httpSession;
-
-       public HttpServletRequest getRequest() {
-               return request;
-       }
-
-       public void setRequest(HttpServletRequest request) {
-               this.request = request;
-       }
-
-       public HttpServletResponse getResponse() {
-               return response;
-       }
-
-       public void setResponse(HttpServletResponse response) {
-               this.response = response;
-       }
-
-       public HttpSession getHttpSession() {
-               return httpSession;
-       }
-
-       public void setHttpSession(HttpSession httpSession) {
-               this.httpSession = httpSession;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallbackHandler.java
deleted file mode 100644 (file)
index df971e6..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.io.IOException;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.LanguageCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-/**
- * Callback handler populating {@link HttpRequestCallback}s with the provided
- * {@link HttpServletRequest}, and ignoring any other callback.
- */
-public class HttpRequestCallbackHandler implements CallbackHandler {
-       final private HttpServletRequest request;
-       final private HttpServletResponse response;
-       final private HttpSession httpSession;
-
-       public HttpRequestCallbackHandler(HttpServletRequest request, HttpServletResponse response) {
-               this.request = request;
-               this.httpSession = request.getSession(false);
-               this.response = response;
-       }
-
-       public HttpRequestCallbackHandler(HttpSession httpSession) {
-               this.httpSession = httpSession;
-               this.request = null;
-               this.response = null;
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               for (Callback callback : callbacks)
-                       if (callback instanceof HttpRequestCallback) {
-                               ((HttpRequestCallback) callback).setRequest(request);
-                               ((HttpRequestCallback) callback).setResponse(response);
-                               ((HttpRequestCallback) callback).setHttpSession(httpSession);
-                       } else if (callback instanceof LanguageCallback) {
-                               ((LanguageCallback) callback).setLocale(request.getLocale());
-                       }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java
deleted file mode 100644 (file)
index 8cb524f..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.io.IOException;
-import java.security.cert.X509Certificate;
-import java.util.Base64;
-import java.util.Locale;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.login.LoginException;
-import javax.security.auth.spi.LoginModule;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.internal.kernel.Activator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
-import org.osgi.service.useradmin.Authorization;
-
-/** Use the HTTP session as the basis for authentication. */
-public class HttpSessionLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(HttpSessionLoginModule.class);
-
-       private Subject subject = null;
-       private CallbackHandler callbackHandler = null;
-       private Map<String, Object> sharedState = null;
-
-       private HttpServletRequest request = null;
-       private HttpServletResponse response = null;
-
-       private BundleContext bc;
-
-       private Authorization authorization;
-       private Locale locale;
-
-       @SuppressWarnings("unchecked")
-       @Override
-       public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
-                       Map<String, ?> options) {
-               bc = FrameworkUtil.getBundle(HttpSessionLoginModule.class).getBundleContext();
-               assert bc != null;
-               this.subject = subject;
-               this.callbackHandler = callbackHandler;
-               this.sharedState = (Map<String, Object>) sharedState;
-       }
-
-       @Override
-       public boolean login() throws LoginException {
-               if (callbackHandler == null)
-                       return false;
-               HttpRequestCallback httpCallback = new HttpRequestCallback();
-               try {
-                       callbackHandler.handle(new Callback[] { httpCallback });
-               } catch (IOException e) {
-                       throw new LoginException("Cannot handle http callback: " + e.getMessage());
-               } catch (UnsupportedCallbackException e) {
-                       return false;
-               }
-               request = httpCallback.getRequest();
-               if (request == null) {
-                       HttpSession httpSession = httpCallback.getHttpSession();
-                       if (httpSession == null)
-                               return false;
-                       // TODO factorize with below
-                       String httpSessionId = httpSession.getId();
-                       if (log.isTraceEnabled())
-                               log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
-                       CmsSession cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
-                       if (cmsSession != null) {
-                               authorization = cmsSession.getAuthorization();
-                               locale = cmsSession.getLocale();
-                               if (log.isTraceEnabled())
-                                       log.trace("Retrieved authorization from " + cmsSession);
-                       }
-               } else {
-                       authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
-                       if (authorization == null) {// search by session ID
-                               HttpSession httpSession = request.getSession(false);
-                               if (httpSession == null) {
-                                       // TODO make sure this is always safe
-                                       if (log.isTraceEnabled())
-                                               log.trace("Create http session");
-                                       httpSession = request.getSession(true);
-                               }
-                               String httpSessionId = httpSession.getId();
-                               if (log.isTraceEnabled())
-                                       log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
-                               CmsSession cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
-                               if (cmsSession != null) {
-                                       authorization = cmsSession.getAuthorization();
-                                       locale = cmsSession.getLocale();
-                                       if (log.isTraceEnabled())
-                                               log.trace("Retrieved authorization from " + cmsSession);
-                               }
-                       }
-                       sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
-                       extractHttpAuth(request);
-                       extractClientCertificate(request);
-               }
-               if (authorization == null) {
-                       if (log.isTraceEnabled())
-                               log.trace("HTTP login: " + false);
-                       return false;
-               } else {
-                       if (log.isTraceEnabled())
-                               log.trace("HTTP login: " + true);
-                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
-                       return true;
-               }
-       }
-
-       @Override
-       public boolean commit() throws LoginException {
-               byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
-               if (outToken != null) {
-                       response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
-                                       "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
-               }
-
-               if (authorization != null) {
-                       // Locale locale = request.getLocale();
-                       if (locale == null && request != null)
-                               locale = request.getLocale();
-                       if (locale != null)
-                               subject.getPublicCredentials().add(locale);
-                       CmsAuthUtils.addAuthorization(subject, authorization);
-                       CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
-                       cleanUp();
-                       return true;
-               } else {
-                       cleanUp();
-                       return false;
-               }
-       }
-
-       @Override
-       public boolean abort() throws LoginException {
-               cleanUp();
-               return false;
-       }
-
-       private void cleanUp() {
-               authorization = null;
-               request = null;
-       }
-
-       @Override
-       public boolean logout() throws LoginException {
-               cleanUp();
-               return true;
-       }
-
-       private void extractHttpAuth(final HttpServletRequest httpRequest) {
-               String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
-               extractHttpAuth(authHeader);
-       }
-
-       private void extractHttpAuth(String authHeader) {
-               if (authHeader != null) {
-                       StringTokenizer st = new StringTokenizer(authHeader);
-                       if (st.hasMoreTokens()) {
-                               String basic = st.nextToken();
-                               if (basic.equalsIgnoreCase("Basic")) {
-                                       try {
-                                               // TODO manipulate char[]
-                                               Base64.Decoder decoder = Base64.getDecoder();
-                                               String credentials = new String(decoder.decode(st.nextToken()), "UTF-8");
-                                               // log.debug("Credentials: " + credentials);
-                                               int p = credentials.indexOf(":");
-                                               if (p != -1) {
-                                                       final String login = credentials.substring(0, p).trim();
-                                                       final char[] password = credentials.substring(p + 1).trim().toCharArray();
-                                                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
-                                                       sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
-                                               } else {
-                                                       throw new IllegalStateException("Invalid authentication token");
-                                               }
-                                       } catch (Exception e) {
-                                               throw new IllegalStateException("Couldn't retrieve authentication", e);
-                                       }
-                               } else if (basic.equalsIgnoreCase("Negotiate")) {
-                                       String spnegoToken = st.nextToken();
-                                       Base64.Decoder decoder = Base64.getDecoder();
-                                       byte[] authToken = decoder.decode(spnegoToken);
-                                       sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
-                               }
-                       }
-               }
-
-               // auth token
-               // String mail = request.getParameter(LdapAttrs.mail.name());
-               // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
-               // if (authPassword != null) {
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
-               // if (mail != null)
-               // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
-               // }
-       }
-
-       private void extractClientCertificate(HttpServletRequest req) {
-               X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
-               if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
-                       String certDn = certs[0].getSubjectX500Principal().getName();
-                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
-                       sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
-                       if (log.isDebugEnabled())
-                               log.debug("Client certificate " + certDn + " verified by servlet container");
-               } // Reverse proxy verified the client certificate
-               String clientDnHttpHeader = Activator.getHttpProxySslHeader();
-               if (clientDnHttpHeader != null) {
-                       String certDn = req.getHeader(clientDnHttpHeader);
-                       // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
-                       // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
-                       if (certDn != null && !certDn.trim().equals("(null)")) {
-                               sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
-                               sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
-                               if (log.isDebugEnabled())
-                                       log.debug("Client certificate " + certDn + " verified by reverse proxy");
-                       }
-               }
-       }
-
-}
index ec6b5a30c196b77b7bde1ed4c10cb0f7e8568d31..097e588e43737e83f09e6633a5309dcc8bdf25c4 100644 (file)
@@ -9,16 +9,14 @@ import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
-import javax.servlet.http.HttpServletRequest;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.internal.kernel.Activator;
-import org.argeo.ident.IdentClient;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.ident.IdentClient;
+import org.argeo.cms.internal.runtime.CmsStateImpl;
 
 /** Use an ident service to identify. */
 public class IdentLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(IdentLoginModule.class);
+       private final static CmsLog log = CmsLog.getLog(IdentLoginModule.class);
 
        private CallbackHandler callbackHandler = null;
        private Map<String, Object> sharedState = null;
@@ -35,7 +33,7 @@ public class IdentLoginModule implements LoginModule {
        public boolean login() throws LoginException {
                if (callbackHandler == null)
                        return false;
-               HttpRequestCallback httpCallback = new HttpRequestCallback();
+               RemoteAuthCallback httpCallback = new RemoteAuthCallback();
                try {
                        callbackHandler.handle(new Callback[] { httpCallback });
                } catch (IOException e) {
@@ -43,10 +41,10 @@ public class IdentLoginModule implements LoginModule {
                } catch (UnsupportedCallbackException e) {
                        return false;
                }
-               HttpServletRequest request = httpCallback.getRequest();
+               RemoteAuthRequest request = httpCallback.getRequest();
                if (request == null)
                        return false;
-               IdentClient identClient = Activator.getIdentClient(request.getRemoteAddr());
+               IdentClient identClient = CmsStateImpl.getIdentClient(request.getRemoteAddr());
                if (identClient == null)
                        return false;
                String identUsername;
index 920c76b650cbf6529d2d6bd31ae0793a0cb6de1d..c49a59ef1dcc5ea69033618702e0caff7d8c2271 100644 (file)
@@ -15,7 +15,7 @@ import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.argeo.api.security.PBEKeySpecCallback;
+import org.argeo.cms.security.PBEKeySpecCallback;
 import org.argeo.util.PasswordEncryption;
 
 /** Adds a secret key to the private credentials */
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallback.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallback.java
new file mode 100644 (file)
index 0000000..d19bac8
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.cms.auth;
+
+import javax.security.auth.callback.Callback;
+
+/** Retrieves credentials from an HTTP request. */
+public class RemoteAuthCallback implements Callback {
+       private RemoteAuthRequest request;
+       private RemoteAuthResponse response;
+       private RemoteAuthSession httpSession;
+
+       public RemoteAuthRequest getRequest() {
+               return request;
+       }
+
+       public void setRequest(RemoteAuthRequest request) {
+               this.request = request;
+       }
+
+       public RemoteAuthResponse getResponse() {
+               return response;
+       }
+
+       public void setResponse(RemoteAuthResponse response) {
+               this.response = response;
+       }
+
+       public RemoteAuthSession getHttpSession() {
+               return httpSession;
+       }
+
+       public void setHttpSession(RemoteAuthSession httpSession) {
+               this.httpSession = httpSession;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthCallbackHandler.java
new file mode 100644 (file)
index 0000000..68d126b
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo.cms.auth;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.LanguageCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+/**
+ * Callback handler populating {@link RemoteAuthCallback}s with the provided
+ * {@link HttpServletRequest}, and ignoring any other callback.
+ */
+public class RemoteAuthCallbackHandler implements CallbackHandler {
+       final private RemoteAuthRequest request;
+       final private RemoteAuthResponse response;
+       final private RemoteAuthSession httpSession;
+
+       public RemoteAuthCallbackHandler(RemoteAuthRequest request, RemoteAuthResponse response) {
+               this.request = request;
+               this.httpSession = request.getSession();
+               this.response = response;
+       }
+
+       public RemoteAuthCallbackHandler(RemoteAuthSession httpSession) {
+               this.httpSession = httpSession;
+               this.request = null;
+               this.response = null;
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks)
+                       if (callback instanceof RemoteAuthCallback) {
+                               ((RemoteAuthCallback) callback).setRequest(request);
+                               ((RemoteAuthCallback) callback).setResponse(response);
+                               ((RemoteAuthCallback) callback).setHttpSession(httpSession);
+                       } else if (callback instanceof LanguageCallback) {
+                               ((LanguageCallback) callback).setLocale(request.getLocale());
+                       }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java
new file mode 100644 (file)
index 0000000..2d1d14b
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.auth;
+
+import java.util.Locale;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthRequest {
+       RemoteAuthSession getSession();
+
+       RemoteAuthSession createSession();
+
+       Locale getLocale();
+
+       Object getAttribute(String key);
+
+       void setAttribute(String key, Object object);
+
+       String getHeader(String key);
+
+       String getRemoteAddr();
+
+       int getLocalPort();
+
+       int getRemotePort();
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthResponse.java
new file mode 100644 (file)
index 0000000..f91b6c5
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.cms.auth;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthResponse {
+       void setHeader(String keys, String value);
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthSession.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthSession.java
new file mode 100644 (file)
index 0000000..6708285
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.cms.auth;
+
+/** Transitional interface to decouple from the Servlet API. */
+public interface RemoteAuthSession {
+       boolean isValid();
+
+       String getId();
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
new file mode 100644 (file)
index 0000000..d51997d
--- /dev/null
@@ -0,0 +1,64 @@
+package org.argeo.cms.auth;
+
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.function.Supplier;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+/** Remote authentication utilities. */
+public class RemoteAuthUtils {
+       static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
+       private static BundleContext bundleContext = FrameworkUtil.getBundle(RemoteAuthUtils.class).getBundleContext();
+
+       /**
+        * Execute this supplier, using the CMS class loader as context classloader.
+        * Useful to log in to JCR.
+        */
+       public final static <T> T doAs(Supplier<T> supplier, RemoteAuthRequest req) {
+               ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(RemoteAuthUtils.class.getClassLoader());
+               try {
+                       return Subject.doAs(
+                                       Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
+                                       new PrivilegedAction<T>() {
+
+                                               @Override
+                                               public T run() {
+                                                       return supplier.get();
+                                               }
+
+                                       });
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentContextCl);
+               }
+       }
+
+       public final static void configureRequestSecurity(RemoteAuthRequest req) {
+               if (req.getAttribute(AccessControlContext.class.getName()) != null)
+                       throw new IllegalStateException("Request already authenticated.");
+               AccessControlContext acc = AccessController.getContext();
+               req.setAttribute(REMOTE_USER, CurrentUser.getUsername());
+               req.setAttribute(AccessControlContext.class.getName(), acc);
+       }
+
+       public final static void clearRequestSecurity(RemoteAuthRequest req) {
+               if (req.getAttribute(AccessControlContext.class.getName()) == null)
+                       throw new IllegalStateException("Cannot clear non-authenticated request.");
+               req.setAttribute(REMOTE_USER, null);
+               req.setAttribute(AccessControlContext.class.getName(), null);
+       }
+
+       public static CmsSession getCmsSession(RemoteAuthRequest req) {
+               Subject subject = Subject
+                               .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
+               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, subject);
+               return cmsSession;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
new file mode 100644 (file)
index 0000000..962094d
--- /dev/null
@@ -0,0 +1,229 @@
+package org.argeo.cms.auth;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.auth.CmsSessionImpl;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.useradmin.Authorization;
+
+/** Use the HTTP session as the basis for authentication. */
+public class RemoteSessionLoginModule implements LoginModule {
+       private final static CmsLog log = CmsLog.getLog(RemoteSessionLoginModule.class);
+
+       private Subject subject = null;
+       private CallbackHandler callbackHandler = null;
+       private Map<String, Object> sharedState = null;
+
+       private RemoteAuthRequest request = null;
+       private RemoteAuthResponse response = null;
+
+       private BundleContext bc;
+
+       private Authorization authorization;
+       private Locale locale;
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+                       Map<String, ?> options) {
+               bc = FrameworkUtil.getBundle(RemoteSessionLoginModule.class).getBundleContext();
+               assert bc != null;
+               this.subject = subject;
+               this.callbackHandler = callbackHandler;
+               this.sharedState = (Map<String, Object>) sharedState;
+       }
+
+       @Override
+       public boolean login() throws LoginException {
+               if (callbackHandler == null)
+                       return false;
+               RemoteAuthCallback httpCallback = new RemoteAuthCallback();
+               try {
+                       callbackHandler.handle(new Callback[] { httpCallback });
+               } catch (IOException e) {
+                       throw new LoginException("Cannot handle http callback: " + e.getMessage());
+               } catch (UnsupportedCallbackException e) {
+                       return false;
+               }
+               request = httpCallback.getRequest();
+               if (request == null) {
+                       RemoteAuthSession httpSession = httpCallback.getHttpSession();
+                       if (httpSession == null)
+                               return false;
+                       // TODO factorize with below
+                       String httpSessionId = httpSession.getId();
+//                     if (log.isTraceEnabled())
+//                             log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
+                       CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
+                       if (cmsSession != null) {
+                               authorization = cmsSession.getAuthorization();
+                               locale = cmsSession.getLocale();
+                               if (log.isTraceEnabled())
+                                       log.trace("Retrieved authorization from " + cmsSession);
+                       }
+               } else {
+                       authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
+                       if (authorization == null) {// search by session ID
+                               RemoteAuthSession httpSession = request.getSession();
+                               if (httpSession == null) {
+                                       // TODO make sure this is always safe
+                                       if (log.isTraceEnabled())
+                                               log.trace("Create http session");
+                                       httpSession = request.createSession();
+                               }
+                               String httpSessionId = httpSession.getId();
+//                             if (log.isTraceEnabled())
+//                                     log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
+                               CmsSessionImpl cmsSession = CmsAuthUtils.cmsSessionFromHttpSession(bc, httpSessionId);
+                               if (cmsSession != null) {
+                                       authorization = cmsSession.getAuthorization();
+                                       locale = cmsSession.getLocale();
+                                       if (log.isTraceEnabled())
+                                               log.trace("Retrieved authorization from " + cmsSession);
+                               }
+                       }
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
+                       extractHttpAuth(request);
+                       extractClientCertificate(request);
+               }
+               if (authorization == null) {
+                       if (log.isTraceEnabled())
+                               log.trace("HTTP login: " + false);
+                       return false;
+               } else {
+                       if (log.isTraceEnabled())
+                               log.trace("HTTP login: " + true);
+                       request.setAttribute(HttpContext.AUTHORIZATION, authorization);
+                       return true;
+               }
+       }
+
+       @Override
+       public boolean commit() throws LoginException {
+               byte[] outToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN);
+               if (outToken != null) {
+                       response.setHeader(CmsAuthUtils.HEADER_WWW_AUTHENTICATE,
+                                       "Negotiate " + java.util.Base64.getEncoder().encodeToString(outToken));
+               }
+
+               if (authorization != null) {
+                       // Locale locale = request.getLocale();
+                       if (locale == null && request != null)
+                               locale = request.getLocale();
+                       if (locale != null)
+                               subject.getPublicCredentials().add(locale);
+                       CmsAuthUtils.addAuthorization(subject, authorization);
+                       CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
+                       cleanUp();
+                       return true;
+               } else {
+                       cleanUp();
+                       return false;
+               }
+       }
+
+       @Override
+       public boolean abort() throws LoginException {
+               cleanUp();
+               return false;
+       }
+
+       private void cleanUp() {
+               authorization = null;
+               request = null;
+       }
+
+       @Override
+       public boolean logout() throws LoginException {
+               cleanUp();
+               return true;
+       }
+
+       private void extractHttpAuth(final RemoteAuthRequest httpRequest) {
+               String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
+               extractHttpAuth(authHeader);
+       }
+
+       private void extractHttpAuth(String authHeader) {
+               if (authHeader != null) {
+                       StringTokenizer st = new StringTokenizer(authHeader);
+                       if (st.hasMoreTokens()) {
+                               String basic = st.nextToken();
+                               if (basic.equalsIgnoreCase("Basic")) {
+                                       try {
+                                               // TODO manipulate char[]
+                                               Base64.Decoder decoder = Base64.getDecoder();
+                                               String credentials = new String(decoder.decode(st.nextToken()), "UTF-8");
+                                               // log.debug("Credentials: " + credentials);
+                                               int p = credentials.indexOf(":");
+                                               if (p != -1) {
+                                                       final String login = credentials.substring(0, p).trim();
+                                                       final char[] password = credentials.substring(p + 1).trim().toCharArray();
+                                                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
+                                                       sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
+                                               } else {
+                                                       throw new IllegalStateException("Invalid authentication token");
+                                               }
+                                       } catch (Exception e) {
+                                               throw new IllegalStateException("Couldn't retrieve authentication", e);
+                                       }
+                               } else if (basic.equalsIgnoreCase("Negotiate")) {
+                                       String spnegoToken = st.nextToken();
+                                       Base64.Decoder decoder = Base64.getDecoder();
+                                       byte[] authToken = decoder.decode(spnegoToken);
+                                       sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
+                               }
+                       }
+               }
+
+               // auth token
+               // String mail = request.getParameter(LdapAttrs.mail.name());
+               // String authPassword = request.getParameter(LdapAttrs.authPassword.name());
+               // if (authPassword != null) {
+               // sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, authPassword);
+               // if (mail != null)
+               // sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, mail);
+               // }
+       }
+
+       private void extractClientCertificate(RemoteAuthRequest req) {
+               X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+               if (null != certs && certs.length > 0) {// Servlet container verified the client certificate
+                       String certDn = certs[0].getSubjectX500Principal().getName();
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
+                       sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, certs);
+                       if (log.isDebugEnabled())
+                               log.debug("Client certificate " + certDn + " verified by servlet container");
+               } // Reverse proxy verified the client certificate
+               String clientDnHttpHeader = KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
+               if (clientDnHttpHeader != null) {
+                       String certDn = req.getHeader(clientDnHttpHeader);
+                       // TODO retrieve more cf. https://httpd.apache.org/docs/current/mod/mod_ssl.html
+                       // String issuerDn = req.getHeader("SSL_CLIENT_I_DN");
+                       if (certDn != null && !certDn.trim().equals("(null)")) {
+                               sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, certDn);
+                               sharedState.put(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN, "");
+                               if (log.isDebugEnabled())
+                                       log.debug("Client certificate " + certDn + " verified by reverse proxy");
+                       }
+               }
+       }
+
+}
index 240564f9ec894b809c956829e01fc653dc8d0942..08380ac5a227cd165756e9b430cec4e6fd9c5e6d 100644 (file)
@@ -12,18 +12,16 @@ import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 import javax.security.auth.x500.X500Principal;
-import javax.servlet.http.HttpServletRequest;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.naming.LdapAttrs;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.osgi.useradmin.IpaUtils;
 import org.argeo.osgi.useradmin.OsUserUtils;
+import org.argeo.util.naming.LdapAttrs;
 import org.osgi.service.useradmin.Authorization;
 
 /** Login module for when the system is owned by a single user. */
 public class SingleUserLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(SingleUserLoginModule.class);
+       private final static CmsLog log = CmsLog.getLog(SingleUserLoginModule.class);
 
        private Subject subject;
        private Map<String, Object> sharedState = null;
@@ -68,7 +66,7 @@ public class SingleUserLoginModule implements LoginModule {
                        authorizationName = principal.getName();
                }
 
-               HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+               RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
                Locale locale = Locale.getDefault();
                if (request != null)
                        locale = request.getLocale();
index 27de54be35567496e81c14477b3ae3f9b4768eb5..2dbad96d28d592bcb007d7e186ea6223c054f62c 100644 (file)
@@ -8,9 +8,8 @@ import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSCredential;
 import org.ietf.jgss.GSSException;
@@ -19,7 +18,7 @@ import org.ietf.jgss.GSSName;
 
 /** SPNEGO login */
 public class SpnegoLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(SpnegoLoginModule.class);
+       private final static CmsLog log = CmsLog.getLog(SpnegoLoginModule.class);
 
        private Subject subject;
        private Map<String, Object> sharedState = null;
@@ -112,7 +111,7 @@ public class SpnegoLoginModule implements LoginModule {
        private GSSContext checkToken(byte[] authToken) {
                GSSManager manager = GSSManager.getInstance();
                try {
-                       GSSContext gContext = manager.createContext(Activator.getAcceptorCredentials());
+                       GSSContext gContext = manager.createContext(CmsContextImpl.getAcceptorCredentials());
 
                        if (gContext == null) {
                                log.debug("SpnegoUserRealm: failed to establish GSSContext");
@@ -132,4 +131,9 @@ public class SpnegoLoginModule implements LoginModule {
                return null;
 
        }
+
+       @Deprecated
+       public static boolean hasAcceptorCredentials() {
+               return CmsContextImpl.getAcceptorCredentials() != null;
+       }
 }
index 092a06b7778e8bd6ecbcff7b53293ce3494a36b2..738b507e79495a5898f29098f5a0f2c4169bf8a9 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.auth;
 
-import static org.argeo.naming.LdapAttrs.cn;
+import static org.argeo.util.naming.LdapAttrs.cn;
 
 import java.io.IOException;
 import java.security.PrivilegedAction;
@@ -23,17 +23,16 @@ import javax.security.auth.kerberos.KerberosPrincipal;
 import javax.security.auth.login.CredentialNotFoundException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.security.CryptoKeyring;
-import org.argeo.cms.internal.kernel.Activator;
-import org.argeo.naming.LdapAttrs;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.cms.security.CryptoKeyring;
 import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.argeo.osgi.useradmin.IpaUtils;
 import org.argeo.osgi.useradmin.TokenUtils;
+import org.argeo.util.naming.LdapAttrs;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.ServiceReference;
@@ -47,7 +46,7 @@ import org.osgi.service.useradmin.UserAdmin;
  * authentication.
  */
 public class UserAdminLoginModule implements LoginModule {
-       private final static Log log = LogFactory.getLog(UserAdminLoginModule.class);
+       private final static CmsLog log = CmsLog.getLog(UserAdminLoginModule.class);
 
        private Subject subject;
        private CallbackHandler callbackHandler;
@@ -81,7 +80,7 @@ public class UserAdminLoginModule implements LoginModule {
 
        @Override
        public boolean login() throws LoginException {
-               UserAdmin userAdmin = Activator.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
                final String username;
                final char[] password;
                Object certificateChain = null;
@@ -213,7 +212,7 @@ public class UserAdminLoginModule implements LoginModule {
 //             if (singleUser) {
 //                     OsUserUtils.loginAsSystemUser(subject);
 //             }
-               UserAdmin userAdmin = Activator.getUserAdmin();
+               UserAdmin userAdmin = CmsContextImpl.getUserAdmin();
                Authorization authorization;
                if (callbackHandler == null) {// anonymous
                        authorization = userAdmin.getAuthorization(null);
@@ -253,7 +252,7 @@ public class UserAdminLoginModule implements LoginModule {
                }
 
                // Log and monitor new login
-               HttpServletRequest request = (HttpServletRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
+               RemoteAuthRequest request = (RemoteAuthRequest) sharedState.get(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST);
                CmsAuthUtils.addAuthorization(subject, authorization);
 
                // Unlock keyring (underlying login to the JCR repository)
@@ -338,7 +337,7 @@ public class UserAdminLoginModule implements LoginModule {
        }
 
        protected Group searchForToken(UserAdmin userAdmin, String token) {
-               String dn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
+               String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
                Group tokenGroup = (Group) userAdmin.getRole(dn);
                return tokenGroup;
        }
index ad53086f5c206249d4b066fc7f5f90b0e087ab2b..eed38cc3285aa17de5ca6563f556c0f0e21027c3 100644 (file)
@@ -6,8 +6,8 @@ import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
-import org.argeo.api.NodeConstants;
-import org.argeo.naming.LdapAttrs;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.util.naming.LdapAttrs;
 import org.osgi.service.useradmin.Role;
 import org.osgi.service.useradmin.User;
 import org.osgi.service.useradmin.UserAdmin;
@@ -135,9 +135,9 @@ public class UserAdminUtils {
        /** Simply retrieves a display name of the relevant domain */
        public final static String getDomainName(User user) {
                String dn = user.getName();
-               if (dn.endsWith(NodeConstants.ROLES_BASEDN))
+               if (dn.endsWith(CmsConstants.ROLES_BASEDN))
                        return "System roles";
-               if (dn.endsWith(NodeConstants.TOKENS_BASEDN))
+               if (dn.endsWith(CmsConstants.TOKENS_BASEDN))
                        return "Tokens";
                try {
                        // FIXME deal with non-DC
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ident/IdentClient.java b/org.argeo.cms/src/org/argeo/cms/auth/ident/IdentClient.java
new file mode 100644 (file)
index 0000000..c55ec68
--- /dev/null
@@ -0,0 +1,124 @@
+package org.argeo.cms.auth.ident;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * A simple ident client, supporting authd OpenSSL encrypted username.
+ * 
+ * @see RFC 1413 https://tools.ietf.org/html/rfc1413
+ */
+public class IdentClient {
+       public final static int DEFAULT_IDENT_PORT = 113;
+       public final static String AUTHD_PASSPHRASE_PATH = "/etc/ident.key";
+       final static String NO_USER = "NO-USER";
+
+       private final String host;
+       private final int port;
+
+       private OpenSslDecryptor openSslDecryptor = new OpenSslDecryptor();
+       private String identPassphrase = null;
+
+       public IdentClient(String host) {
+               this(host, readPassphrase(AUTHD_PASSPHRASE_PATH), DEFAULT_IDENT_PORT);
+       }
+
+       public IdentClient(String host, Path passPhrasePath) {
+               this(host, readPassphrase(passPhrasePath), DEFAULT_IDENT_PORT);
+       }
+
+       public IdentClient(String host, String identPassphrase) {
+               this(host, identPassphrase, DEFAULT_IDENT_PORT);
+       }
+
+       public IdentClient(String host, String identPassphrase, int port) {
+               this.host = host;
+               this.identPassphrase = identPassphrase;
+               this.port = port;
+       }
+
+       /** @return the username or null if none */
+       public String getUsername(int serverPort, int clientPort) {
+               String answer;
+               try (Socket socket = new Socket(host, port)) {
+                       String msg = clientPort + "," + serverPort + "\n";
+                       OutputStream out = socket.getOutputStream();
+                       out.write(msg.getBytes(StandardCharsets.US_ASCII));
+                       out.flush();
+                       BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+                       answer = reader.readLine();
+               } catch (ConnectException e) {
+                       System.err.println(
+                                       "Ident client is configured but no ident server available on " + host + " (port " + port + ")");
+                       return null;
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot read from ident server on " + host + " (port " + port + ")", e);
+               }
+               StringTokenizer st = new StringTokenizer(answer, " :\n");
+               String username = null;
+               while (st.hasMoreTokens())
+                       username = st.nextToken();
+
+               if (username.equals(NO_USER))
+                       return null;
+
+               if (identPassphrase != null && username.startsWith("[")) {
+                       String encrypted = username.substring(1, username.length() - 1);
+                       username = openSslDecryptor.decryptAuthd(encrypted, identPassphrase).trim();
+               }
+//             System.out.println(username);
+               return username;
+       }
+
+       public void setOpenSslDecryptor(OpenSslDecryptor openSslDecryptor) {
+               this.openSslDecryptor = openSslDecryptor;
+       }
+
+       public static String readPassphrase(String filePath) {
+               return readPassphrase(Paths.get(filePath));
+       }
+
+       /** @return the first line of the file. */
+       public static String readPassphrase(Path path) {
+               if (!isPathAvailable(path))
+                       return null;
+               List<String> lines;
+               try {
+                       lines = Files.readAllLines(path);
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot read " + path, e);
+               }
+               if (lines.size() == 0)
+                       return null;
+               String passphrase = lines.get(0);
+               return passphrase;
+       }
+
+       public static boolean isDefaultAuthdPassphraseFileAvailable() {
+               return isPathAvailable(Paths.get(AUTHD_PASSPHRASE_PATH));
+       }
+
+       public static boolean isPathAvailable(Path path) {
+               if (!Files.exists(path))
+                       return false;
+               if (!Files.isReadable(path))
+                       return false;
+               return true;
+       }
+
+       public static void main(String[] args) {
+               IdentClient identClient = new IdentClient("127.0.0.1", "changeit");
+               String username = identClient.getUsername(7070, 55958);
+               System.out.println(username);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ident/OpenSslDecryptor.java b/org.argeo.cms/src/org/argeo/cms/auth/ident/OpenSslDecryptor.java
new file mode 100644 (file)
index 0000000..ce7bee9
--- /dev/null
@@ -0,0 +1,162 @@
+package org.argeo.cms.auth.ident;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.Base64;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Decrypts OpenSSL encrypted data.
+ * 
+ * From
+ * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
+ * 
+ * See also
+ * https://stackoverflow.com/questions/54171959/badpadding-exception-when-trying-to-decrypt-aes-based-encrypted-text/54173509#54173509
+ * for new default message digest (not yet in CentOS 7 as of July 2019)
+ */
+public class OpenSslDecryptor {
+       private static final int INDEX_KEY = 0;
+       private static final int INDEX_IV = 1;
+       private static final int ITERATIONS = 1;
+
+       private static final int SALT_OFFSET = 8;
+       private static final int SALT_SIZE = 8;
+       private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
+
+       /** In bits. */
+       private final int keySize;
+
+       private Cipher cipher;
+       private MessageDigest messageDigest;
+
+       public OpenSslDecryptor() {
+               /*
+                * Changed to SHA-256 from OpenSSL v1.1.0 (see
+                * https://stackoverflow.com/questions/39637388/encryption-decryption-doesnt-
+                * work-well-between-two-different-openssl-versions)
+                */
+               this(128, "MD5");
+       }
+
+       public OpenSslDecryptor(int keySize, String messageDigest) {
+               this.keySize = keySize;
+               try {
+                       this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+                       this.messageDigest = MessageDigest.getInstance(messageDigest);
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalArgumentException("Cannot initialise decryptor", e);
+               }
+       }
+
+       public String decryptAuthd(String dataBase64, String passphrase) {
+               try {
+                       byte[] headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64);
+
+                       boolean withSalt = true;
+                       byte[] salt = withSalt ? Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
+                                       : null;
+                       byte[] encrypted = withSalt
+                                       ? Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length)
+                                       : headerSaltAndCipherText;
+
+                       final byte[][] keyAndIV = EVP_BytesToKey(keySize / Byte.SIZE, cipher.getBlockSize(), messageDigest, salt,
+                                       passphrase.getBytes(StandardCharsets.US_ASCII), ITERATIONS);
+                       SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
+                       IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
+
+                       cipher.init(Cipher.DECRYPT_MODE, key, iv);
+                       byte[] decrypted = cipher.doFinal(encrypted);
+
+                       String answer = new String(decrypted, StandardCharsets.US_ASCII);
+                       return answer;
+               } catch (BadPaddingException e) {
+                       throw new IllegalStateException("Bad password, algorithm, mode or padding;"
+                                       + " no salt, wrong number of iterations or corrupted ciphertext.", e);
+               } catch (IllegalBlockSizeException e) {
+                       throw new IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.", e);
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       private static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data,
+                       int count) {
+               byte[][] both = new byte[2][];
+               byte[] key = new byte[key_len];
+               int key_ix = 0;
+               byte[] iv = new byte[iv_len];
+               int iv_ix = 0;
+               both[0] = key;
+               both[1] = iv;
+               byte[] md_buf = null;
+               int nkey = key_len;
+               int niv = iv_len;
+               int i = 0;
+               if (data == null) {
+                       return both;
+               }
+               int addmd = 0;
+               for (;;) {
+                       md.reset();
+                       if (addmd++ > 0) {
+                               md.update(md_buf);
+                       }
+                       md.update(data);
+                       if (null != salt) {
+                               md.update(salt, 0, 8);
+                       }
+                       md_buf = md.digest();
+                       for (i = 1; i < count; i++) {
+                               md.reset();
+                               md.update(md_buf);
+                               md_buf = md.digest();
+                       }
+                       i = 0;
+                       if (nkey > 0) {
+                               for (;;) {
+                                       if (nkey == 0)
+                                               break;
+                                       if (i == md_buf.length)
+                                               break;
+                                       key[key_ix++] = md_buf[i];
+                                       nkey--;
+                                       i++;
+                               }
+                       }
+                       if (niv > 0 && i != md_buf.length) {
+                               for (;;) {
+                                       if (niv == 0)
+                                               break;
+                                       if (i == md_buf.length)
+                                               break;
+                                       iv[iv_ix++] = md_buf[i];
+                                       niv--;
+                                       i++;
+                               }
+                       }
+                       if (nkey == 0 && niv == 0) {
+                               break;
+                       }
+               }
+               for (i = 0; i < md_buf.length; i++) {
+                       md_buf[i] = 0;
+               }
+               return both;
+       }
+
+       public static void main(String[] args) {
+               String dataBase64 = args[0];
+               String passphrase = args[1];
+               OpenSslDecryptor decryptor = new OpenSslDecryptor();
+               System.out.println(decryptor.decryptAuthd(dataBase64, passphrase));
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ident/package-info.java b/org.argeo.cms/src/org/argeo/cms/auth/ident/package-info.java
new file mode 100644 (file)
index 0000000..7fbd5c6
--- /dev/null
@@ -0,0 +1,2 @@
+/** Ident authentication protocol support. */
+package org.argeo.cms.auth.ident;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java b/org.argeo.cms/src/org/argeo/cms/cli/ArgeoCli.java
deleted file mode 100644 (file)
index fbac64d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.cms.cli;
-
-import org.apache.commons.cli.Option;
-import org.argeo.cli.CommandsCli;
-import org.argeo.cli.fs.FsCommands;
-import org.argeo.cli.jcr.JcrCommands;
-import org.argeo.cli.posix.PosixCommands;
-
-/** Argeo command line tools. */
-public class ArgeoCli extends CommandsCli {
-
-       public ArgeoCli(String commandName) {
-               super(commandName);
-               // Common options
-               options.addOption(Option.builder("v").hasArg().argName("verbose").desc("verbosity").build());
-               options.addOption(
-                               Option.builder("D").hasArgs().argName("property=value").desc("use value for given property").build());
-
-               addCommandsCli(new PosixCommands("posix"));
-               addCommandsCli(new FsCommands("fs"));
-               addCommandsCli(new JcrCommands("jcr"));
-       }
-
-       @Override
-       public String getDescription() {
-               return "Argeo command line utilities";
-       }
-
-       public static void main(String[] args) {
-               mainImpl(new ArgeoCli("argeo"), args);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/cli/package-info.java b/org.argeo.cms/src/org/argeo/cms/cli/package-info.java
deleted file mode 100644 (file)
index 078da9a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Argeo command line interface. */
-package org.argeo.cms.cli;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/dn.cnd b/org.argeo.cms/src/org/argeo/cms/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/src/org/argeo/cms/fs/CmsFsUtils.java b/org.argeo.cms/src/org/argeo/cms/fs/CmsFsUtils.java
deleted file mode 100644 (file)
index e152c00..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.NodeConstants;
-import org.argeo.jcr.Jcr;
-
-/** Utilities around documents. */
-public class CmsFsUtils {
-       // TODO make it more robust and configurable
-       private static String baseWorkspaceName = NodeConstants.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(NodeConstants.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() {
-       }
-}
index f40c6fffd561d6315239c96109863fa2d9c35495..aa3a6ad17dd3e61fa4bd4dc751c6533a2a1f8b4a 100644 (file)
@@ -4,21 +4,20 @@ import java.io.Serializable;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
 import java.time.ZonedDateTime;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.function.Consumer;
 
 import javax.crypto.SecretKey;
-import javax.jcr.Repository;
-import javax.jcr.Session;
 import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
@@ -26,12 +25,10 @@ import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.x500.X500Principal;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.security.NodeSecurityUtils;
-import org.argeo.cms.auth.CmsSession;
-import org.argeo.jcr.JcrUtils;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.security.NodeSecurityUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -43,7 +40,7 @@ import org.osgi.service.useradmin.Authorization;
 public class CmsSessionImpl implements CmsSession, Serializable {
        private static final long serialVersionUID = 1867719354246307225L;
        private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionImpl.class).getBundleContext();
-       private final static Log log = LogFactory.getLog(CmsSessionImpl.class);
+       private final static CmsLog log = CmsLog.getLog(CmsSessionImpl.class);
 
        // private final Subject initialSubject;
        private transient AccessControlContext accessControlContext;
@@ -59,12 +56,10 @@ public class CmsSessionImpl implements CmsSession, Serializable {
 
        private ServiceRegistration<CmsSession> serviceRegistration;
 
-       private Map<String, Session> dataSessions = new HashMap<>();
-       private Set<String> dataSessionsInUse = new HashSet<>();
-       private Set<Session> additionalDataSessions = new HashSet<>();
-
        private Map<String, Object> views = new HashMap<>();
 
+       private List<Consumer<CmsSession>> onCloseCallbacks = Collections.synchronizedList(new ArrayList<>());
+
        public CmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale, String localSessionId) {
                this.creationTime = ZonedDateTime.now();
                this.locale = locale;
@@ -103,20 +98,16 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                end = ZonedDateTime.now();
                serviceRegistration.unregister();
 
-               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);
+               for (Consumer<CmsSession> onClose : onCloseCallbacks) {
+                       onClose.accept(this);
                }
 
                try {
                        LoginContext lc;
                        if (isAnonymous()) {
-                               lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS, getSubject());
+                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, getSubject());
                        } else {
-                               lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, getSubject());
+                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, getSubject());
                        }
                        lc.logout();
                } catch (LoginException e) {
@@ -127,7 +118,12 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                log.debug("Closed " + this);
        }
 
-       private Subject getSubject() {
+       @Override
+       public void addOnCloseCallback(Consumer<CmsSession> onClose) {
+               onCloseCallbacks.add(onClose);
+       }
+
+       public Subject getSubject() {
                return Subject.getSubject(accessControlContext);
        }
 
@@ -136,78 +132,6 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                return getSubject().getPrivateCredentials(SecretKey.class);
        }
 
-       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 = NodeConstants.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 " + userDn);
-                                       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 " + userDn);
-               }
-               dataSessionsInUse.add(path);
-               return session;
-       }
-
-       private Session login(Repository repository, String workspace) {
-               try {
-                       return Subject.doAs(getSubject(), new PrivilegedExceptionAction<Session>() {
-                               @Override
-                               public Session run() throws Exception {
-                                       return repository.login(workspace);
-                               }
-                       });
-               } catch (PrivilegedActionException e) {
-                       throw new IllegalStateException("Cannot log in " + userDn + " 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 " + userDn);
-               dataSessionsInUse.remove(path);
-               Session registeredSession = dataSessions.get(path);
-               if (session != registeredSession)
-                       log.warn("Data session " + path + " not consistent for " + userDn);
-               if (log.isTraceEnabled())
-                       log.trace("Released data session " + session + " for " + path);
-               notifyAll();
-       }
-
        @Override
        public boolean isValid() {
                return !isClosed();
@@ -222,12 +146,16 @@ public class CmsSessionImpl implements CmsSession, Serializable {
                return getEnd() != null;
        }
 
-       @Override
        public Authorization getAuthorization() {
                checkValid();
                return authorization;
        }
 
+       @Override
+       public String getDisplayName() {
+               return authorization.toString();
+       }
+
        @Override
        public UUID getUuid() {
                return uuid;
index e64a6ad931e59acae2a40bc23d93f01b4e2eb7fc..19136606da492a3f5d6029bc5f526a5907aa03fd 100644 (file)
@@ -1,8 +1,8 @@
 package org.argeo.cms.internal.auth;
 
-import static org.argeo.naming.LdapAttrs.cn;
-import static org.argeo.naming.LdapAttrs.description;
-import static org.argeo.naming.LdapAttrs.owner;
+import static org.argeo.util.naming.LdapAttrs.cn;
+import static org.argeo.util.naming.LdapAttrs.description;
+import static org.argeo.util.naming.LdapAttrs.owner;
 
 import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
@@ -19,26 +19,22 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
-import javax.jcr.Node;
 import javax.naming.InvalidNameException;
 import javax.naming.ldap.LdapName;
 import javax.security.auth.Subject;
-import javax.transaction.Status;
-import javax.transaction.UserTransaction;
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.CmsUserManager;
 import org.argeo.cms.auth.CurrentUser;
 import org.argeo.cms.auth.UserAdminUtils;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.naming.NamingUtils;
-import org.argeo.naming.SharedSecret;
+import org.argeo.osgi.transaction.WorkTransaction;
 import org.argeo.osgi.useradmin.TokenUtils;
 import org.argeo.osgi.useradmin.UserAdminConf;
 import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.NamingUtils;
+import org.argeo.util.naming.SharedSecret;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.service.useradmin.Authorization;
 import org.osgi.service.useradmin.Group;
@@ -59,13 +55,13 @@ import org.osgi.service.useradmin.UserAdmin;
  * </ul>
  */
 public class CmsUserManagerImpl implements CmsUserManager {
-       private final static Log log = LogFactory.getLog(CmsUserManagerImpl.class);
+       private final static CmsLog log = CmsLog.getLog(CmsUserManagerImpl.class);
 
        private UserAdmin userAdmin;
 //     private Map<String, String> serviceProperties;
-       private UserTransaction userTransaction;
+       private WorkTransaction userTransaction;
 
-       private Map<UserDirectory, Hashtable<String, String>> userDirectories = Collections
+       private Map<UserDirectory, Hashtable<String, Object>> userDirectories = Collections
                        .synchronizedMap(new LinkedHashMap<>());
 
        @Override
@@ -155,7 +151,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                List<User> users = new ArrayList<User>();
                for (Role role : roles) {
                        if ((includeUsers && role.getType() == Role.USER || role.getType() == Role.GROUP) && !users.contains(role)
-                                       && (includeSystemRoles || !role.getName().toLowerCase().endsWith(NodeConstants.ROLES_BASEDN))) {
+                                       && (includeSystemRoles || !role.getName().toLowerCase().endsWith(CmsConstants.ROLES_BASEDN))) {
                                if (match(role, filter))
                                        users.add((User) role);
                        }
@@ -237,9 +233,9 @@ public class CmsUserManagerImpl implements CmsUserManager {
 
                        if (onlyWritable && readOnly)
                                continue;
-                       if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN))
+                       if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN))
                                continue;
-                       if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN))
+                       if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN))
                                continue;
                        dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString());
 
@@ -353,7 +349,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                        return tokenStr;
                } catch (Exception e1) {
                        try {
-                               if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
+                               if (!userTransaction.isNoTransactionStatus())
                                        userTransaction.rollback();
                        } catch (Exception e2) {
                                if (log.isTraceEnabled())
@@ -367,7 +363,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
        public void expireAuthToken(String token) {
                try {
                        userTransaction.begin();
-                       String dn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
+                       String dn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
                        Group tokenGroup = (Group) userAdmin.getRole(dn);
                        String ldapDate = NamingUtils.instantToLdapDate(ZonedDateTime.now(ZoneOffset.UTC));
                        tokenGroup.getProperties().put(description.name(), ldapDate);
@@ -376,7 +372,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                                log.debug("Token " + token + " expired.");
                } catch (Exception e1) {
                        try {
-                               if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
+                               if (!userTransaction.isNoTransactionStatus())
                                        userTransaction.rollback();
                        } catch (Exception e2) {
                                if (log.isTraceEnabled())
@@ -388,7 +384,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
 
        @Override
        public void expireAuthTokens(Subject subject) {
-               Set<String> tokens = TokenUtils.tokensUsed(subject, NodeConstants.TOKENS_BASEDN);
+               Set<String> tokens = TokenUtils.tokensUsed(subject, CmsConstants.TOKENS_BASEDN);
                for (String token : tokens)
                        expireAuthToken(token);
        }
@@ -403,7 +399,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                try {
                        userTransaction.begin();
                        User user = (User) userAdmin.getRole(userDn);
-                       String tokenDn = cn + "=" + token + "," + NodeConstants.TOKENS_BASEDN;
+                       String tokenDn = cn + "=" + token + "," + CmsConstants.TOKENS_BASEDN;
                        Group tokenGroup = (Group) userAdmin.createRole(tokenDn, Role.GROUP);
                        if (roles != null)
                                for (String role : roles) {
@@ -411,7 +407,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                                        if (r != null)
                                                tokenGroup.addMember(r);
                                        else {
-                                               if (!role.equals(NodeConstants.ROLE_USER)) {
+                                               if (!role.equals(CmsConstants.ROLE_USER)) {
                                                        throw new IllegalStateException(
                                                                        "Cannot add role " + role + " to token " + token + " for " + userDn);
                                                }
@@ -425,7 +421,7 @@ public class CmsUserManagerImpl implements CmsUserManager {
                        userTransaction.commit();
                } catch (Exception e1) {
                        try {
-                               if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION)
+                               if (!userTransaction.isNoTransactionStatus())
                                        userTransaction.rollback();
                        } catch (Exception e2) {
                                if (log.isTraceEnabled())
@@ -435,44 +431,44 @@ public class CmsUserManagerImpl implements CmsUserManager {
                }
        }
 
-       public User createUserFromPerson(Node person) {
-               String email = JcrUtils.get(person, LdapAttrs.mail.property());
-               String dn = buildDefaultDN(email, Role.USER);
-               User user;
-               try {
-                       userTransaction.begin();
-                       user = (User) userAdmin.createRole(dn, Role.USER);
-                       Dictionary<String, Object> userProperties = user.getProperties();
-                       String name = JcrUtils.get(person, LdapAttrs.displayName.property());
-                       userProperties.put(LdapAttrs.cn.name(), name);
-                       userProperties.put(LdapAttrs.displayName.name(), name);
-                       String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
-                       String surname = JcrUtils.get(person, LdapAttrs.sn.property());
-                       userProperties.put(LdapAttrs.givenName.name(), givenName);
-                       userProperties.put(LdapAttrs.sn.name(), surname);
-                       userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               log.error("Could not roll back", e1);
-                       }
-                       if (e instanceof RuntimeException)
-                               throw (RuntimeException) e;
-                       else
-                               throw new RuntimeException("Cannot create user", e);
-               }
-               return user;
-       }
+//     public User createUserFromPerson(Node person) {
+//             String email = JcrUtils.get(person, LdapAttrs.mail.property());
+//             String dn = buildDefaultDN(email, Role.USER);
+//             User user;
+//             try {
+//                     userTransaction.begin();
+//                     user = (User) userAdmin.createRole(dn, Role.USER);
+//                     Dictionary<String, Object> userProperties = user.getProperties();
+//                     String name = JcrUtils.get(person, LdapAttrs.displayName.property());
+//                     userProperties.put(LdapAttrs.cn.name(), name);
+//                     userProperties.put(LdapAttrs.displayName.name(), name);
+//                     String givenName = JcrUtils.get(person, LdapAttrs.givenName.property());
+//                     String surname = JcrUtils.get(person, LdapAttrs.sn.property());
+//                     userProperties.put(LdapAttrs.givenName.name(), givenName);
+//                     userProperties.put(LdapAttrs.sn.name(), surname);
+//                     userProperties.put(LdapAttrs.mail.name(), email.toLowerCase());
+//                     userTransaction.commit();
+//             } catch (Exception e) {
+//                     try {
+//                             userTransaction.rollback();
+//                     } catch (Exception e1) {
+//                             log.error("Could not roll back", e1);
+//                     }
+//                     if (e instanceof RuntimeException)
+//                             throw (RuntimeException) e;
+//                     else
+//                             throw new RuntimeException("Cannot create user", e);
+//             }
+//             return user;
+//     }
 
        public UserAdmin getUserAdmin() {
                return userAdmin;
        }
 
-       public UserTransaction getUserTransaction() {
-               return userTransaction;
-       }
+//     public UserTransaction getUserTransaction() {
+//             return userTransaction;
+//     }
 
        /* DEPENDENCY INJECTION */
        public void setUserAdmin(UserAdmin userAdmin) {
@@ -480,15 +476,15 @@ public class CmsUserManagerImpl implements CmsUserManager {
 //             this.serviceProperties = serviceProperties;
        }
 
-       public void setUserTransaction(UserTransaction userTransaction) {
+       public void setUserTransaction(WorkTransaction userTransaction) {
                this.userTransaction = userTransaction;
        }
-       
-       public void addUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
+
+       public void addUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
                userDirectories.put(userDirectory, new Hashtable<>(properties));
        }
 
-       public void removeUserDirectory(UserDirectory userDirectory, Map<String, String> properties) {
+       public void removeUserDirectory(UserDirectory userDirectory, Map<String, Object> properties) {
                userDirectories.remove(userDirectory);
        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsRemotingServlet.java
deleted file mode 100644 (file)
index 1bda6c7..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.cms.internal.http;
-
-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.NodeConstants;
-
-/** 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(NodeConstants.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/src/org/argeo/cms/internal/http/CmsSessionProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsSessionProvider.java
deleted file mode 100644 (file)
index f21f724..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.io.Serializable;
-import java.util.LinkedHashMap;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.server.SessionProvider;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-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 Log log = LogFactory.getLog(CmsSessionProvider.class);
-
-       private final String alias;
-
-       private LinkedHashMap<Session, CmsSessionImpl> 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);
-               if (log.isTraceEnabled()) {
-                       log.trace("Get JCR session from " + cmsSession);
-               }
-               if (cmsSession == null)
-                       throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI());
-               Session session = cmsSession.getDataSession(alias, workspace, rep);
-               cmsSessions.put(session, cmsSession);
-               return session;
-       }
-
-       public void releaseSession(Session session) {
-//             JcrUtils.logoutQuietly(session);
-               if (cmsSessions.containsKey(session)) {
-                       CmsSessionImpl cmsSession = cmsSessions.get(session);
-                       cmsSession.releaseDataSession(alias, session);
-               } else {
-                       log.warn("JCR session " + session + " not found in CMS session list. Logging it out...");
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsWebDavServlet.java
deleted file mode 100644 (file)
index d61e5f4..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
-import org.argeo.api.NodeConstants;
-
-/** 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(NodeConstants.CN);
-               if (alias != null)
-                       setSessionProvider(new CmsSessionProvider(alias));
-               else
-                       throw new IllegalArgumentException("Only aliased repositories are supported");
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/DataHttpContext.java
deleted file mode 100644 (file)
index edc6326..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.io.IOException;
-import java.net.URL;
-
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
-
-@Deprecated
-public class DataHttpContext implements HttpContext {
-       private final static Log log = LogFactory.getLog(DataHttpContext.class);
-
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       // FIXME Make it more unique
-       private final String httpAuthRealm;
-       private final boolean forceBasic;
-
-       public DataHttpContext(String httpAuthrealm, boolean forceBasic) {
-               this.httpAuthRealm = httpAuthrealm;
-               this.forceBasic = forceBasic;
-       }
-
-       public DataHttpContext(String httpAuthrealm) {
-               this(httpAuthrealm, false);
-       }
-
-       @Override
-       public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response) throws IOException {
-
-               if (log.isTraceEnabled())
-                       HttpUtils.logRequestHeaders(log, request);
-               LoginContext lc;
-               try {
-                       lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request, response));
-                       lc.login();
-               } catch (LoginException e) {
-                       lc = processUnauthorized(request, response);
-                       if (lc == null)
-                               return false;
-               }
-               return true;
-       }
-
-       @Override
-       public URL getResource(String name) {
-               return bc.getBundle().getResource(name);
-       }
-
-       @Override
-       public String getMimeType(String name) {
-               return null;
-       }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               // anonymous
-               try {
-                       LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS, new HttpRequestCallbackHandler(request, response));
-                       lc.login();
-                       return lc;
-               } catch (LoginException e1) {
-                       if (log.isDebugEnabled())
-                               log.error("Cannot log in as anonymous", e1);
-                       return null;
-               }
-       }
-       protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
-               response.setStatus(401);
-               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
-               // realm=\"" + httpAuthRealm + "\"");
-               if (org.argeo.cms.internal.kernel.Activator.getAcceptorCredentials() != null && !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");
-
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/http/HttpUtils.java
deleted file mode 100644 (file)
index 70998ea..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.util.Enumeration;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.logging.Log;
-
-public class HttpUtils {
-       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/internal/http/protectedHandlers.xml";
-       public final static String WEBDAV_CONFIG = "/org/argeo/cms/internal/http/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(Log 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(Log 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(Log 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/src/org/argeo/cms/internal/http/LinkServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/LinkServlet.java
deleted file mode 100644 (file)
index 574b09a..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-package org.argeo.cms.internal.http;
-
-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.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.CmsException;
-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,
-                                                       "(" + NodeConstants.CN + "=" + NodeConstants.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(NodeUtils.getDataPath(NodeConstants.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(NodeConstants.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/src/org/argeo/cms/internal/http/PkgServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PkgServlet.java
deleted file mode 100644 (file)
index 9cbd21c..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.Collection;
-import java.util.Enumeration;
-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.api.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/src/org/argeo/cms/internal/http/PrivateHttpContext.java b/org.argeo.cms/src/org/argeo/cms/internal/http/PrivateHttpContext.java
deleted file mode 100644 (file)
index 793478c..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.cms.internal.http;
-
-import javax.security.auth.login.LoginContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/** Requests authorisation */
-@Deprecated
-public class PrivateHttpContext extends DataHttpContext {
-
-       public PrivateHttpContext(String httpAuthrealm, boolean forceBasic) {
-               super(httpAuthrealm, forceBasic);
-       }
-
-       public PrivateHttpContext(String httpAuthrealm) {
-               super(httpAuthrealm);
-       }
-
-       @Override
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               askForWwwAuth(request, response);
-               return null;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java b/org.argeo.cms/src/org/argeo/cms/internal/http/RobotServlet.java
deleted file mode 100644 (file)
index 6d3d302..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.argeo.cms.internal.http;
-
-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();
-       }
-
-}
index 20f4c032e6ab5cab6ac55ea978df809546aaf2e1..fd51c597a4795308a6ef9591fd65cdea0a90de9c 100644 (file)
@@ -3,38 +3,31 @@ package org.argeo.cms.internal.http;
 import java.util.Locale;
 
 import javax.security.auth.Subject;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
 
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
 import org.osgi.service.useradmin.Authorization;
 
 /** CMS session implementation in a web context. */
 public class WebCmsSessionImpl extends CmsSessionImpl {
-       // private final static Log log =
-       // LogFactory.getLog(WebCmsSessionImpl.class);
-
-       private HttpSession httpSession;
+       private static final long serialVersionUID = -5178883380637048025L;
+       private RemoteAuthSession httpSession;
 
        public WebCmsSessionImpl(Subject initialSubject, Authorization authorization, Locale locale,
-                       HttpServletRequest request) {
-               super(initialSubject, authorization, locale, request.getSession(false).getId());
-               httpSession = request.getSession(false);
+                       RemoteAuthRequest request) {
+               super(initialSubject, authorization, locale, request.getSession().getId());
+               httpSession = request.getSession();
        }
 
        @Override
        public boolean isValid() {
                if (isClosed())
                        return false;
-               try {// test http session
-                       httpSession.getCreationTime();
-                       return true;
-               } catch (IllegalStateException ise) {
-                       return false;
-               }
+               return httpSession.isValid();
        }
 
-       public static CmsSessionImpl getCmsSession(HttpServletRequest request) {
-               return CmsSessionImpl.getByLocalId(request.getSession(false).getId());
+       public static CmsSessionImpl getCmsSession(RemoteAuthRequest request) {
+               return CmsSessionImpl.getByLocalId(request.getSession().getId());
        }
 }
index 334e43c85a1672e9cd737bd6f9096a3ec78329d3..4abdd145830a2b8d5ce19809a6b97cec3ad12d36 100644 (file)
@@ -20,7 +20,7 @@ import org.apache.commons.httpclient.auth.MalformedChallengeException;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.params.DefaultHttpParams;
 import org.apache.commons.httpclient.params.HttpParams;
-import org.argeo.cms.internal.kernel.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelConstants;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/protectedHandlers.xml b/org.argeo.cms/src/org/argeo/cms/internal/http/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/src/org/argeo/cms/internal/http/webdav-config.xml b/org.argeo.cms/src/org/argeo/cms/internal/http/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/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/RepoConf.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepoConf.java
deleted file mode 100644 (file)
index a48adcc..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.internal.jcr;
-
-import org.argeo.api.NodeConstants;
-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(NodeConstants.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/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/org.argeo.cms/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java
deleted file mode 100644 (file)
index fbb1e4f..0000000
+++ /dev/null
@@ -1,226 +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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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.NodeConstants;
-import org.argeo.cms.internal.kernel.CmsPaths;
-import org.xml.sax.InputSource;
-
-/** Can interpret properties in order to create an actual JCR repository. */
-public class RepositoryBuilder {
-       private final static Log log = LogFactory.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(NodeConstants.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/src/org/argeo/cms/internal/jcr/repository-h2.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-memory.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/org.argeo.cms/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/src/org/argeo/cms/internal/kernel/.gitignore b/org.argeo.cms/src/org/argeo/cms/internal/kernel/.gitignore
deleted file mode 100644 (file)
index 50e1322..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/*.log
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java
deleted file mode 100644 (file)
index 6d50f3d..0000000
+++ /dev/null
@@ -1,293 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.AllPermission;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import javax.security.auth.login.Configuration;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.ArgeoLogger;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeDeployment;
-import org.argeo.api.NodeInstance;
-import org.argeo.api.NodeState;
-import org.argeo.ident.IdentClient;
-import org.ietf.jgss.GSSCredential;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.condpermadmin.BundleLocationCondition;
-import org.osgi.service.condpermadmin.ConditionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
-import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.log.LogReaderService;
-import org.osgi.service.permissionadmin.PermissionInfo;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * Activates the kernel. Gives access to kernel information for the rest of the
- * bundle (and only it)
- */
-public class Activator implements BundleActivator {
-       private final static Log log = LogFactory.getLog(Activator.class);
-
-       private static Activator instance;
-
-       // TODO make it configurable
-       private boolean hardened = false;
-
-       private static BundleContext bundleContext;
-
-       private LogReaderService logReaderService;
-
-       private NodeLogger logger;
-       private CmsState nodeState;
-       private CmsDeployment nodeDeployment;
-       private CmsInstance nodeInstance;
-
-       private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
-       private ExecutorService internalExecutorService;
-
-       static {
-               Bundle bundle = FrameworkUtil.getBundle(Activator.class);
-               if (bundle != null) {
-                       bundleContext = bundle.getBundleContext();
-               }
-       }
-
-       void init() {
-               Runtime.getRuntime().addShutdownHook(new CmsShutdown());
-               instance = this;
-//             this.bc = bundleContext;
-               if (bundleContext != null)
-                       this.logReaderService = getService(LogReaderService.class);
-               this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
-
-               try {
-                       initSecurity();
-                       initArgeoLogger();
-                       initNode();
-
-                       if (log.isTraceEnabled())
-                               log.trace("Kernel bundle started");
-               } catch (Throwable e) {
-                       log.error("## FATAL: CMS activator failed", e);
-               }
-       }
-
-       void destroy() {
-               try {
-                       if (nodeInstance != null)
-                               nodeInstance.shutdown();
-                       if (nodeDeployment != null)
-                               nodeDeployment.shutdown();
-                       if (nodeState != null)
-                               nodeState.shutdown();
-
-                       if (userAdminSt != null)
-                               userAdminSt.close();
-
-                       internalExecutorService.shutdown();
-                       instance = null;
-                       bundleContext = null;
-                       this.logReaderService = null;
-                       // this.configurationAdmin = null;
-               } catch (Exception e) {
-                       log.error("CMS activator shutdown failed", e);
-               }
-       }
-
-       private void initSecurity() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG;
-                       URL url = getClass().getResource(jaasConfig);
-                       // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
-                       // url.toExternalForm());
-                       KernelUtils.setJaasConfiguration(url);
-               }
-               // explicitly load JAAS configuration
-               Configuration.getConfiguration();
-
-               // code-level permissions
-               String osgiSecurity = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_SECURITY);
-               if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
-                       // TODO rather use a tracker?
-                       ConditionalPermissionAdmin permissionAdmin = bundleContext
-                                       .getService(bundleContext.getServiceReference(ConditionalPermissionAdmin.class));
-                       if (!hardened) {
-                               // All permissions to all bundles
-                               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
-                               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] {
-                                                               new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-                               // TODO data admin permission
-//                             PermissionInfo dataAdminPerm = new PermissionInfo(AuthPermission.class.getName(),
-//                                             "createLoginContext." + NodeConstants.LOGIN_CONTEXT_DATA_ADMIN, null);
-//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] {
-//                                                             new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
-//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.DENY));
-//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] {
-//                                                             new ConditionInfo(BundleSignerCondition.class.getName(), new String[] { "CN=\"Eclipse.org Foundation, Inc.\", OU=IT, O=\"Eclipse.org Foundation, Inc.\", L=Nepean, ST=Ontario, C=CA" }) },
-//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.ALLOW));
-                               update.commit();
-                       } else {
-                               SecurityProfile securityProfile = new SecurityProfile() {
-                               };
-                               securityProfile.applySystemPermissions(permissionAdmin);
-                       }
-               }
-
-       }
-
-       private void initArgeoLogger() {
-               logger = new NodeLogger(logReaderService);
-               if (bundleContext != null)
-                       bundleContext.registerService(ArgeoLogger.class, logger, null);
-       }
-
-       private void initNode() throws IOException {
-               // Node state
-               nodeState = new CmsState();
-               registerService(NodeState.class, nodeState, null);
-
-               // Node deployment
-               nodeDeployment = new CmsDeployment();
-               registerService(NodeDeployment.class, nodeDeployment, null);
-
-               // Node instance
-               nodeInstance = new CmsInstance();
-               registerService(NodeInstance.class, nodeInstance, null);
-       }
-
-       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
-               if (bundleContext != null) {
-                       bundleContext.registerService(clss, service, properties);
-               }
-
-       }
-
-       public static <T> T getService(Class<T> clss) {
-               if (bundleContext != null) {
-                       return bundleContext.getService(bundleContext.getServiceReference(clss));
-               } else {
-                       return null;
-               }
-       }
-
-       /*
-        * OSGi
-        */
-
-       @Override
-       public void start(BundleContext bc) throws Exception {
-               if (!bc.getBundle().equals(bundleContext.getBundle()))
-                       throw new IllegalStateException(
-                                       "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
-               init();
-               userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
-               userAdminSt.open();
-       }
-
-       @Override
-       public void stop(BundleContext bc) throws Exception {
-               if (!bc.getBundle().equals(bundleContext.getBundle()))
-                       throw new IllegalStateException(
-                                       "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
-               destroy();
-       }
-
-//     private <T> T getService(Class<T> clazz) {
-//             ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
-//             if (sr == null)
-//                     throw new IllegalStateException("No service available for " + clazz);
-//             return bundleContext.getService(sr);
-//     }
-
-       public static NodeState getNodeState() {
-               return instance.nodeState;
-       }
-
-       public static GSSCredential getAcceptorCredentials() {
-               return getNodeUserAdmin().getAcceptorCredentials();
-       }
-
-       @Deprecated
-       public static boolean isSingleUser() {
-               return getNodeUserAdmin().isSingleUser();
-       }
-
-       public static UserAdmin getUserAdmin() {
-               return (UserAdmin) getNodeUserAdmin();
-       }
-
-       public static String getHttpProxySslHeader() {
-               return KernelUtils.getFrameworkProp(NodeConstants.HTTP_PROXY_SSL_DN);
-       }
-
-       public static IdentClient getIdentClient(String remoteAddr) {
-               if (!IdentClient.isDefaultAuthdPassphraseFileAvailable())
-                       return null;
-               // TODO make passphrase more configurable
-               return new IdentClient(remoteAddr);
-       }
-
-       private static NodeUserAdmin getNodeUserAdmin() {
-               NodeUserAdmin res;
-               try {
-                       res = instance.userAdminSt.waitForService(60000);
-               } catch (InterruptedException e) {
-                       throw new IllegalStateException("Cannot retrieve Node user admin", e);
-               }
-               if (res == null)
-                       throw new IllegalStateException("No Node user admin found");
-
-               return res;
-               // ServiceReference<UserAdmin> sr =
-               // instance.bc.getServiceReference(UserAdmin.class);
-               // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
-               // return userAdmin;
-
-       }
-
-       static ExecutorService getInternalExecutorService() {
-               return instance.internalExecutorService;
-       }
-
-       // static CmsSecurity getCmsSecurity() {
-       // return instance.nodeSecurity;
-       // }
-
-       public String[] getLocales() {
-               // TODO optimize?
-               List<Locale> locales = getNodeState().getLocales();
-               String[] res = new String[locales.size()];
-               for (int i = 0; i < locales.size(); i++)
-                       res[i] = locales.get(i).toString();
-               return res;
-       }
-
-       static BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public static void main(String[] args) {
-               instance = new Activator();
-               instance.init();
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java
deleted file mode 100644 (file)
index 2667d98..0000000
+++ /dev/null
@@ -1,625 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.api.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.lang.management.ManagementFactory;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.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 javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.DataModelNamespace;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeDeployment;
-import org.argeo.api.NodeState;
-import org.argeo.api.NodeUtils;
-import org.argeo.api.security.CryptoKeyring;
-import org.argeo.api.security.Keyring;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.internal.http.CmsRemotingServlet;
-import org.argeo.cms.internal.http.CmsWebDavServlet;
-import org.argeo.cms.internal.http.HttpUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.maintenance.backup.LogicalRestore;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.util.LangUtils;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-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.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ManagedService;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** Implementation of a CMS deployment. */
-public class CmsDeployment implements NodeDeployment {
-       private final Log log = LogFactory.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private DataModels dataModels;
-       private DeployConfig deployConfig;
-
-       private Long availableSince;
-
-//     private final boolean cleanState;
-
-//     private NodeHttp nodeHttp;
-       private String webDavConfig = HttpUtils.WEBDAV_CONFIG;
-
-       private boolean argeoDataModelExtensionsAvailable = false;
-
-       // Readiness
-       private boolean nodeAvailable = false;
-       private boolean userAdminAvailable = false;
-       private boolean httpExpected = false;
-       private boolean httpAvailable = false;
-
-       public CmsDeployment() {
-//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
-//             if (nodeStateSr == null)
-//                     throw new CmsException("No node state available");
-
-//             NodeState nodeState = bc.getService(nodeStateSr);
-//             cleanState = nodeState.isClean();
-
-//             nodeHttp = new NodeHttp();
-               dataModels = new DataModels(bc);
-               initTrackers();
-       }
-
-       private void initTrackers() {
-               ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
-
-                       @Override
-                       public HttpService addingService(ServiceReference<HttpService> sr) {
-                               httpAvailable = true;
-                               Object httpPort = sr.getProperty("http.port");
-                               Object httpsPort = sr.getProperty("https.port");
-                               log.info(httpPortsMsg(httpPort, httpsPort));
-                               checkReadiness();
-                               return super.addingService(sr);
-                       }
-               };
-               // httpSt.open();
-               KernelUtils.asyncOpen(httpSt);
-
-               ServiceTracker<?, ?> repoContextSt = new RepositoryContextStc();
-               // repoContextSt.open();
-               KernelUtils.asyncOpen(repoContextSt);
-
-               ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
-                       @Override
-                       public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
-                               UserAdmin userAdmin = super.addingService(reference);
-                               addStandardSystemRoles(userAdmin);
-                               userAdminAvailable = true;
-                               checkReadiness();
-                               return userAdmin;
-                       }
-               };
-               // userAdminSt.open();
-               KernelUtils.asyncOpen(userAdminSt);
-
-               ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
-                               ConfigurationAdmin.class, null) {
-                       @Override
-                       public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
-                               ConfigurationAdmin configurationAdmin = bc.getService(reference);
-                               boolean isClean;
-                               try {
-                                       Configuration[] confs = configurationAdmin
-                                                       .listConfigurations("(service.factoryPid=" + NodeConstants.NODE_USER_ADMIN_PID + ")");
-                                       isClean = confs == null || confs.length == 0;
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot analyse clean state", e);
-                               }
-                               deployConfig = new DeployConfig(configurationAdmin, dataModels, isClean);
-                               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
-                               try {
-                                       Configuration[] configs = configurationAdmin
-                                                       .listConfigurations("(service.factoryPid=" + NodeConstants.NODE_USER_ADMIN_PID + ")");
-
-                                       boolean hasDomain = false;
-                                       for (Configuration config : configs) {
-                                               Object realm = config.getProperties().get(UserAdminConf.realm.name());
-                                               if (realm != null) {
-                                                       log.debug("Found realm: " + realm);
-                                                       hasDomain = true;
-                                               }
-                                       }
-                                       if (hasDomain) {
-                                               loadIpaJaasConfiguration();
-                                       }
-                               } catch (Exception e) {
-                                       throw new IllegalStateException("Cannot initialize config", e);
-                               }
-                               return super.addingService(reference);
-                       }
-               };
-               // confAdminSt.open();
-               KernelUtils.asyncOpen(confAdminSt);
-       }
-
-       private String httpPortsMsg(Object httpPort, Object httpsPort) {
-               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
-       }
-
-       private void addStandardSystemRoles(UserAdmin userAdmin) {
-               // we assume UserTransaction is already available (TODO make it more robust)
-               UserTransaction userTransaction = bc.getService(bc.getServiceReference(UserTransaction.class));
-               try {
-                       userTransaction.begin();
-                       Role adminRole = userAdmin.getRole(NodeConstants.ROLE_ADMIN);
-                       if (adminRole == null) {
-                               adminRole = userAdmin.createRole(NodeConstants.ROLE_ADMIN, Role.GROUP);
-                       }
-                       if (userAdmin.getRole(NodeConstants.ROLE_USER_ADMIN) == null) {
-                               Group userAdminRole = (Group) userAdmin.createRole(NodeConstants.ROLE_USER_ADMIN, Role.GROUP);
-                               userAdminRole.addMember(adminRole);
-                       }
-                       userTransaction.commit();
-               } catch (Exception e) {
-                       try {
-                               userTransaction.rollback();
-                       } catch (Exception e1) {
-                               // silent
-                       }
-                       throw new IllegalStateException("Cannot add standard system roles", e);
-               }
-       }
-
-       private void loadIpaJaasConfiguration() {
-               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
-                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
-                       URL url = getClass().getClassLoader().getResource(jaasConfig);
-                       KernelUtils.setJaasConfiguration(url);
-                       log.debug("Set IPA JAAS configuration.");
-               }
-       }
-
-       public void shutdown() {
-//             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 sclean repsoitories", e1);
-               }
-
-               try {
-                       JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
-               } catch (Exception e) {
-                       log.error("Cannot stop default Jetty server.", e);
-               }
-
-               if (deployConfig != null) {
-                       new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
-               }
-       }
-
-       /**
-        * 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);
-               }
-       }
-
-       final private void tributeToFreeSoftware(long initDuration) {
-               if (log.isTraceEnabled()) {
-                       long ms = initDuration / 100;
-                       log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
-                       long beginNano = System.nanoTime();
-                       try {
-                               Thread.sleep(ms, 0);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       long durationNano = System.nanoTime() - beginNano;
-                       final double M = 1000d * 1000d;
-                       double sleepAccuracy = ((double) durationNano) / (ms * M);
-                       log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
-               }
-       }
-
-       private void prepareNodeRepository(Repository deployedNodeRepository, List<String> publishAsLocalRepo) {
-               if (availableSince != null) {
-                       throw new IllegalStateException("Deployment is already available");
-               }
-
-               // home
-               prepareDataModel(NodeConstants.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,
-                                       "(" + NodeConstants.CN + "=" + NodeConstants.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 = NodeUtils.openDataAdminSession(deployedNodeRepository, workspaceName);
-                                       } catch (IllegalArgumentException e) {// no such workspace
-                                               Session adminSession = NodeUtils.openDataAdminSession(deployedNodeRepository, null);
-                                               try {
-                                                       adminSession.getWorkspace().createWorkspace(workspaceName);
-                                               } finally {
-                                                       Jcr.logout(adminSession);
-                                               }
-                                               targetSession = NodeUtils.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(NodeConstants.CN, NodeConstants.EGO_REPOSITORY);
-               regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               Repository egoRepository = new EgoRepository(deployedRepository, false);
-               bc.registerService(Repository.class, egoRepository, regProps);
-               registerRepositoryServlets(NodeConstants.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, NodeConstants.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 (NodeConstants.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 = deployConfig.isStandalone(name);
-               boolean publishLocalRepo;
-               if (isStandalone && name.equals(cn))// includes the node itself
-                       publishLocalRepo = true;
-               else if (!isStandalone && cn.equals(NodeConstants.NODE_REPOSITORY))
-                       publishLocalRepo = true;
-               else
-                       publishLocalRepo = false;
-
-               return publishLocalRepo;
-       }
-
-       private void publishLocalRepo(String dataModelName, Repository repository) {
-               Hashtable<String, Object> properties = new Hashtable<>();
-               properties.put(NodeConstants.CN, dataModelName);
-               LocalRepository localRepository;
-               String[] classes;
-               if (repository instanceof RepositoryImpl) {
-                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(),
-                                       JackrabbitLocalRepository.class.getName() };
-               } else {
-                       localRepository = new LocalRepository(repository, dataModelName);
-                       classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() };
-               }
-               bc.registerService(classes, localRepository, properties);
-
-               // TODO make it configurable
-               registerRepositoryServlets(dataModelName, localRepository);
-               if (log.isTraceEnabled())
-                       log.trace("Published data model " + dataModelName);
-       }
-
-       @Override
-       public synchronized Long getAvailableSince() {
-               return availableSince;
-       }
-
-       public synchronized boolean isAvailable() {
-               return availableSince != null;
-       }
-
-       protected void registerRepositoryServlets(String alias, Repository repository) {
-               registerRemotingServlet(alias, repository);
-               registerWebdavServlet(alias, repository);
-       }
-
-       protected void registerWebdavServlet(String alias, Repository repository) {
-               CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository);
-               Hashtable<String, String> ip = new Hashtable<>();
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
-               ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                               "/" + alias);
-
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*");
-               ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT,
-                               "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + NodeConstants.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(NodeConstants.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,
-                               HttpUtils.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 + "=" + NodeConstants.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(NodeConstants.CN);
-                       if (cn != null) {
-                               List<String> publishAsLocalRepo = new ArrayList<>();
-                               if (cn.equals(NodeConstants.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/src/org/argeo/cms/internal/kernel/CmsFsProvider.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsFsProvider.java
deleted file mode 100644 (file)
index 6cd0bf8..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider;
-import org.argeo.jcr.fs.JcrFileSystem;
-import org.argeo.jcr.fs.JcrFileSystemProvider;
-import org.argeo.jcr.fs.JcrFsException;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-
-/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */
-public class CmsFsProvider extends AbstractJackrabbitFsProvider {
-       private Map<String, CmsFileSystem> fileSystems = new HashMap<>();
-
-       @Override
-       public String getScheme() {
-               return NodeConstants.SCHEME_NODE;
-       }
-
-       @Override
-       public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
-               BundleContext bc = FrameworkUtil.getBundle(CmsFsProvider.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 = NodeUtils.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=" + NodeConstants.EGO_REPOSITORY + ")")
-                                                               .iterator().next());
-//                             Session session = repository.login();
-                               CmsFileSystem fileSystem = new CmsFileSystem(this, repository);
-                               fileSystems.put(username, fileSystem);
-                               return fileSystem;
-                       }
-               } catch (InvalidSyntaxException | 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(NodeConstants.HOME_WORKSPACE);
-                       return NodeUtils.getUserHome(session);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get user home", e);
-               }
-       }
-
-       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/src/org/argeo/cms/internal/kernel/CmsInstance.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsInstance.java
deleted file mode 100644 (file)
index eef8d92..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import javax.jcr.Repository;
-import javax.naming.ldap.LdapName;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeInstance;
-import org.argeo.cms.CmsException;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-
-public class CmsInstance implements NodeInstance {
-       private final Log log = LogFactory.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private EgoRepository egoRepository;
-
-       public CmsInstance() {
-               initTrackers();
-       }
-
-       private void initTrackers() {
-               // node repository
-               new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
-                       @Override
-                       public Repository addingService(ServiceReference<Repository> reference) {
-                               Object cn = reference.getProperty(NodeConstants.CN);
-                               if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
-//                                     egoRepository = (EgoRepository) bc.getService(reference);
-                                       if (log.isTraceEnabled())
-                                               log.trace("Home repository is available");
-                               }
-                               return super.addingService(reference);
-                       }
-
-                       @Override
-                       public void removedService(ServiceReference<Repository> reference, Repository service) {
-                               super.removedService(reference, service);
-//                             egoRepository = null;
-                       }
-
-               }.open();
-       }
-
-       public void shutdown() {
-
-       }
-
-       @Override
-       public void createWorkgroup(LdapName dn) {
-               if (egoRepository == null)
-                       throw new CmsException("Ego repository is not available");
-               // TODO add check that the group exists
-               egoRepository.createWorkgroup(dn);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsPaths.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsPaths.java
deleted file mode 100644 (file)
index ebdb925..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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/src/org/argeo/cms/internal/kernel/CmsShutdown.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsShutdown.java
deleted file mode 100644 (file)
index bfc5850..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkEvent;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.launch.Framework;
-
-/** Shutdowns the OSGi framework */
-class CmsShutdown extends Thread {
-       public final int EXIT_OK = 0;
-       public final int EXIT_ERROR = 1;
-       public final int EXIT_TIMEOUT = 2;
-       public final int EXIT_UNKNOWN = 3;
-
-       private final Log log = LogFactory.getLog(CmsShutdown.class);
-       // private final BundleContext bc =
-       // FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext();
-       private final Framework framework;
-
-       /** Shutdown timeout in ms */
-       private long timeout = 10 * 60 * 1000;
-
-       public CmsShutdown() {
-               super("CMS Shutdown Hook");
-               framework = FrameworkUtil.getBundle(CmsShutdown.class) != null
-                               ? (Framework) FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext().getBundle(0)
-                               : null;
-       }
-
-       @Override
-       public void run() {
-               if (framework != null && framework.getState() != Bundle.ACTIVE) {
-                       return;
-               }
-
-               if (log.isDebugEnabled())
-                       log.debug("Shutting down OSGi framework...");
-               try {
-                       if (framework != null) {
-                               // shutdown framework
-                               framework.stop();
-                               // wait for shutdown
-                               FrameworkEvent shutdownEvent = framework.waitForStop(timeout);
-                               int stoppedType = shutdownEvent.getType();
-                               Runtime runtime = Runtime.getRuntime();
-                               if (stoppedType == FrameworkEvent.STOPPED) {
-                                       // close VM
-                                       // System.exit(EXIT_OK);
-                               } else if (stoppedType == FrameworkEvent.ERROR) {
-                                       log.error("The OSGi framework stopped with an error");
-                                       runtime.halt(EXIT_ERROR);
-                               } else if (stoppedType == FrameworkEvent.WAIT_TIMEDOUT) {
-                                       log.error("The OSGi framework hasn't stopped after " + timeout + "ms."
-                                                       + " Forcibly terminating the JVM...");
-                                       runtime.halt(EXIT_TIMEOUT);
-                               } else {
-                                       log.error("Unknown state of OSGi framework after " + timeout + "ms."
-                                                       + " Forcibly terminating the JVM... (" + shutdownEvent + ")");
-                                       runtime.halt(EXIT_UNKNOWN);
-                               }
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       log.error("Unexpected exception " + e + " in shutdown hook. " + " Forcibly terminating the JVM...");
-                       Runtime.getRuntime().halt(EXIT_UNKNOWN);
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsState.java
deleted file mode 100644 (file)
index b794d08..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static java.util.Locale.ENGLISH;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-import javax.jcr.RepositoryFactory;
-import javax.transaction.TransactionManager;
-import javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeState;
-import org.argeo.cms.LocaleUtils;
-import org.argeo.transaction.simple.SimpleTransactionManager;
-import org.argeo.util.LangUtils;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ManagedServiceFactory;
-
-/**
- * Implementation of a {@link NodeState}, initialising the required services.
- */
-public class CmsState implements NodeState {
-       private final static Log log = LogFactory.getLog(CmsState.class);
-//     private final BundleContext bc = FrameworkUtil.getBundle(CmsState.class).getBundleContext();
-
-       // REFERENCES
-       private Long availableSince;
-
-       // i18n
-       private Locale defaultLocale;
-       private List<Locale> locales = null;
-
-       private ThreadGroup threadGroup = new ThreadGroup("CMS");
-       private KernelThread kernelThread;
-       private List<Runnable> stopHooks = new ArrayList<>();
-
-       private final String stateUuid;
-//     private final boolean cleanState;
-       private String hostname;
-
-       public CmsState() {
-//             this.stateUuid = stateUuid;
-               this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
-//             this.cleanState = stateUuid.equals(frameworkUuid);
-               try {
-                       this.hostname = InetAddress.getLocalHost().getHostName();
-               } catch (UnknownHostException e) {
-                       log.error("Cannot set hostname: " + e);
-               }
-
-               availableSince = System.currentTimeMillis();
-               if (log.isDebugEnabled())
-                       // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
-                       // (clean state) " : " "));
-                       log.debug("## CMS starting... (" + stateUuid + ")");
-
-               initI18n();
-               initServices();
-
-               // kernel thread
-               kernelThread = new KernelThread(threadGroup, "Kernel Thread");
-               kernelThread.setContextClassLoader(getClass().getClassLoader());
-               kernelThread.start();
-       }
-
-       private void initI18n() {
-               Object defaultLocaleValue = KernelUtils.getFrameworkProp(NodeConstants.I18N_DEFAULT_LOCALE);
-               defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
-                               : new Locale(ENGLISH.getLanguage());
-               locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(NodeConstants.I18N_LOCALES));
-       }
-
-       private void initServices() {
-               // JTA
-               String tmType = KernelUtils.getFrameworkProp(NodeConstants.TRANSACTION_MANAGER,
-                               NodeConstants.TRANSACTION_MANAGER_SIMPLE);
-               if (NodeConstants.TRANSACTION_MANAGER_SIMPLE.equals(tmType)) {
-                       initSimpleTransactionManager();
-               } else if (NodeConstants.TRANSACTION_MANAGER_BITRONIX.equals(tmType)) {
-//                     initBitronixTransactionManager();
-                       throw new UnsupportedOperationException(
-                                       "Bitronix is not supported anymore, but could be again if there is enough interest.");
-               } else {
-                       throw new IllegalArgumentException("Usupported transaction manager type " + tmType);
-               }
-
-               // POI
-//             POIXMLTypeLoader.setClassLoader(CTConnection.class.getClassLoader());
-
-               // Tika
-//             OpenDocumentParser odfParser = new OpenDocumentParser();
-//             bc.registerService(Parser.class, odfParser, new Hashtable());
-//             PDFParser pdfParser = new PDFParser();
-//             bc.registerService(Parser.class, pdfParser, new Hashtable());
-//             OOXMLParser ooxmlParser = new OOXMLParser();
-//             bc.registerService(Parser.class, ooxmlParser, new Hashtable());
-//             TesseractOCRParser ocrParser = new TesseractOCRParser();
-//             ocrParser.setLanguage("ara");
-//             bc.registerService(Parser.class, ocrParser, new Hashtable());
-
-               // JCR
-               RepositoryServiceFactory repositoryServiceFactory = new RepositoryServiceFactory();
-               stopHooks.add(() -> repositoryServiceFactory.shutdown());
-               Activator.registerService(ManagedServiceFactory.class, repositoryServiceFactory,
-                               LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_REPOS_FACTORY_PID));
-
-               NodeRepositoryFactory repositoryFactory = new NodeRepositoryFactory();
-               Activator.registerService(RepositoryFactory.class, repositoryFactory, null);
-
-               // Security
-               NodeUserAdmin userAdmin = new NodeUserAdmin(NodeConstants.ROLES_BASEDN, NodeConstants.TOKENS_BASEDN);
-               stopHooks.add(() -> userAdmin.destroy());
-               Activator.registerService(ManagedServiceFactory.class, userAdmin,
-                               LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_USER_ADMIN_PID));
-
-               // File System
-               CmsFsProvider cmsFsProvider = new CmsFsProvider();
-//             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);
-//             }
-               Activator.registerService(FileSystemProvider.class, cmsFsProvider,
-                               LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_FS_PROVIDER_PID));
-       }
-
-       private void initSimpleTransactionManager() {
-               SimpleTransactionManager transactionManager = new SimpleTransactionManager();
-               Activator.registerService(TransactionManager.class, transactionManager, null);
-               Activator.registerService(UserTransaction.class, transactionManager, null);
-               // TODO TransactionSynchronizationRegistry
-       }
-
-//     private void initBitronixTransactionManager() {
-//             // TODO manage it in a managed service, as startup could be long
-//             ServiceReference<TransactionManager> existingTm = bc.getServiceReference(TransactionManager.class);
-//             if (existingTm != null) {
-//                     if (log.isDebugEnabled())
-//                             log.debug("Using provided transaction manager " + existingTm);
-//                     return;
-//             }
-//
-//             if (!TransactionManagerServices.isTransactionManagerRunning()) {
-//                     bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
-//                     tmConf.setServerId(UUID.randomUUID().toString());
-//
-//                     Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class);
-//                     File tmBaseDir = bitronixBundle.getDataFile(KernelConstants.DIR_TRANSACTIONS);
-//                     File tmDir1 = new File(tmBaseDir, "btm1");
-//                     tmDir1.mkdirs();
-//                     tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
-//                     File tmDir2 = new File(tmBaseDir, "btm2");
-//                     tmDir2.mkdirs();
-//                     tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
-//             }
-//             BitronixTransactionManager transactionManager = getTransactionManager();
-//             stopHooks.add(() -> transactionManager.shutdown());
-//             BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
-//             // register
-//             bc.registerService(TransactionManager.class, transactionManager, null);
-//             bc.registerService(UserTransaction.class, transactionManager, null);
-//             bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
-//             if (log.isDebugEnabled())
-//                     log.debug("Initialised default Bitronix transaction manager");
-//     }
-
-       void shutdown() {
-               if (log.isDebugEnabled())
-                       log.debug("CMS stopping...  (" + this.stateUuid + ")");
-
-               if (kernelThread != null)
-                       kernelThread.destroyAndJoin();
-               // In a different thread in order to avoid interruptions
-               Thread stopHookThread = new Thread(() -> applyStopHooks(), "Apply Argeo Stop Hooks");
-               stopHookThread.start();
-               try {
-                       stopHookThread.join(10 * 60 * 1000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-
-               long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
-               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
-       }
-
-       /** Apply shutdown hoos in reverse order. */
-       private void applyStopHooks() {
-               for (int i = stopHooks.size() - 1; i >= 0; i--) {
-                       try {
-                               stopHooks.get(i).run();
-                       } catch (Exception e) {
-                               log.error("Could not run shutdown hook #" + i);
-                       }
-               }
-               // Clean hanging Gogo shell thread
-               new GogoShellKiller().start();
-       }
-
-//     @Override
-//     public boolean isClean() {
-//             return cleanState;
-//     }
-
-       @Override
-       public Long getAvailableSince() {
-               return availableSince;
-       }
-
-       /*
-        * ACCESSORS
-        */
-       public Locale getDefaultLocale() {
-               return defaultLocale;
-       }
-
-       public List<Locale> getLocales() {
-               return locales;
-       }
-
-       public String getHostname() {
-               return hostname;
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspace.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspace.java
deleted file mode 100644 (file)
index 2033241..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.jcr.AccessDeniedException;
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.InvalidSerializedDataException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Workspace;
-import javax.jcr.lock.LockException;
-import javax.jcr.lock.LockManager;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NodeTypeManager;
-import javax.jcr.observation.ObservationManager;
-import javax.jcr.query.QueryManager;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionException;
-import javax.jcr.version.VersionManager;
-
-import org.xml.sax.ContentHandler;
-
-public class CmsWorkspace implements Workspace {
-       private String name;
-       private Session session;
-
-       @Override
-       public Session getSession() {
-               return session;
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public void copy(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException,
-                       AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void copy(String srcWorkspace, String srcAbsPath, String destAbsPath)
-                       throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException,
-                       PathNotFoundException, ItemExistsException, LockException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void clone(String srcWorkspace, String srcAbsPath, String destAbsPath, boolean removeExisting)
-                       throws NoSuchWorkspaceException, ConstraintViolationException, VersionException, AccessDeniedException,
-                       PathNotFoundException, ItemExistsException, LockException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void move(String srcAbsPath, String destAbsPath) throws ConstraintViolationException, VersionException,
-                       AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void restore(Version[] versions, boolean removeExisting)
-                       throws ItemExistsException, UnsupportedRepositoryOperationException, VersionException, LockException,
-                       InvalidItemStateException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public LockManager getLockManager() throws UnsupportedRepositoryOperationException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public QueryManager getQueryManager() throws RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public NamespaceRegistry getNamespaceRegistry() throws RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public NodeTypeManager getNodeTypeManager() throws RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public ObservationManager getObservationManager()
-                       throws UnsupportedRepositoryOperationException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public VersionManager getVersionManager() throws UnsupportedRepositoryOperationException, RepositoryException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public String[] getAccessibleWorkspaceNames() throws RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException,
-                       ConstraintViolationException, VersionException, LockException, AccessDeniedException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException, VersionException,
-                       PathNotFoundException, ItemExistsException, ConstraintViolationException, InvalidSerializedDataException,
-                       LockException, AccessDeniedException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void createWorkspace(String name)
-                       throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void createWorkspace(String name, String srcWorkspace) throws AccessDeniedException,
-                       UnsupportedRepositoryOperationException, NoSuchWorkspaceException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void deleteWorkspace(String name) throws AccessDeniedException, UnsupportedRepositoryOperationException,
-                       NoSuchWorkspaceException, RepositoryException {
-               throw new UnsupportedOperationException();
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsWorkspaceIndexer.java
deleted file mode 100644 (file)
index bb38d6a..0000000
+++ /dev/null
@@ -1,343 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.jcr.JcrUtils;
-
-/** Ensure consistency of files, folder and last modified nodes. */
-class CmsWorkspaceIndexer implements EventListener {
-       private final static Log log = LogFactory.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/src/org/argeo/cms/internal/kernel/DataModels.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DataModels.java
deleted file mode 100644 (file)
index acf0dbf..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.api.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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.DataModelNamespace;
-import org.argeo.cms.CmsException;
-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 Log log = LogFactory.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 CmsException("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/src/org/argeo/cms/internal/kernel/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java
deleted file mode 100644 (file)
index f481f3f..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.naming.AttributesDictionary;
-import org.argeo.naming.LdifParser;
-import org.argeo.naming.LdifWriter;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.eclipse.equinox.http.jetty.JettyConfigurator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.cm.Configuration;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.cm.ConfigurationEvent;
-import org.osgi.service.cm.ConfigurationListener;
-
-/** Manages the LDIF-based deployment configuration. */
-class DeployConfig implements ConfigurationListener {
-       private final Log log = LogFactory.getLog(getClass());
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
-       private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
-       private final DataModels dataModels;
-
-       private boolean isFirstInit = false;
-
-       private final static String ROLES = "roles";
-
-       public DeployConfig(ConfigurationAdmin configurationAdmin, DataModels dataModels, boolean isClean) {
-               this.dataModels = dataModels;
-               // ConfigurationAdmin configurationAdmin =
-               // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
-               try {
-                       if (!isInitialized()) { // first init
-                               isFirstInit = true;
-                               firstInit();
-                       }
-                       init(configurationAdmin, isClean, isFirstInit);
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not init deploy configs", e);
-               }
-               // FIXME check race conditions during initialization
-               // bc.registerService(ConfigurationListener.class, this, null);
-       }
-
-       private void firstInit() throws IOException {
-               log.info("## FIRST INIT ##");
-               Files.createDirectories(deployConfigPath.getParent());
-
-               // FirstInit firstInit = new FirstInit();
-               InitUtils.prepareFirstInitInstanceArea();
-
-               if (!Files.exists(deployConfigPath))
-                       deployConfigs = new TreeMap<>();
-               else// config file could have juste been copied by preparation
-                       try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                               deployConfigs = new LdifParser().read(in);
-                       }
-               save();
-       }
-
-       private void setFromFrameworkProperties(boolean isFirstInit) {
-               // node repository
-               Dictionary<String, Object> nodeConfig = InitUtils
-                               .getNodeRepositoryConfig(getProps(NodeConstants.NODE_REPOS_FACTORY_PID, NodeConstants.NODE));
-               // node repository is mandatory
-               putFactoryDeployConfig(NodeConstants.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 = InitUtils.getRepositoryConfig(dataModel.getName(),
-                                       getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName()));
-                       if (config.size() != 0)
-                               putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config);
-               }
-
-               // user admin
-               List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
-               if (userDirectoryConfigs.size() != 0) {
-                       List<String> activeCns = new ArrayList<>();
-                       for (int i = 0; i < userDirectoryConfigs.size(); i++) {
-                               Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
-                               String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
-                               String cn;
-                               if (NodeConstants.ROLES_BASEDN.equals(baseDn))
-                                       cn = ROLES;
-                               else
-                                       cn = UserAdminConf.baseDnHash(userDirectoryConfig);
-                               activeCns.add(cn);
-                               userDirectoryConfig.put(NodeConstants.CN, cn);
-                               putFactoryDeployConfig(NodeConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
-                       }
-                       // disable others
-                       LdapName userAdminFactoryName = serviceFactoryDn(NodeConstants.NODE_USER_ADMIN_PID);
-                       for (LdapName name : deployConfigs.keySet()) {
-                               if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
-//                                     try {
-                                       Attributes attrs = deployConfigs.get(name);
-                                       String cn = name.getRdn(name.size() - 1).getValue().toString();
-                                       if (!activeCns.contains(cn)) {
-                                               attrs.put(UserAdminConf.disabled.name(), "true");
-                                       }
-//                                     } catch (Exception e) {
-//                                             throw new CmsException("Cannot disable user directory " + name, e);
-//                                     }
-                               }
-                       }
-               }
-
-               // http server
-//             Dictionary<String, Object> webServerConfig = InitUtils
-//                             .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
-//             if (!webServerConfig.isEmpty()) {
-//                     // TODO check for other customizers
-//                     webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
-//                     putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
-//             }
-               LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT);
-               if (deployConfigs.containsKey(defaultHttpServiceDn)) {
-                       // remove old default configs since we have now to start Jetty servlet bridge
-                       // indirectly
-                       deployConfigs.remove(defaultHttpServiceDn);
-               }
-
-               // SAVE
-               save();
-               //
-
-               // Explicitly configures Jetty so that the default server is not started by the
-               // activator of the Equinox Jetty bundle.
-               Dictionary<String, Object> webServerConfig = InitUtils
-                               .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, NodeConstants.DEFAULT));
-//             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 {
-                                       JettyConfigurator.startServer(KernelConstants.DEFAULT_JETTY_SERVER, webServerConfig);
-                                       // 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 " + webServerConfig, e);
-               }
-
-       }
-
-       private void init(ConfigurationAdmin configurationAdmin, boolean isClean, boolean isFirstInit) throws IOException {
-
-               try (InputStream in = Files.newInputStream(deployConfigPath)) {
-                       deployConfigs = new LdifParser().read(in);
-               }
-               if (isClean) {
-                       if (log.isDebugEnabled())
-                               log.debug("Clean state, loading from framework properties...");
-                       setFromFrameworkProperties(isFirstInit);
-
-                       // FIXME make it more robust
-                       Configuration systemRolesConf = null;
-                       LdapName systemRolesDn;
-                       try {
-                               // FIXME make it more robust
-                               systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
-                       } catch (InvalidNameException e) {
-                               throw new IllegalArgumentException(e);
-                       }
-                       deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
-                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                               LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
-                               if (prefix.toString().equals(NodeConstants.DEPLOY_BASEDN)) {
-                                       if (lastRdn.getType().equals(NodeConstants.CN)) {
-                                               // service
-                                               String pid = lastRdn.getValue().toString();
-                                               Configuration conf = configurationAdmin.getConfiguration(pid);
-                                               AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
-                                               conf.update(dico);
-                                       } else {
-                                               // service factory definition
-                                       }
-                               } else {
-                                       Attributes config = deployConfigs.get(dn);
-                                       Attribute disabled = config.get(UserAdminConf.disabled.name());
-                                       if (disabled != null)
-                                               continue deployConfigs;
-                                       // service factory service
-                                       Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
-                                       assert beforeLastRdn.getType().equals(NodeConstants.OU);
-                                       String factoryPid = beforeLastRdn.getValue().toString();
-                                       Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                                       if (systemRolesDn.equals(dn)) {
-                                               systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
-                                       } else {
-                                               AttributesDictionary dico = new AttributesDictionary(config);
-                                               conf.update(dico);
-                                       }
-                               }
-                       }
-
-                       // system roles must be last since it triggers node user admin publication
-                       if (systemRolesConf == null)
-                               throw new IllegalStateException("System roles are not configured.");
-                       systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
-               }
-               // TODO check consistency if not clean
-       }
-
-       @Override
-       public void configurationEvent(ConfigurationEvent event) {
-               try {
-                       if (ConfigurationEvent.CM_UPDATED == event.getType()) {
-                               ConfigurationAdmin configurationAdmin = bc.getService(event.getReference());
-                               Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
-                               LdapName serviceDn = null;
-                               String factoryPid = conf.getFactoryPid();
-                               if (factoryPid != null) {
-                                       LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-                                       if (deployConfigs.containsKey(serviceFactoryDn)) {
-                                               for (LdapName dn : deployConfigs.keySet()) {
-                                                       if (dn.startsWith(serviceFactoryDn)) {
-                                                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
-                                                               assert lastRdn.getType().equals(NodeConstants.CN);
-                                                               Object value = conf.getProperties().get(lastRdn.getType());
-                                                               assert value != null;
-                                                               if (value.equals(lastRdn.getValue())) {
-                                                                       serviceDn = dn;
-                                                                       break;
-                                                               }
-                                                       }
-                                               }
-
-                                               Object cn = conf.getProperties().get(NodeConstants.CN);
-                                               if (cn == null)
-                                                       throw new IllegalArgumentException("Properties must contain cn");
-                                               if (serviceDn == null) {
-                                                       putFactoryDeployConfig(factoryPid, conf.getProperties());
-                                               } else {
-                                                       Attributes attrs = deployConfigs.get(serviceDn);
-                                                       assert attrs != null;
-                                                       AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               }
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
-                                       } else {
-                                               // ignore non config-registered service factories
-                                       }
-                               } else {
-                                       serviceDn = serviceDn(event.getPid());
-                                       if (deployConfigs.containsKey(serviceDn)) {
-                                               Attributes attrs = deployConfigs.get(serviceDn);
-                                               assert attrs != null;
-                                               AttributesDictionary.copy(conf.getProperties(), attrs);
-                                               save();
-                                               if (log.isDebugEnabled())
-                                                       log.debug("Updated deploy config " + serviceDn);
-                                       } else {
-                                               // ignore non config-registered services
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Could not handle configuration event", e);
-               }
-       }
-
-       void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
-               Object cn = props.get(NodeConstants.CN);
-               if (cn == null)
-                       throw new IllegalArgumentException("cn must be set in properties");
-               LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
-               if (!deployConfigs.containsKey(serviceFactoryDn))
-                       deployConfigs.put(serviceFactoryDn, new BasicAttributes(NodeConstants.OU, factoryPid));
-               LdapName serviceDn = serviceDn(factoryPid, cn.toString());
-               Attributes attrs = new BasicAttributes();
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
-               LdapName serviceDn = serviceDn(servicePid);
-               Attributes attrs = new BasicAttributes(NodeConstants.CN, servicePid);
-               AttributesDictionary.copy(props, attrs);
-               deployConfigs.put(serviceDn, attrs);
-       }
-
-       void save() {
-               try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
-                       new LdifWriter(writer).write(deployConfigs);
-               } catch (IOException e) {
-                       // throw new CmsException("Cannot save deploy configs", e);
-                       log.error("Cannot save deploy configs", e);
-               }
-       }
-
-       boolean isStandalone(String dataModelName) {
-               return getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null;
-       }
-
-       /*
-        * UTILITIES
-        */
-       private LdapName serviceFactoryDn(String factoryPid) {
-               try {
-                       return new LdapName(NodeConstants.OU + "=" + factoryPid + "," + NodeConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
-               }
-       }
-
-       private LdapName serviceDn(String servicePid) {
-               try {
-                       return new LdapName(NodeConstants.CN + "=" + servicePid + "," + NodeConstants.DEPLOY_BASEDN);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
-               }
-       }
-
-       private LdapName serviceDn(String factoryPid, String cn) {
-               try {
-                       return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(NodeConstants.CN, cn));
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
-               }
-       }
-
-       Dictionary<String, Object> getProps(String factoryPid, String cn) {
-               Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
-               if (attrs != null)
-                       return new AttributesDictionary(attrs);
-               else
-                       return null;
-       }
-
-       private static boolean isInitialized() {
-               return Files.exists(deployConfigPath);
-       }
-
-       public boolean isFirstInit() {
-               return isFirstInit;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/EgoRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/EgoRepository.java
deleted file mode 100644 (file)
index c866eaa..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.cms.CmsException;
-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 = NodeConstants.HOME_WORKSPACE;
-       private String defaultGroupsWorkspace = NodeConstants.SRV_WORKSPACE;
-//     private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE;
-       private final boolean remote;
-
-       public EgoRepository(Repository repository, boolean remote) {
-               super(repository);
-               this.remote = remote;
-               putDescriptor(NodeConstants.CN, NodeConstants.EGO_REPOSITORY);
-               if (!remote) {
-                       LoginContext lc;
-                       try {
-                               lc = new LoginContext(NodeConstants.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(NodeConstants.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(NodeConstants.ROLES_BASEDN))
-                       return;
-
-               try {
-                       Node userHome = NodeUtils.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 = NodeUtils.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/src/org/argeo/cms/internal/kernel/GogoShellKiller.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/GogoShellKiller.java
deleted file mode 100644 (file)
index 39b11a5..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-/**
- * Workaround for killing Gogo shell by system shutdown.
- * 
- * @see https://issues.apache.org/jira/browse/FELIX-4208
- */
-class GogoShellKiller extends Thread {
-
-       public GogoShellKiller() {
-               super("Gogo Shell Killer");
-               setDaemon(true);
-       }
-
-       @Override
-       public void run() {
-               ThreadGroup rootTg = getRootThreadGroup(null);
-               Thread gogoShellThread = findGogoShellThread(rootTg);
-               if (gogoShellThread == null)
-                       return;
-               while (getNonDaemonCount(rootTg) > 2) {
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-               }
-               gogoShellThread = findGogoShellThread(rootTg);
-               if (gogoShellThread == null)
-                       return;
-               // No non-deamon threads left, forcibly halting the VM
-               Runtime.getRuntime().halt(0);
-       }
-
-       private ThreadGroup getRootThreadGroup(ThreadGroup tg) {
-               if (tg == null)
-                       tg = Thread.currentThread().getThreadGroup();
-               if (tg.getParent() == null)
-                       return tg;
-               else
-                       return getRootThreadGroup(tg.getParent());
-       }
-
-       private int getNonDaemonCount(ThreadGroup rootThreadGroup) {
-               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
-               rootThreadGroup.enumerate(threads);
-               int nonDameonCount = 0;
-               for (Thread t : threads)
-                       if (t != null && !t.isDaemon())
-                               nonDameonCount++;
-               return nonDameonCount;
-       }
-
-       private Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
-               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
-               rootThreadGroup.enumerate(threads, true);
-               for (Thread thread : threads) {
-                       if (thread.getName().equals("pipe-gosh --login --noshutdown"))
-                               return thread;
-               }
-               return null;
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java
deleted file mode 100644 (file)
index 95d17d8..0000000
+++ /dev/null
@@ -1,357 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.Reader;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-import javax.security.auth.x500.X500Principal;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.internal.http.InternalHttpConstants;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.JcrException;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-
-/**
- * Interprets framework properties in order to generate the initial deploy
- * configuration.
- */
-class InitUtils {
-       private final static Log log = LogFactory.getLog(InitUtils.class);
-
-       /** Override the provided config with the framework properties */
-       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(NodeConstants.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(NodeConstants.CN, NodeConstants.NODE_REPOSITORY);
-               return props;
-       }
-
-       static Dictionary<String, Object> getRepositoryConfig(String dataModelName, Dictionary<String, Object> provided) {
-               if (dataModelName.equals(NodeConstants.NODE_REPOSITORY) || dataModelName.equals(NodeConstants.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(
-                                       NodeConstants.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(NodeConstants.CN, dataModelName);
-               return props;
-       }
-
-       /** Override the provided config with the framework properties */
-       static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
-               String httpPort = getFrameworkProp("org.osgi.service.http.port");
-               String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
-               /// TODO make it more generic
-               String httpHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
-               String httpsHost = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
-               String webSocketEnabled = getFrameworkProp(
-                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
-
-               final Hashtable<String, Object> props = new Hashtable<String, Object>();
-               // try {
-               if (httpPort != null || httpsPort != null) {
-                       boolean httpEnabled = httpPort != null;
-                       props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
-                       boolean httpsEnabled = httpsPort != null;
-                       props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
-
-                       if (httpEnabled) {
-                               props.put(InternalHttpConstants.HTTP_PORT, httpPort);
-                               if (httpHost != null)
-                                       props.put(InternalHttpConstants.HTTP_HOST, httpHost);
-                       }
-
-                       if (httpsEnabled) {
-                               props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
-                               if (httpsHost != null)
-                                       props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
-
-                               // server certificate
-                               Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
-                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
-                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
-                               String keyStorePasswordStr = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
-                               char[] keyStorePassword;
-                               if (keyStorePasswordStr == null)
-                                       keyStorePassword = "changeit".toCharArray();
-                               else
-                                       keyStorePassword = keyStorePasswordStr.toCharArray();
-
-                               // if PEM files both exists, update the PKCS12 file
-                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
-                                       // TODO check certificate update time? monitor changes?
-                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
-                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
-                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
-                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                                               if (log.isDebugEnabled())
-                                                       log.debug("PEM certificate stored in " + keyStorePath);
-                                       } catch (IOException e) {
-                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
-                                       }
-                               }
-
-                               if (!Files.exists(keyStorePath))
-                                       createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
-                               props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
-                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
-
-//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
-//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
-//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
-
-                               // client certificate authentication
-                               String wantClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
-                               if (wantClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
-                               String needClientAuth = getFrameworkProp(
-                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
-                               if (needClientAuth != null)
-                                       props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
-                       }
-
-                       // web socket
-                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
-                               props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
-
-                       props.put(NodeConstants.CN, NodeConstants.DEFAULT);
-               }
-               return props;
-       }
-
-       static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
-               List<Dictionary<String, Object>> res = new ArrayList<>();
-               File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
-               List<String> uris = new ArrayList<>();
-
-               // node roles
-               String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
-               String baseNodeRoleDn = NodeConstants.ROLES_BASEDN;
-               if (nodeRolesUri == null) {
-                       nodeRolesUri = baseNodeRoleDn + ".ldif";
-                       File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
-                       if (!nodeRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
-                                                       nodeRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeRolesUri);
-
-               // node tokens
-               String nodeTokensUri = getFrameworkProp(NodeConstants.TOKENS_URI);
-               String baseNodeTokensDn = NodeConstants.TOKENS_BASEDN;
-               if (nodeTokensUri == null) {
-                       nodeTokensUri = baseNodeTokensDn + ".ldif";
-                       File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
-                       if (!nodeTokensFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
-                                                       nodeTokensFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resource", e);
-                               }
-                       // nodeRolesUri = nodeRolesFile.toURI().toString();
-               }
-               uris.add(nodeTokensUri);
-
-               // Business roles
-               String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS);
-               if (userAdminUris == null) {
-                       String demoBaseDn = "dc=example,dc=com";
-                       userAdminUris = demoBaseDn + ".ldif";
-                       File businessRolesFile = new File(nodeBaseDir, userAdminUris);
-                       File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
-                       if (!businessRolesFile.exists())
-                               try {
-                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
-                                                       businessRolesFile);
-                                       if (!systemRolesFile.exists())
-                                               FileUtils.copyInputStreamToFile(
-                                                               InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
-                               } catch (IOException e) {
-                                       throw new RuntimeException("Cannot copy demo resources", e);
-                               }
-                       // userAdminUris = businessRolesFile.toURI().toString();
-                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
-                       // TODO downgrade security level
-               }
-               for (String userAdminUri : userAdminUris.split(" "))
-                       uris.add(userAdminUri);
-
-               // Interprets URIs
-               for (String uri : uris) {
-                       URI u;
-                       try {
-                               u = new URI(uri);
-                               if (u.getPath() == null)
-                                       throw new IllegalArgumentException(
-                                                       "URI " + uri + " must have a path in order to determine base DN");
-                               if (u.getScheme() == null) {
-                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
-                                               u = new File(uri).getCanonicalFile().toURI();
-                                       else if (!uri.contains("/")) {
-                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
-                                               u = new URI(uri);
-                                       } else
-                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
-                               } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
-                                       u = new File(u).getCanonicalFile().toURI();
-                               }
-                       } catch (Exception e) {
-                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
-                       }
-                       Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
-                       res.add(properties);
-               }
-
-               return res;
-       }
-
-       /**
-        * Called before node initialisation, in order populate OSGi instance are with
-        * some files (typically LDIF, etc).
-        */
-       static void prepareFirstInitInstanceArea() {
-               String nodeInits = getFrameworkProp(NodeConstants.NODE_INIT);
-               if (nodeInits == null)
-                       nodeInits = "../../init";
-
-               for (String nodeInit : nodeInits.split(",")) {
-
-                       if (nodeInit.startsWith("http")) {
-                               registerRemoteInit(nodeInit);
-                       } else {
-
-                               // TODO use java.nio.file
-                               File initDir;
-                               if (nodeInit.startsWith("."))
-                                       initDir = KernelUtils.getExecutionDir(nodeInit);
-                               else
-                                       initDir = new File(nodeInit);
-                               // TODO also uncompress archives
-                               if (initDir.exists())
-                                       try {
-                                               FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
-
-                                                       @Override
-                                                       public boolean accept(File pathname) {
-                                                               if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
-                                                                       return false;
-                                                               return true;
-                                                       }
-                                               });
-                                               log.info("CMS initialized from " + initDir.getCanonicalPath());
-                                       } catch (IOException e) {
-                                               throw new RuntimeException("Cannot initialize from " + initDir, e);
-                                       }
-                       }
-               }
-       }
-
-       private static void registerRemoteInit(String uri) {
-               try {
-                       BundleContext bundleContext = KernelUtils.getBundleContext();
-                       Repository repository = createRemoteRepository(new URI(uri));
-                       Hashtable<String, Object> properties = new Hashtable<>();
-                       properties.put(NodeConstants.CN, NodeConstants.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, NodeConstants.SYS_WORKSPACE);
-               return repositoryFactory.getRepository(params);
-       }
-
-       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
-               // for (Provider provider : Security.getProviders())
-               // System.out.println(provider.getName());
-//             File keyStoreFile = keyStorePath.toFile();
-               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
-               if (!Files.exists(keyStorePath)) {
-                       try {
-                               Files.createDirectories(keyStorePath.getParent());
-                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
-                               PkiUtils.generateSelfSignedCertificate(keyStore,
-                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
-                                               1024, keyPwd);
-                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                               if (log.isDebugEnabled())
-                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
-                       } catch (Exception e) {
-                               try {
-                                       if (Files.size(keyStorePath) == 0)
-                                               Files.delete(keyStorePath);
-                               } catch (IOException e1) {
-                                       // silent
-                               }
-                               log.error("Cannot create keystore " + keyStorePath, e);
-                       }
-               } else {
-                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java
deleted file mode 100644 (file)
index 61cf11d..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.api.NodeConstants;
-
-class JackrabbitLocalRepository extends LocalRepository {
-       private final static Log log = LogFactory.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 (!NodeConstants.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/src/org/argeo/cms/internal/kernel/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
deleted file mode 100644 (file)
index 7d14ae9..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import org.argeo.api.NodeConstants;
-
-/** Internal CMS constants. */
-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 + '/' + NodeConstants.DEPLOY_BASEDN + ".ldif";
-       String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + NodeConstants.NODE + ".p12";
-       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + NodeConstants.NODE + ".key";
-       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + NodeConstants.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/src/org/argeo/cms/internal/kernel/KernelThread.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelThread.java
deleted file mode 100644 (file)
index a127866..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.awt.image.Kernel;
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.stats.RepositoryStatistics;
-import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
-import org.argeo.cms.internal.auth.CmsSessionImpl;
-
-/**
- * Background thread started by the {@link Kernel}, which gather statistics and
- * monitor/control other processes.
- */
-class KernelThread extends Thread {
-       private final static Log log = LogFactory.getLog(KernelThread.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 Log kernelStatsLog = LogFactory.getLog("argeo.stats.kernel");
-       private Log nodeStatsLog = LogFactory.getLog("argeo.stats.node");
-
-       @SuppressWarnings("unused")
-       private long cycle = 0l;
-
-       public KernelThread(ThreadGroup threadGroup, String name) {
-               super(threadGroup, name);
-       }
-
-       private void doSmallestPeriod() {
-               // Clean expired sessions
-               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++;
-               }
-       }
-
-       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/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
deleted file mode 100644 (file)
index 7d296ae..0000000
+++ /dev/null
@@ -1,261 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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.apache.commons.logging.Log;
-import org.argeo.api.DataModelNamespace;
-import org.argeo.api.NodeConstants;
-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 = Activator.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(Log 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(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN);
-                       loginContext.login();
-               } catch (LoginException e1) {
-                       throw new IllegalStateException("Could not login as data admin", e1);
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentCl);
-               }
-               return loginContext;
-       }
-
-       static void doAsDataAdmin(Runnable action) {
-               LoginContext loginContext = loginAsDataAdmin();
-               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
-
-                       @Override
-                       public Void run() {
-                               try {
-                                       action.run();
-                                       return null;
-                               } finally {
-                                       try {
-                                               loginContext.logout();
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException(e);
-                                       }
-                               }
-                       }
-
-               });
-       }
-
-       static void asyncOpen(ServiceTracker<?, ?> st) {
-               Runnable run = new Runnable() {
-
-                       @Override
-                       public void run() {
-                               st.open();
-                       }
-               };
-               Activator.getInternalExecutorService().execute(run);
-//             new Thread(run, "Open service tracker " + st).start();
-       }
-
-       static BundleContext getBundleContext() {
-               return Activator.getBundleContext();
-       }
-
-       static boolean asBoolean(String value) {
-               if (value == null)
-                       return false;
-               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/src/org/argeo/cms/internal/kernel/LocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/LocalRepository.java
deleted file mode 100644 (file)
index fd085e2..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import javax.jcr.Repository;
-
-import org.argeo.api.NodeConstants;
-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(NodeConstants.CN, cn);
-       }
-
-       String getCn() {
-               return cn;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeAuthorization.java
deleted file mode 100644 (file)
index ddb9730..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.osgi.service.useradmin.Authorization;
-
-@Deprecated
-class NodeAuthorization implements Authorization {
-       private final String name;
-       private final String displayName;
-       private final List<String> systemRoles;
-       private final List<String> roles;
-
-       public NodeAuthorization(String name, String displayName,
-                       Collection<String> systemRoles, String[] roles) {
-               this.name = new X500Principal(name).getName();
-               this.displayName = displayName;
-               this.systemRoles = Collections.unmodifiableList(new ArrayList<String>(
-                               systemRoles));
-               this.roles = Collections.unmodifiableList(Arrays.asList(roles));
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               if (systemRoles.contains(name))
-                       return true;
-               if (roles.contains(name))
-                       return true;
-               return false;
-       }
-
-       @Override
-       public String[] getRoles() {
-               int size = systemRoles.size() + roles.size();
-               List<String> res = new ArrayList<String>(size);
-               res.addAll(systemRoles);
-               res.addAll(roles);
-               return res.toArray(new String[size]);
-       }
-
-       @Override
-       public int hashCode() {
-               if (name == null)
-                       return super.hashCode();
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof Authorization))
-                       return false;
-               Authorization that = (Authorization) obj;
-               if (name == null)
-                       return that.getName() == null;
-               return name.equals(that.getName());
-       }
-
-       @Override
-       public String toString() {
-               return displayName;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeHttp.java
deleted file mode 100644 (file)
index 353a6c9..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Properties;
-
-import javax.jcr.Repository;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.server.SessionProvider;
-import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
-import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.internal.http.CmsSessionProvider;
-import org.argeo.cms.internal.http.DataHttpContext;
-import org.argeo.cms.internal.http.HttpUtils;
-import org.argeo.cms.internal.http.LinkServlet;
-import org.argeo.cms.internal.http.PrivateHttpContext;
-import org.argeo.cms.internal.http.RobotServlet;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.http.HttpService;
-import org.osgi.service.http.NamespaceException;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * Intercepts and enriches http access, mainly focusing on security and
- * transactionality.
- */
-@Deprecated
-public class NodeHttp implements KernelConstants {
-       private final static Log log = LogFactory.getLog(NodeHttp.class);
-
-       private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       private ServiceTracker<Repository, Repository> repositories;
-       private final ServiceTracker<HttpService, HttpService> httpServiceTracker;
-
-       private String httpRealm = "Argeo";
-       private String webDavConfig = HttpUtils.WEBDAV_CONFIG;
-//     private final boolean cleanState;
-
-       public NodeHttp() {
-//             this.cleanState = cleanState;
-               httpServiceTracker = new PrepareHttpStc();
-               // httpServiceTracker.open();
-               KernelUtils.asyncOpen(httpServiceTracker);
-       }
-
-       public void destroy() {
-               if (repositories != null)
-                       repositories.close();
-       }
-
-       public void registerRepositoryServlets(HttpService httpService, String alias, Repository repository) {
-               if (httpService == null)
-                       throw new CmsException("No HTTP service available");
-               try {
-                       registerWebdavServlet(httpService, alias, repository);
-                       registerRemotingServlet(httpService, alias, repository);
-                       if (NodeConstants.EGO_REPOSITORY.equals(alias))
-                               registerFilesServlet(httpService, alias, repository);
-                       if (log.isTraceEnabled())
-                               log.trace("Registered servlets for repository '" + alias + "'");
-               } catch (Exception e) {
-                       throw new CmsException("Could not register servlets for repository '" + alias + "'", e);
-               }
-       }
-
-       public static void unregisterRepositoryServlets(HttpService httpService, String alias) {
-               if (httpService == null)
-                       return;
-               try {
-                       httpService.unregister(webdavPath(alias));
-                       httpService.unregister(remotingPath(alias));
-                       if (NodeConstants.EGO_REPOSITORY.equals(alias))
-                               httpService.unregister(filesPath(alias));
-                       if (log.isTraceEnabled())
-                               log.trace("Unregistered servlets for repository '" + alias + "'");
-               } catch (Exception e) {
-                       log.error("Could not unregister servlets for repository '" + alias + "'", e);
-               }
-       }
-
-       void registerWebdavServlet(HttpService httpService, String alias, Repository repository)
-                       throws NamespaceException, ServletException {
-               // WebdavServlet webdavServlet = new WebdavServlet(repository, new
-               // OpenInViewSessionProvider(alias));
-               WebdavServlet webdavServlet = new WebdavServlet(repository, new CmsSessionProvider(alias));
-               String path = webdavPath(alias);
-               Properties ip = new Properties();
-               ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
-               ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
-               httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext(httpRealm));
-       }
-
-       void registerFilesServlet(HttpService httpService, String alias, Repository repository)
-                       throws NamespaceException, ServletException {
-               WebdavServlet filesServlet = new WebdavServlet(repository, new CmsSessionProvider(alias));
-               String path = filesPath(alias);
-               Properties ip = new Properties();
-               ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig);
-               ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
-               httpService.registerServlet(path, filesServlet, ip, new PrivateHttpContext(httpRealm, true));
-       }
-
-       void registerRemotingServlet(HttpService httpService, String alias, Repository repository)
-                       throws NamespaceException, ServletException {
-               RemotingServlet remotingServlet = new RemotingServlet(repository, new CmsSessionProvider(alias));
-               String path = remotingPath(alias);
-               Properties ip = new Properties();
-               ip.setProperty(JcrRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
-               ip.setProperty(JcrRemotingServlet.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 CmsException("Cannot create temp directory for remoting servlet", e);
-               }
-               ip.setProperty(RemotingServlet.INIT_PARAM_HOME, tmpDir.toString());
-               ip.setProperty(RemotingServlet.INIT_PARAM_TMP_DIRECTORY, "remoting_" + alias);
-               ip.setProperty(RemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, HttpUtils.DEFAULT_PROTECTED_HANDLERS);
-               ip.setProperty(RemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false");
-               httpService.registerServlet(path, remotingServlet, ip, new PrivateHttpContext(httpRealm));
-       }
-
-       static String webdavPath(String alias) {
-               return NodeConstants.PATH_DATA + "/" + alias;
-       }
-
-       static String remotingPath(String alias) {
-               return NodeConstants.PATH_JCR + "/" + alias;
-       }
-
-       static String filesPath(String alias) {
-               return NodeConstants.PATH_FILES;
-       }
-
-       class RepositoriesStc extends ServiceTracker<Repository, Repository> {
-               private final HttpService httpService;
-
-               private final BundleContext bc;
-
-               public RepositoriesStc(BundleContext bc, HttpService httpService) {
-                       super(bc, Repository.class, null);
-                       this.httpService = httpService;
-                       this.bc = bc;
-               }
-
-               @Override
-               public Repository addingService(ServiceReference<Repository> reference) {
-                       Repository repository = bc.getService(reference);
-                       Object jcrRepoAlias = reference.getProperty(NodeConstants.CN);
-                       if (jcrRepoAlias != null) {
-                               String alias = jcrRepoAlias.toString();
-                               registerRepositoryServlets(httpService, alias, repository);
-                       }
-                       return repository;
-               }
-
-               @Override
-               public void modifiedService(ServiceReference<Repository> reference, Repository service) {
-               }
-
-               @Override
-               public void removedService(ServiceReference<Repository> reference, Repository service) {
-                       Object jcrRepoAlias = reference.getProperty(NodeConstants.CN);
-                       if (jcrRepoAlias != null) {
-                               String alias = jcrRepoAlias.toString();
-                               unregisterRepositoryServlets(httpService, alias);
-                       }
-               }
-       }
-
-       private class PrepareHttpStc extends ServiceTracker<HttpService, HttpService> {
-               public PrepareHttpStc() {
-                       super(bc, HttpService.class, null);
-               }
-
-               @Override
-               public HttpService addingService(ServiceReference<HttpService> reference) {
-                       long begin = System.currentTimeMillis();
-                       if (log.isTraceEnabled())
-                               log.trace("HTTP prepare starts...");
-                       HttpService httpService = addHttpService(reference);
-                       if (log.isTraceEnabled())
-                               log.trace("HTTP prepare duration: " + (System.currentTimeMillis() - begin) + "ms");
-                       return httpService;
-               }
-
-               @Override
-               public void removedService(ServiceReference<HttpService> reference, HttpService service) {
-                       repositories.close();
-                       repositories = null;
-               }
-
-               private HttpService addHttpService(ServiceReference<HttpService> sr) {
-                       HttpService httpService = bc.getService(sr);
-                       // TODO find constants
-                       Object httpPort = sr.getProperty("http.port");
-                       Object httpsPort = sr.getProperty("https.port");
-
-                       try {
-                               httpService.registerServlet("/!", new LinkServlet(), null, null);
-                               httpService.registerServlet("/robots.txt", new RobotServlet(), null, null);
-                               // httpService.registerServlet("/html", new HtmlServlet(), null, null);
-                       } catch (Exception e) {
-                               throw new CmsException("Cannot register filters", e);
-                       }
-                       // track repositories
-                       if (repositories != null)
-                               throw new CmsException("An http service is already configured");
-                       repositories = new RepositoriesStc(bc, httpService);
-                       // repositories.open();
-
-                       ///if (cleanState)
-                       // FIXME properly publish servlets
-                       //KernelUtils.asyncOpen(repositories);
-
-                       log.info(httpPortsMsg(httpPort, httpsPort));
-                       // httpAvailable = true;
-                       // checkReadiness();
-
-                       bc.registerService(NodeHttp.class, NodeHttp.this, null);
-                       return httpService;
-               }
-
-               private String httpPortsMsg(Object httpPort, Object httpsPort) {
-                       return (httpPort != null ? "HTTP " + httpPort + " " : " ")
-                                       + (httpsPort != null ? "HTTPS " + httpsPort : "");
-               }
-       }
-
-       private static class WebdavServlet extends SimpleWebdavServlet {
-               private static final long serialVersionUID = -4687354117811443881L;
-               private final Repository repository;
-
-               public WebdavServlet(Repository repository, SessionProvider sessionProvider) {
-                       this.repository = repository;
-                       setSessionProvider(sessionProvider);
-               }
-
-               public Repository getRepository() {
-                       return repository;
-               }
-
-               @Override
-               protected void service(final HttpServletRequest request, final HttpServletResponse response)
-                               throws ServletException, IOException {
-                       WebdavServlet.super.service(request, response);
-                       // try {
-                       // Subject subject = subjectFromRequest(request);
-                       // // TODO make it stronger, with eTags.
-                       // // if (CurrentUser.isAnonymous(subject) &&
-                       // // request.getMethod().equals("GET")) {
-                       // // response.setHeader("Cache-Control", "no-transform, public,
-                       // // max-age=300, s-maxage=900");
-                       // // }
-                       //
-                       // Subject.doAs(subject, new PrivilegedExceptionAction<Void>() {
-                       // @Override
-                       // public Void run() throws Exception {
-                       // WebdavServlet.super.service(request, response);
-                       // return null;
-                       // }
-                       // });
-                       // } catch (PrivilegedActionException e) {
-                       // throw new CmsException("Cannot process webdav request",
-                       // e.getException());
-                       // }
-               }
-
-       }
-
-       private static class RemotingServlet extends JcrRemotingServlet {
-               private final Log log = LogFactory.getLog(RemotingServlet.class);
-               private static final long serialVersionUID = 4605238259548058883L;
-               private final Repository repository;
-               private final SessionProvider sessionProvider;
-
-               public RemotingServlet(Repository repository, SessionProvider sessionProvider) {
-                       this.repository = repository;
-                       this.sessionProvider = sessionProvider;
-               }
-
-               @Override
-               protected Repository getRepository() {
-                       return repository;
-               }
-
-               @Override
-               protected SessionProvider getSessionProvider() {
-                       return sessionProvider;
-               }
-
-               @Override
-               protected void service(final HttpServletRequest request, final HttpServletResponse response)
-                               throws ServletException, IOException {
-                       if (log.isTraceEnabled())
-                               HttpUtils.logRequest(log, request);
-                       RemotingServlet.super.service(request, response);
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeKeyRing.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeKeyRing.java
deleted file mode 100644 (file)
index 0b774d9..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.util.Dictionary;
-
-import javax.jcr.Repository;
-
-import org.argeo.cms.security.JcrKeyring;
-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/src/org/argeo/cms/internal/kernel/NodeLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeLogger.java
deleted file mode 100644 (file)
index fef7a7a..0000000
+++ /dev/null
@@ -1,549 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.security.SignatureException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.log4j.AppenderSkeleton;
-import org.apache.log4j.Level;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.Logger;
-import org.apache.log4j.PropertyConfigurator;
-import org.apache.log4j.spi.LoggingEvent;
-import org.argeo.api.ArgeoLogListener;
-import org.argeo.api.ArgeoLogger;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-import org.argeo.cms.auth.CurrentUser;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.cm.ConfigurationAdmin;
-import org.osgi.service.log.LogEntry;
-import org.osgi.service.log.LogLevel;
-import org.osgi.service.log.LogListener;
-import org.osgi.service.log.LogReaderService;
-
-/** Not meant to be used directly in standard log4j config */
-class NodeLogger implements ArgeoLogger, LogListener {
-       /** Internal debug for development purposes. */
-       private static Boolean debug = false;
-
-       // private final static Log log = LogFactory.getLog(NodeLogger.class);
-
-       private Boolean disabled = false;
-
-       private String level = null;
-
-       private Level log4jLevel = null;
-       // private Layout layout;
-
-       private Properties configuration;
-
-       private AppenderImpl appender;
-
-       private final List<ArgeoLogListener> everythingListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final List<ArgeoLogListener> allUsersListeners = Collections
-                       .synchronizedList(new ArrayList<ArgeoLogListener>());
-       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
-                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
-
-       private BlockingQueue<LogEvent> events;
-       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
-
-       private Integer maxLastEventsCount = 10 * 1000;
-
-       /** Marker to prevent stack overflow */
-       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
-
-               @Override
-               protected Boolean initialValue() {
-                       return false;
-               }
-       };
-
-       public NodeLogger(LogReaderService lrs) {
-               if (lrs != null) {
-                       Enumeration<LogEntry> logEntries = lrs.getLog();
-                       while (logEntries.hasMoreElements())
-                               logged(logEntries.nextElement());
-                       lrs.addLogListener(this);
-
-                       // configure log4j watcher
-                       String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
-                       if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
-                               if (log4jConfiguration.contains("..")) {
-                                       if (log4jConfiguration.startsWith("file://"))
-                                               log4jConfiguration = log4jConfiguration.substring("file://".length());
-                                       else if (log4jConfiguration.startsWith("file:"))
-                                               log4jConfiguration = log4jConfiguration.substring("file:".length());
-                               }
-                               try {
-                                       Path log4jconfigPath;
-                                       if (log4jConfiguration.startsWith("file:"))
-                                               log4jconfigPath = Paths.get(new URI(log4jConfiguration));
-                                       else
-                                               log4jconfigPath = Paths.get(log4jConfiguration);
-                                       Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
-                                       log4jConfWatcher.start();
-                               } catch (Exception e) {
-                                       stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
-                               }
-                       }
-               }
-       }
-
-       public void init() {
-               try {
-                       events = new LinkedBlockingQueue<LogEvent>();
-
-                       // if (layout != null)
-                       // setLayout(layout);
-                       // else
-                       // setLayout(new PatternLayout(pattern));
-                       appender = new AppenderImpl();
-                       reloadConfiguration();
-                       Logger.getRootLogger().addAppender(appender);
-
-                       logDispatcherThread = new LogDispatcherThread();
-                       logDispatcherThread.start();
-               } catch (Exception e) {
-                       throw new CmsException("Cannot initialize log4j");
-               }
-       }
-
-       public void destroy() throws Exception {
-               Logger.getRootLogger().removeAppender(appender);
-               allUsersListeners.clear();
-               for (List<ArgeoLogListener> lst : userListeners.values())
-                       lst.clear();
-               userListeners.clear();
-
-               events.clear();
-               events = null;
-               logDispatcherThread.interrupt();
-       }
-
-       // public void setLayout(Layout layout) {
-       // this.layout = layout;
-       // }
-
-       public String toString() {
-               return "Node Logger";
-       }
-
-       //
-       // OSGi LOGGER
-       //
-       @Override
-       public void logged(LogEntry status) {
-               Log pluginLog = LogFactory.getLog(status.getBundle().getSymbolicName());
-               LogLevel severity = status.getLogLevel();
-               if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) {
-                       // FIXME Fix Argeo TP
-                       if (status.getException() instanceof SignatureException)
-                               return;
-                       pluginLog.error(msg(status), status.getException());
-               } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) {
-                       if (pluginLog.isTraceEnabled())
-                               pluginLog.warn(msg(status), status.getException());
-                       else
-                               pluginLog.warn(msg(status));
-               } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled())
-                       pluginLog.debug(msg(status), status.getException());
-               else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled())
-                       pluginLog.trace(msg(status), status.getException());
-               else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled())
-                       pluginLog.trace(msg(status), status.getException());
-       }
-
-       private String msg(LogEntry status) {
-               StringBuilder sb = new StringBuilder();
-               sb.append(status.getMessage());
-               Bundle bundle = status.getBundle();
-               if (bundle != null) {
-                       sb.append(" '" + bundle.getSymbolicName() + "'");
-               }
-               ServiceReference<?> sr = status.getServiceReference();
-               if (sr != null) {
-                       sb.append(' ');
-                       String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
-                       if (isSpringApplicationContext(objectClasses)) {
-                               sb.append("{org.springframework.context.ApplicationContext}");
-                               Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
-                               if (symbolicName != null)
-                                       sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
-                       } else {
-                               sb.append(arrayToString(objectClasses));
-                       }
-                       Object cn = sr.getProperty(NodeConstants.CN);
-                       if (cn != null)
-                               sb.append(" " + NodeConstants.CN + ": " + cn);
-                       Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
-                       if (factoryPid != null)
-                               sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
-                       // else {
-                       // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
-                       // if (servicePid != null)
-                       // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
-                       // }
-                       // servlets
-                       Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
-                       if (whiteBoardPattern != null) {
-                               if (whiteBoardPattern instanceof String) {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
-                               } else {
-                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
-                                                       + arrayToString((String[]) whiteBoardPattern));
-                               }
-                       }
-                       // RWT
-                       Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
-                       if (contextName != null)
-                               sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
-
-                       // user directories
-                       Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
-                       if (baseDn != null)
-                               sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
-
-               }
-               return sb.toString();
-       }
-
-       private String arrayToString(Object[] arr) {
-               StringBuilder sb = new StringBuilder();
-               sb.append('[');
-               for (int i = 0; i < arr.length; i++) {
-                       if (i != 0)
-                               sb.append(',');
-                       sb.append(arr[i]);
-               }
-               sb.append(']');
-               return sb.toString();
-       }
-
-       private boolean isSpringApplicationContext(String[] objectClasses) {
-               for (String clss : objectClasses) {
-                       if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-       //
-       // ARGEO LOGGER
-       //
-
-       public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
-               String username = CurrentUser.getUsername();
-               if (username == null)
-                       throw new CmsException("Only authenticated users can register a log listener");
-
-               if (!userListeners.containsKey(username)) {
-                       List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
-                       userListeners.put(username, lst);
-               }
-               userListeners.get(username).add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       dispatchEvent(listener, evt);
-       }
-
-       public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
-                       boolean everything) {
-               if (everything)
-                       everythingListeners.add(listener);
-               else
-                       allUsersListeners.add(listener);
-               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
-               for (LogEvent evt : lastEvents)
-                       if (everything || evt.getUsername() != null)
-                               dispatchEvent(listener, evt);
-       }
-
-       public synchronized void unregister(ArgeoLogListener listener) {
-               String username = CurrentUser.getUsername();
-               if (username == null)// FIXME
-                       return;
-               if (!userListeners.containsKey(username))
-                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
-               if (!userListeners.get(username).contains(listener))
-                       throw new CmsException("No user listeners " + listener + " registered for user " + username);
-               userListeners.get(username).remove(listener);
-               if (userListeners.get(username).isEmpty())
-                       userListeners.remove(username);
-
-       }
-
-       public synchronized void unregisterForAll(ArgeoLogListener listener) {
-               everythingListeners.remove(listener);
-               allUsersListeners.remove(listener);
-       }
-
-       /** For development purpose, since using regular logging is not easy here */
-       private static void stdOut(Object obj) {
-               System.out.println(obj);
-       }
-
-       private static void stdErr(Object obj) {
-               System.err.println(obj);
-       }
-
-       private static void debug(Object obj) {
-               if (debug)
-                       System.out.println(obj);
-       }
-
-       private static boolean isInternalDebugEnabled() {
-               return debug;
-       }
-
-       // public void setPattern(String pattern) {
-       // this.pattern = pattern;
-       // }
-
-       public void setDisabled(Boolean disabled) {
-               this.disabled = disabled;
-       }
-
-       public void setLevel(String level) {
-               this.level = level;
-       }
-
-       public void setConfiguration(Properties configuration) {
-               this.configuration = configuration;
-       }
-
-       public void updateConfiguration(Properties configuration) {
-               setConfiguration(configuration);
-               reloadConfiguration();
-       }
-
-       public Properties getConfiguration() {
-               return configuration;
-       }
-
-       /**
-        * Reloads configuration (if the configuration {@link Properties} is set)
-        */
-       protected void reloadConfiguration() {
-               if (configuration != null) {
-                       LogManager.resetConfiguration();
-                       PropertyConfigurator.configure(configuration);
-               }
-       }
-
-       protected synchronized void processLoggingEvent(LogEvent event) {
-               if (disabled)
-                       return;
-
-               if (dispatching.get())
-                       return;
-
-               if (level != null && !level.trim().equals("")) {
-                       if (log4jLevel == null || !log4jLevel.toString().equals(level))
-                               try {
-                                       log4jLevel = Level.toLevel(level);
-                               } catch (Exception e) {
-                                       System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
-                                       e.printStackTrace();
-                                       level = null;
-                               }
-
-                       if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
-                               return;
-                       }
-               }
-
-               try {
-                       // admin listeners
-                       Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
-                       while (everythingIt.hasNext())
-                               dispatchEvent(everythingIt.next(), event);
-
-                       if (event.getUsername() != null) {
-                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
-                               while (allUsersIt.hasNext())
-                                       dispatchEvent(allUsersIt.next(), event);
-
-                               if (userListeners.containsKey(event.getUsername())) {
-                                       Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
-                                       while (userIt.hasNext())
-                                               dispatchEvent(userIt.next(), event);
-                               }
-                       }
-               } catch (Exception e) {
-                       stdOut("Cannot process logging event");
-                       e.printStackTrace();
-               }
-       }
-
-       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
-               LoggingEvent event = evt.getLoggingEvent();
-               logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
-                               event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
-       }
-
-       private class AppenderImpl extends AppenderSkeleton {
-               public boolean requiresLayout() {
-                       return false;
-               }
-
-               public void close() {
-               }
-
-               @Override
-               protected void append(LoggingEvent event) {
-                       if (events != null) {
-                               try {
-                                       String username = CurrentUser.getUsername();
-                                       events.put(new LogEvent(username, event));
-                               } catch (InterruptedException e) {
-                                       // silent
-                               }
-                       }
-               }
-
-       }
-
-       private class LogDispatcherThread extends Thread {
-               /** encapsulated in order to simplify concurrency management */
-               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
-
-               public LogDispatcherThread() {
-                       super("Argeo Logging Dispatcher Thread");
-               }
-
-               public void run() {
-                       while (events != null) {
-                               try {
-                                       LogEvent loggingEvent = events.take();
-                                       processLoggingEvent(loggingEvent);
-                                       addLastEvent(loggingEvent);
-                               } catch (InterruptedException e) {
-                                       if (events == null)
-                                               return;
-                               }
-                       }
-               }
-
-               protected synchronized void addLastEvent(LogEvent loggingEvent) {
-                       if (lastEvents.size() >= maxLastEventsCount)
-                               lastEvents.poll();
-                       lastEvents.add(loggingEvent);
-               }
-
-               public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
-                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
-                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
-                       int count = 0;
-                       while (it.hasPrevious() && (count < maxCount)) {
-                               LogEvent evt = it.previous();
-                               if (username == null || username.equals(evt.getUsername())) {
-                                       evts.push(evt);
-                                       count++;
-                               }
-                       }
-                       return evts;
-               }
-       }
-
-       private class LogEvent {
-               private final String username;
-               private final LoggingEvent loggingEvent;
-
-               public LogEvent(String username, LoggingEvent loggingEvent) {
-                       super();
-                       this.username = username;
-                       this.loggingEvent = loggingEvent;
-               }
-
-               @Override
-               public int hashCode() {
-                       return loggingEvent.hashCode();
-               }
-
-               @Override
-               public boolean equals(Object obj) {
-                       return loggingEvent.equals(obj);
-               }
-
-               @Override
-               public String toString() {
-                       return username + "@ " + loggingEvent.toString();
-               }
-
-               public String getUsername() {
-                       return username;
-               }
-
-               public LoggingEvent getLoggingEvent() {
-                       return loggingEvent;
-               }
-
-       }
-
-       private class Log4jConfWatcherThread extends Thread {
-               private Path log4jConfigurationPath;
-
-               public Log4jConfWatcherThread(Path log4jConfigurationPath) {
-                       super("Log4j Configuration Watcher");
-                       try {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
-                       } catch (IOException e) {
-                               this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
-                               stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
-                       }
-               }
-
-               public void run() {
-                       Path parentDir = log4jConfigurationPath.getParent();
-                       try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
-                               parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
-                               WatchKey wk;
-                               watching: while ((wk = watchService.take()) != null) {
-                                       for (WatchEvent<?> event : wk.pollEvents()) {
-                                               final Path changed = (Path) event.context();
-                                               if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
-                                                       if (isInternalDebugEnabled())
-                                                               debug(log4jConfigurationPath + " has changed, reloading.");
-                                                       PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
-                                               }
-                                       }
-                                       // reset the key
-                                       boolean valid = wk.reset();
-                                       if (!valid) {
-                                               break watching;
-                                       }
-                               }
-                       } catch (IOException | InterruptedException e) {
-                               stdErr("Log4j configuration watcher failed: " + e.getMessage());
-                       }
-               }
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepositoryFactory.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeRepositoryFactory.java
deleted file mode 100644 (file)
index efbb724..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-
-/**
- * OSGi-aware Jackrabbit repository factory which can retrieve/publish
- * {@link Repository} as OSGi services.
- */
-class NodeRepositoryFactory implements RepositoryFactory {
-       private final Log log = LogFactory.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 = Activator.getBundleContext();
-               if (bundleContext != null) {
-                       try {
-                               Collection<ServiceReference<Repository>> srs = bundleContext.getServiceReferences(Repository.class,
-                                               "(" + NodeConstants.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(NodeConstants.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(NodeConstants.CN)) {
-                       // Properties properties = new Properties();
-                       // properties.putAll(parameters);
-                       String alias = parameters.get(NodeConstants.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/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeUserAdmin.java
deleted file mode 100644 (file)
index 1a98174..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-import javax.transaction.TransactionManager;
-
-import org.apache.commons.httpclient.auth.AuthPolicy;
-import org.apache.commons.httpclient.auth.CredentialsProvider;
-import org.apache.commons.httpclient.params.DefaultHttpParams;
-import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.params.HttpParams;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.internal.http.client.HttpCredentialProvider;
-import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
-import org.argeo.naming.DnsBrowser;
-import org.argeo.osgi.useradmin.AbstractUserDirectory;
-import org.argeo.osgi.useradmin.AggregatingUserAdmin;
-import org.argeo.osgi.useradmin.LdapUserAdmin;
-import org.argeo.osgi.useradmin.LdifUserAdmin;
-import org.argeo.osgi.useradmin.OsUserDirectory;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.argeo.osgi.useradmin.UserDirectory;
-import org.ietf.jgss.GSSCredential;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.GSSManager;
-import org.ietf.jgss.GSSName;
-import org.ietf.jgss.Oid;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.service.cm.ConfigurationException;
-import org.osgi.service.cm.ManagedServiceFactory;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.UserAdmin;
-import org.osgi.util.tracker.ServiceTracker;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
-       private final static Log log = LogFactory.getLog(NodeUserAdmin.class);
-//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
-
-       // OSGi
-       private Map<String, LdapName> pidToBaseDn = new HashMap<>();
-//     private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
-//     private ServiceRegistration<UserAdmin> userAdminReg;
-
-       // JTA
-       private final ServiceTracker<TransactionManager, TransactionManager> tmTracker;
-       // private final String cacheName = UserDirectory.class.getName();
-
-       // GSS API
-       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
-       private GSSCredential acceptorCredentials;
-
-       private boolean singleUser = false;
-//     private boolean systemRolesAvailable = false;
-
-       public NodeUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
-               super(systemRolesBaseDn, tokensBaseDn);
-               BundleContext bc = Activator.getBundleContext();
-               if (bc != null) {
-                       tmTracker = new ServiceTracker<>(bc, TransactionManager.class, null);
-                       tmTracker.open();
-               } else {
-                       tmTracker = null;
-               }
-       }
-
-       @Override
-       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
-               String uri = (String) properties.get(UserAdminConf.uri.name());
-               Object realm = properties.get(UserAdminConf.realm.name());
-               URI u;
-               try {
-                       if (uri == null) {
-                               String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
-                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
-                       } else if (realm != null) {
-                               u = null;
-                       } else {
-                               u = new URI(uri);
-                       }
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
-               }
-
-               // Create
-               AbstractUserDirectory userDirectory;
-               if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
-                               || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
-                       userDirectory = new LdapUserAdmin(properties);
-               } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
-                       userDirectory = new LdifUserAdmin(u, properties);
-               } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
-                       userDirectory = new OsUserDirectory(u, properties);
-                       singleUser = true;
-               } else {
-                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
-               }
-               addUserDirectory(userDirectory);
-
-               // OSGi
-               LdapName baseDn = userDirectory.getBaseDn();
-               Dictionary<String, Object> regProps = new Hashtable<>();
-               regProps.put(Constants.SERVICE_PID, pid);
-               if (isSystemRolesBaseDn(baseDn))
-                       regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               regProps.put(UserAdminConf.baseDn.name(), baseDn);
-               // ServiceRegistration<UserDirectory> reg =
-               // bc.registerService(UserDirectory.class, userDirectory, regProps);
-               Activator.registerService(UserDirectory.class, userDirectory, regProps);
-               pidToBaseDn.put(pid, baseDn);
-               // pidToServiceRegs.put(pid, reg);
-
-               if (log.isDebugEnabled()) {
-                       log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
-                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
-               }
-
-               if (isSystemRolesBaseDn(baseDn)) {
-                       // publishes only when system roles are available
-                       Dictionary<String, Object> userAdminregProps = new Hashtable<>();
-                       userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
-                       userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-                       Activator.registerService(UserAdmin.class, this, userAdminregProps);
-               }
-
-//             if (isSystemRolesBaseDn(baseDn))
-//                     systemRolesAvailable = true;
-//
-//             // start publishing only when system roles are available
-//             if (systemRolesAvailable) {
-//                     // The list of baseDns is published as properties
-//                     // TODO clients should rather reference USerDirectory services
-//                     if (userAdminReg != null)
-//                             userAdminReg.unregister();
-//                     // register self as main user admin
-//                     Dictionary<String, Object> userAdminregProps = currentState();
-//                     userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
-//                     userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-//                     userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
-//             }
-       }
-
-       @Override
-       public void deleted(String pid) {
-               // assert pidToServiceRegs.get(pid) != null;
-               assert pidToBaseDn.get(pid) != null;
-               // pidToServiceRegs.remove(pid).unregister();
-               LdapName baseDn = pidToBaseDn.remove(pid);
-               removeUserDirectory(baseDn);
-       }
-
-       @Override
-       public String getName() {
-               return "Node User Admin";
-       }
-
-       @Override
-       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-               if (rawAuthorization.getName() == null) {
-                       sysRoles.add(NodeConstants.ROLE_ANONYMOUS);
-               } else {
-                       sysRoles.add(NodeConstants.ROLE_USER);
-               }
-       }
-
-       protected void postAdd(AbstractUserDirectory userDirectory) {
-               // JTA
-               TransactionManager tm = tmTracker != null ? tmTracker.getService() : null;
-               if (tm == null)
-                       throw new IllegalStateException("A JTA transaction manager must be available.");
-               userDirectory.setTransactionManager(tm);
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (Files.exists(nodeKeyTab)) {
-                               String servicePrincipal = getKerberosServicePrincipal(realm.toString());
-                               if (servicePrincipal != null) {
-                                       CallbackHandler callbackHandler = new CallbackHandler() {
-                                               @Override
-                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-                                                       for (Callback callback : callbacks)
-                                                               if (callback instanceof NameCallback)
-                                                                       ((NameCallback) callback).setName(servicePrincipal);
-
-                                               }
-                                       };
-                                       try {
-                                               LoginContext nodeLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, callbackHandler);
-                                               nodeLc.login();
-                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
-                                       } catch (LoginException e) {
-                                               throw new IllegalStateException("Cannot log in kernel", e);
-                                       }
-                               }
-                       }
-
-                       // Register client-side SPNEGO auth scheme
-                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
-                       HttpParams params = DefaultHttpParams.getDefaultParams();
-                       ArrayList<String> schemes = new ArrayList<>();
-                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
-                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
-                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
-                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
-                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
-                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
-               }
-       }
-
-       protected void preDestroy(AbstractUserDirectory userDirectory) {
-//             if (tmTracker.getService() instanceof BitronixTransactionManager)
-//                     EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
-
-               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
-               if (realm != null) {
-                       if (acceptorCredentials != null) {
-                               try {
-                                       acceptorCredentials.dispose();
-                               } catch (GSSException e) {
-                                       // silent
-                               }
-                               acceptorCredentials = null;
-                       }
-               }
-       }
-
-       private String getKerberosServicePrincipal(String realm) {
-               String hostname;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
-                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
-                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
-                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
-                       } else
-                               return null;
-               } catch (Exception e) {
-                       log.warn("Exception when determining kerberos principal", e);
-                       return null;
-               }
-       }
-
-       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
-               // GSS
-               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
-               if (!krb5It.hasNext())
-                       return null;
-               KerberosPrincipal krb5Principal = null;
-               while (krb5It.hasNext()) {
-                       KerberosPrincipal principal = krb5It.next();
-                       if (principal.getName().equals(servicePrincipal))
-                               krb5Principal = principal;
-               }
-
-               if (krb5Principal == null)
-                       return null;
-
-               GSSManager manager = GSSManager.getInstance();
-               try {
-                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
-                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
-
-                               @Override
-                               public GSSCredential run() throws GSSException {
-                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
-                                                       GSSCredential.ACCEPT_ONLY);
-
-                               }
-
-                       });
-                       if (log.isDebugEnabled())
-                               log.debug("GSS acceptor configured for " + krb5Principal);
-                       return serverCredentials;
-               } catch (Exception gsse) {
-                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
-               }
-       }
-
-       public GSSCredential getAcceptorCredentials() {
-               return acceptorCredentials;
-       }
-
-       public boolean isSingleUser() {
-               return singleUser;
-       }
-
-       public final static Oid KERBEROS_OID;
-       static {
-               try {
-                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
-               } catch (GSSException e) {
-                       throw new IllegalStateException("Cannot create Kerberos OID", e);
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java
deleted file mode 100644 (file)
index 2105e05..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.math.BigInteger;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.GeneralSecurityException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.SecureRandom;
-import java.security.Security;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.X509v3CertificateBuilder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openssl.PEMParser;
-import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
-import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
-import org.bouncycastle.operator.ContentSigner;
-import org.bouncycastle.operator.InputDecryptorProvider;
-import org.bouncycastle.operator.OperatorCreationException;
-import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
-import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
-import org.bouncycastle.pkcs.PKCSException;
-
-/**
- * Utilities around private keys and certificate, mostly wrapping BouncyCastle
- * implementations.
- */
-class PkiUtils {
-       final static String PKCS12 = "PKCS12";
-
-       private final static String SECURITY_PROVIDER;
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-               SECURITY_PROVIDER = "BC";
-       }
-
-       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
-                       int keySize, char[] keyPassword) {
-               try {
-                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
-                       kpGen.initialize(keySize, new SecureRandom());
-                       KeyPair pair = kpGen.generateKeyPair();
-                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
-                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
-                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
-                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
-                                       notAfter, x500Principal, pair.getPublic());
-                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
-                                       .build(pair.getPrivate());
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certGen.build(sigGen));
-                       cert.checkValidity(new Date());
-                       cert.verify(cert.getPublicKey());
-
-                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
-                       return cert;
-               } catch (GeneralSecurityException | OperatorCreationException e) {
-                       throw new RuntimeException("Cannot generate self-signed certificate", e);
-               }
-       }
-
-       public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
-               try {
-                       KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
-                       if (Files.exists(keyStoreFile)) {
-                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
-                                       store.load(fis, keyStorePassword);
-                               }
-                       } else {
-                               store.load(null);
-                       }
-                       return store;
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
-               }
-       }
-
-       public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
-               try {
-                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
-                               keyStore.store(fis, keyStorePassword);
-                       }
-               } catch (GeneralSecurityException | IOException e) {
-                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
-               }
-       }
-
-//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
-//                     throws Exception {
-//             // Get the private key
-//             FileReader reader = new FileReader(keyFile);
-//
-//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
-//                     @Override
-//                     public char[] getPassword() {
-//                             return password.toCharArray();
-//                     }
-//             });
-//
-//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Get the certificate
-//             reader = new FileReader(cerFile);
-//             pem = new PEMReader(reader);
-//
-//             X509Certificate cert = (X509Certificate) pem.readObject();
-//
-//             pem.close();
-//             reader.close();
-//
-//             // Put them into a PKCS12 keystore and write it to a byte[]
-//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
-//             KeyStore ks = KeyStore.getInstance("PKCS12");
-//             ks.load(null);
-//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
-//             ks.store(bos, password.toCharArray());
-//             bos.close();
-//             return bos.toByteArray();
-//     }
-
-       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
-               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
-               X509Certificate certificate = loadPemCertificate(cert);
-               try {
-                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
-                                       new java.security.cert.Certificate[] { certificate });
-               } catch (KeyStoreException e) {
-                       throw new RuntimeException("Cannot store PEM certificate", e);
-               }
-       }
-
-       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
-                       Object object = pemParser.readObject();
-                       PrivateKeyInfo privateKeyInfo;
-                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
-                               if (keyPassword == null)
-                                       throw new IllegalArgumentException("A key password is required");
-                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
-                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
-                       } else if (object instanceof PrivateKeyInfo) {
-                               privateKeyInfo = (PrivateKeyInfo) object;
-                       } else {
-                               throw new IllegalArgumentException("Unsupported format for private key");
-                       }
-                       return converter.getPrivateKey(privateKeyInfo);
-               } catch (IOException | OperatorCreationException | PKCSException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
-
-       public static X509Certificate loadPemCertificate(Reader reader) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
-                                       .getCertificate(certHolder);
-                       return cert;
-               } catch (IOException | CertificateException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
-
-       public static void main(String[] args) throws Exception {
-               final String ALGORITHM = "RSA";
-               final String provider = "BC";
-               SecureRandom secureRandom = new SecureRandom();
-               long begin = System.currentTimeMillis();
-               for (int i = 512; i < 1024; i = i + 2) {
-                       try {
-                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
-                               keyGen.initialize(i, secureRandom);
-                               keyGen.generateKeyPair();
-                       } catch (Exception e) {
-                               System.err.println(i + " : " + e.getMessage());
-                       }
-               }
-               System.out.println((System.currentTimeMillis() - begin) + " ms");
-
-               // // String text = "a";
-               // String text =
-               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
-               // try {
-               // System.out.println(text);
-               // PrivateKey privateKey;
-               // PublicKey publicKey;
-               // char[] password = "changeit".toCharArray();
-               // String alias = "CN=test";
-               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
-               // File p12file = new File("test.p12");
-               // p12file.delete();
-               // if (!p12file.exists()) {
-               // keyStore.load(null);
-               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
-               // 513, password);
-               // try (OutputStream out = new FileOutputStream(p12file)) {
-               // keyStore.store(out, password);
-               // }
-               // }
-               // try (InputStream in = new FileInputStream(p12file)) {
-               // keyStore.load(in, password);
-               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
-               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
-               // }
-               // // KeyPair key;
-               // // final KeyPairGenerator keyGen =
-               // // KeyPairGenerator.getInstance(ALGORITHM);
-               // // keyGen.initialize(4096, new SecureRandom());
-               // // long begin = System.currentTimeMillis();
-               // // key = keyGen.generateKeyPair();
-               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
-               // // keyStore.load(null);
-               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
-               // // try(OutputStream out=new FileOutputStream(p12file)) {
-               // // keyStore.store(out, password);
-               // // }
-               // // privateKey = key.getPrivate();
-               // // publicKey = key.getPublic();
-               //
-               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
-               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
-               // byte[] encrypted = encrypt.doFinal(text.getBytes());
-               // String encryptedBase64 =
-               // Base64.getEncoder().encodeToString(encrypted);
-               // System.out.println(encryptedBase64);
-               // byte[] encryptedFromBase64 =
-               // Base64.getDecoder().decode(encryptedBase64);
-               //
-               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
-               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
-               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
-               // System.out.println(new String(decrypted));
-               // } catch (Exception e) {
-               // e.printStackTrace();
-               // }
-
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/RepositoryServiceFactory.java
deleted file mode 100644 (file)
index 630a453..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.net.URI;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.core.RepositoryContext;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.internal.jcr.RepoConf;
-import org.argeo.cms.internal.jcr.RepositoryBuilder;
-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. */
-class RepositoryServiceFactory implements ManagedServiceFactory {
-       private final static Log log = LogFactory.getLog(RepositoryServiceFactory.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>();
-
-       @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;
-
-               if (repositories.containsKey(pid)) {
-                       log.warn("Ignore update of Jackrabbit repository " + pid);
-                       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()));
-                               Object cn = properties.get(NodeConstants.CN);
-                               if (cn != null) {
-                                       props.put(NodeConstants.CN, cn);
-                                       // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
-                                       pidToCn.put(pid, cn);
-                               }
-                               Activator.registerService(RepositoryContext.class, repositoryContext, props);
-                       } else {
-                               try {
-                                       Object cn = properties.get(NodeConstants.CN);
-                                       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 = Activator.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(NodeConstants.CN, cn);
-                                               // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn);
-                                               pidToCn.put(pid, cn);
-                                       }
-                                       Activator.registerService(Repository.class, repository, props);
-
-                                       // home
-                                       if (cn.equals(NodeConstants.NODE_REPOSITORY)) {
-                                               Dictionary<String, Object> homeProps = LangUtils.dict(NodeConstants.CN,
-                                                               NodeConstants.EGO_REPOSITORY);
-                                               EgoRepository homeRepository = new EgoRepository(repository, true);
-                                               Activator.registerService(Repository.class, homeRepository, homeProps);
-                                       }
-                               } catch (Exception e) {
-                                       // TODO Auto-generated catch block
-                                       e.printStackTrace();
-                               }
-                       }
-               } catch (Exception 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);
-       }
-
-       public void shutdown() {
-               for (String pid : repositories.keySet()) {
-                       try {
-                               repositories.get(pid).getRepository().shutdown();
-                               if (log.isDebugEnabled())
-                                       log.debug("Shut down repository " + pid
-                                                       + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : ""));
-                       } catch (Exception e) {
-                               log.error("Error when shutting down Jackrabbit repository " + pid, e);
-                       }
-               }
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/SecurityProfile.java
deleted file mode 100644 (file)
index 127cb9a..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-package org.argeo.cms.internal.kernel;
-
-import java.io.FilePermission;
-import java.lang.reflect.ReflectPermission;
-import java.net.SocketPermission;
-import java.security.AllPermission;
-import java.util.PropertyPermission;
-
-import javax.security.auth.AuthPermission;
-
-import org.argeo.api.NodeUtils;
-import org.osgi.framework.AdminPermission;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.ServicePermission;
-import org.osgi.service.cm.ConfigurationPermission;
-import org.osgi.service.condpermadmin.BundleLocationCondition;
-import org.osgi.service.condpermadmin.ConditionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
-import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
-import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
-import org.osgi.service.permissionadmin.PermissionAdmin;
-import org.osgi.service.permissionadmin.PermissionInfo;
-
-/** Security profile based on OSGi {@link PermissionAdmin}. */
-public interface SecurityProfile {
-       BundleContext bc = FrameworkUtil.getBundle(SecurityProfile.class).getBundleContext();
-
-       default void applySystemPermissions(ConditionalPermissionAdmin permissionAdmin) {
-               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
-               // Self
-               String nodeAPiBundleLocation = locate(NodeUtils.class);
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { nodeAPiBundleLocation }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-               String cmsBundleLocation = locate(SecurityProfile.class);
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { cmsBundleLocation }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-               String frameworkBundleLocation = bc.getBundle(0).getLocation();
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { frameworkBundleLocation }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
-                                               ConditionalPermissionInfo.ALLOW));
-               // All
-               // FIXME understand why Jetty and Jackrabbit require that
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null, null, new PermissionInfo[] {
-                                               new PermissionInfo(SocketPermission.class.getName(), "localhost:7070", "listen,resolve"),
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "DEBUG", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "STOP.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "org.apache.jackrabbit.*", "read"),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Eclipse
-               // update.getConditionalPermissionInfos()
-               // .add(permissionAdmin.newConditionalPermissionInfo(null,
-               // new ConditionInfo[] { new
-               // ConditionInfo(BundleLocationCondition.class.getName(),
-               // new String[] { "*/org.eclipse.*" }) },
-               // new PermissionInfo[] { new
-               // PermissionInfo(RuntimePermission.class.getName(), "*", "*"),
-               // new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-               // new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-               // new PermissionInfo(ServicePermission.class.getName(), "*",
-               // "register"),
-               // new PermissionInfo(TopicPermission.class.getName(), "*", "publish"),
-               // new PermissionInfo(TopicPermission.class.getName(), "*",
-               // "subscribe"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "osgi.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(),
-               // "org.eclipse.*", "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "equinox.*",
-               // "read"),
-               // new PermissionInfo(PropertyPermission.class.getName(), "xml.*",
-               // "read"),
-               // new PermissionInfo("org.eclipse.equinox.log.LogPermission", "*",
-               // "log"), },
-               // ConditionalPermissionInfo.ALLOW));
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { "*/org.eclipse.*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
-                                               ConditionalPermissionInfo.ALLOW));
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { "*/org.apache.felix.*" }) },
-                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Configuration admin
-//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                             new String[] { locate(configurationAdmin.getService().getClass()) }) },
-//                             new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
-//                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-//                                             new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"), },
-//                             ConditionalPermissionInfo.ALLOW));
-
-               // Bitronix
-//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                             new String[] { locate(BitronixTransactionManager.class) }) },
-//                             new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "bitronix.tm.*", "read"),
-//                                             new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
-//                                             new PermissionInfo(MBeanServerPermission.class.getName(), "createMBeanServer", null),
-//                                             new PermissionInfo(MBeanPermission.class.getName(), "bitronix.tm.*", "registerMBean"),
-//                                             new PermissionInfo(MBeanTrustPermission.class.getName(), "register", null) },
-//                             ConditionalPermissionInfo.ALLOW));
-
-               // DS
-               Bundle dsBundle = findBundle("org.eclipse.equinox.ds");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { dsBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
-                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
-                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // Jetty
-               // Bundle jettyUtilBundle = findBundle("org.eclipse.equinox.http.jetty");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { "*/org.eclipse.jetty.*" }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle servletBundle = findBundle("javax.servlet");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { servletBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(),
-                                               "org.glassfish.web.rfc2109_cookie_names_enforced", "read") },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // required to be able to get the BundleContext in the customizer
-               Bundle jettyCustomizerBundle = findBundle("org.argeo.ext.equinox.jetty");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { jettyCustomizerBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Blueprint
-//             Bundle blueprintBundle = findBundle("org.eclipse.gemini.blueprint.core");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { blueprintBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle blueprintExtenderBundle = findBundle("org.eclipse.gemini.blueprint.extender");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin
-//                                             .newConditionalPermissionInfo(null,
-//                                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                                             new String[] { blueprintExtenderBundle.getLocation() }) },
-//                                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                                             new PermissionInfo(PropertyPermission.class.getName(), "org.eclipse.gemini.*",
-//                                                                                             "read"),
-//                                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
-//                                                                             new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
-//                                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle springCoreBundle = findBundle("org.springframework.core");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { springCoreBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-//             Bundle blueprintIoBundle = findBundle("org.eclipse.gemini.blueprint.io");
-//             update.getConditionalPermissionInfos()
-//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
-//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-//                                                             new String[] { blueprintIoBundle.getLocation() }) },
-//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
-//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-//                                             ConditionalPermissionInfo.ALLOW));
-
-               // Equinox
-               Bundle registryBundle = findBundle("org.eclipse.equinox.registry");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { registryBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*", "read"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle equinoxUtilBundle = findBundle("org.eclipse.equinox.util");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { equinoxUtilBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
-                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle equinoxCommonBundle = findBundle("org.eclipse.equinox.common");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { equinoxCommonBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle consoleBundle = findBundle("org.eclipse.equinox.console");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { consoleBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
-                                                               new PermissionInfo(AdminPermission.class.getName(), "*", "listener") },
-                                               ConditionalPermissionInfo.ALLOW));
-               Bundle preferencesBundle = findBundle("org.eclipse.equinox.preferences");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { preferencesBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle appBundle = findBundle("org.eclipse.equinox.app");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { appBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // Jackrabbit
-               Bundle jackrabbitCoreBundle = findBundle("org.apache.jackrabbit.core");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitCoreBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
-                                               new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "getLoginConfiguration", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle jackrabbitDataBundle = findBundle("org.apache.jackrabbit.data");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitDataBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write") },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle jackrabbitCommonBundle = findBundle("org.apache.jackrabbit.jcr.commons");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { jackrabbitCommonBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
-                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
-                               ConditionalPermissionInfo.ALLOW));
-
-               Bundle jackrabbitExtBundle = findBundle("org.argeo.ext.jackrabbit");
-               update.getConditionalPermissionInfos()
-                               .add(permissionAdmin.newConditionalPermissionInfo(null,
-                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                                               new String[] { jackrabbitExtBundle.getLocation() }) },
-                                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "*", "*"), },
-                                               ConditionalPermissionInfo.ALLOW));
-
-               // Tika
-               Bundle tikaCoreBundle = findBundle("org.apache.tika.core");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { tikaCoreBundle.getLocation() }) },
-                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
-                               ConditionalPermissionInfo.ALLOW));
-               Bundle luceneBundle = findBundle("org.apache.lucene");
-               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
-                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
-                                               new String[] { luceneBundle.getLocation() }) },
-                               new PermissionInfo[] {
-                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
-                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read"),
-                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
-                               ConditionalPermissionInfo.ALLOW));
-
-               // COMMIT
-               update.commit();
-       }
-
-       /** @return bundle location */
-       default String locate(Class<?> clzz) {
-               return FrameworkUtil.getBundle(clzz).getLocation();
-       }
-
-       /** Can be null */
-       default Bundle findBundle(String symbolicName) {
-               for (Bundle b : bc.getBundles())
-                       if (b.getSymbolicName().equals(symbolicName))
-                               return b;
-               return null;
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/dc=example,dc=com.ldif
deleted file mode 100644 (file)
index 43e7ade..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-dn: dc=example,dc=com
-objectClass: domain
-objectClass: extensibleObject
-objectClass: top
-dc: example
-
-dn: ou=Groups,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: Groups
-
-dn: ou=People,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: People
-
-dn: uid=demo,ou=People,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: organizationalPerson
-objectClass: person
-objectClass: top
-cn: Demo User
-description: Demo user
-givenName: Demo
-mail: demo@localhost
-sn: User
-uid: demo
-userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
-
-dn: uid=root,ou=People,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: person
-objectClass: organizationalPerson
-objectClass: top
-cn: Super User
-description: Superuser
-givenName: Super
-mail: root@localhost
-sn: User
-uid: root
-userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/example-ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index ffa9073..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-dn: cn=admin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=People,dc=example,dc=com
-
-dn: cn=userAdmin,ou=roles,ou=node
-objectClass: groupOfNames
-objectClass: top
-member: cn=admin,ou=roles,ou=node
-cn: userAdmin
-
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg
deleted file mode 100644 (file)
index b9f05a4..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-USER {
-    org.argeo.cms.auth.HttpSessionLoginModule sufficient;
-    org.argeo.cms.auth.SpnegoLoginModule optional;
-    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
-    org.argeo.cms.auth.UserAdminLoginModule sufficient;
-};
-
-ANONYMOUS {
-    org.argeo.cms.auth.HttpSessionLoginModule sufficient;
-    org.argeo.cms.auth.AnonymousLoginModule sufficient;
-};
-
-DATA_ADMIN {
-    org.argeo.api.DataAdminLoginModule requisite;
-};
-
-NODE {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     keyTab="${osgi.instance.area}node/krb5.keytab" 
-     useKeyTab=true
-     storeKey=true;
-    org.argeo.api.DataAdminLoginModule requisite;
-};
-
-KEYRING {
-    org.argeo.cms.auth.KeyringLoginModule required;
-};
-
-SINGLE_USER {
-    com.sun.security.auth.module.Krb5LoginModule optional
-     principal="${user.name}"
-     storeKey=true
-     useTicketCache=true
-     debug=true;
-    org.argeo.cms.auth.SingleUserLoginModule requisite;
-};
-
-Jackrabbit {
-   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
deleted file mode 100644 (file)
index 0ebfb3a..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-USER {
-    org.argeo.cms.auth.HttpSessionLoginModule sufficient;
-    org.argeo.cms.auth.IdentLoginModule optional;
-    org.argeo.cms.auth.UserAdminLoginModule requisite;
-};
-
-ANONYMOUS {
-    org.argeo.cms.auth.HttpSessionLoginModule sufficient;
-    org.argeo.cms.auth.AnonymousLoginModule requisite;
-};
-
-DATA_ADMIN {
-    org.argeo.api.DataAdminLoginModule requisite;
-};
-
-NODE {
-    org.argeo.api.DataAdminLoginModule requisite;
-};
-
-KEYRING {
-    org.argeo.cms.auth.KeyringLoginModule required;
-};
-
-SINGLE_USER {
-    org.argeo.cms.auth.SingleUserLoginModule requisite;
-};
-
-Jackrabbit {
-   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
-};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=roles,ou=node.ldif
deleted file mode 100644 (file)
index 85247ed..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-dn: ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: node
-
-dn: ou=roles,ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: roles
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/kernel/ou=tokens,ou=node.ldif
deleted file mode 100644 (file)
index 4ae9b88..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-dn: ou=tokens,ou=node
-objectClass: organizationalUnit
-objectClass: top
-ou: tokens
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsActivator.java
new file mode 100644 (file)
index 0000000..038d702
--- /dev/null
@@ -0,0 +1,272 @@
+package org.argeo.cms.internal.osgi;
+
+import java.security.AllPermission;
+import java.util.Dictionary;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoLogger;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.condpermadmin.BundleLocationCondition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.permissionadmin.PermissionInfo;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Activates the kernel. Gives access to kernel information for the rest of the
+ * bundle (and only it)
+ */
+public class CmsActivator implements BundleActivator {
+       private final static CmsLog log = CmsLog.getLog(CmsActivator.class);
+
+//     private static Activator instance;
+
+       // TODO make it configurable
+       private boolean hardened = false;
+
+       private static BundleContext bundleContext;
+
+       private LogReaderService logReaderService;
+
+       private CmsOsgiLogger logger;
+//     private CmsStateImpl nodeState;
+//     private CmsDeploymentImpl nodeDeployment;
+//     private CmsContextImpl nodeInstance;
+
+//     private ServiceTracker<UserAdmin, NodeUserAdmin> userAdminSt;
+
+//     static {
+//             Bundle bundle = FrameworkUtil.getBundle(Activator.class);
+//             if (bundle != null) {
+//                     bundleContext = bundle.getBundleContext();
+//             }
+//     }
+
+       void init() {
+//             Runtime.getRuntime().addShutdownHook(new CmsShutdown());
+//             instance = this;
+//             this.bc = bundleContext;
+               if (bundleContext != null)
+                       this.logReaderService = getService(LogReaderService.class);
+               initArgeoLogger();
+//             this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+//
+//             try {
+//                     initSecurity();
+////                   initArgeoLogger();
+//                     initNode();
+//
+//                     if (log.isTraceEnabled())
+//                             log.trace("Kernel bundle started");
+//             } catch (Throwable e) {
+//                     log.error("## FATAL: CMS activator failed", e);
+//             }
+       }
+
+       void destroy() {
+               try {
+//                     if (nodeInstance != null)
+//                             nodeInstance.shutdown();
+//                     if (nodeDeployment != null)
+//                             nodeDeployment.shutdown();
+//                     if (nodeState != null)
+//                             nodeState.shutdown();
+//
+//                     if (userAdminSt != null)
+//                             userAdminSt.close();
+
+//                     internalExecutorService.shutdown();
+//                     instance = null;
+                       bundleContext = null;
+                       this.logReaderService = null;
+                       // this.configurationAdmin = null;
+               } catch (Exception e) {
+                       log.error("CMS activator shutdown failed", e);
+               }
+               
+               new GogoShellKiller().start();
+       }
+
+       private void initSecurity() {
+               // code-level permissions
+               String osgiSecurity = bundleContext.getProperty(Constants.FRAMEWORK_SECURITY);
+               if (osgiSecurity != null && Constants.FRAMEWORK_SECURITY_OSGI.equals(osgiSecurity)) {
+                       // TODO rather use a tracker?
+                       ConditionalPermissionAdmin permissionAdmin = bundleContext
+                                       .getService(bundleContext.getServiceReference(ConditionalPermissionAdmin.class));
+                       if (!hardened) {
+                               // All permissions to all bundles
+                               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
+                               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] {
+                                                               new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+                               // TODO data admin permission
+//                             PermissionInfo dataAdminPerm = new PermissionInfo(AuthPermission.class.getName(),
+//                                             "createLoginContext." + NodeConstants.LOGIN_CONTEXT_DATA_ADMIN, null);
+//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] {
+//                                                             new ConditionInfo(BundleLocationCondition.class.getName(), new String[] { "*" }) },
+//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.DENY));
+//                             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] {
+//                                                             new ConditionInfo(BundleSignerCondition.class.getName(), new String[] { "CN=\"Eclipse.org Foundation, Inc.\", OU=IT, O=\"Eclipse.org Foundation, Inc.\", L=Nepean, ST=Ontario, C=CA" }) },
+//                                             new PermissionInfo[] { dataAdminPerm }, ConditionalPermissionInfo.ALLOW));
+                               update.commit();
+                       } else {
+                               SecurityProfile securityProfile = new SecurityProfile() {
+                               };
+                               securityProfile.applySystemPermissions(permissionAdmin);
+                       }
+               }
+
+       }
+
+       private void initArgeoLogger() {
+               logger = new CmsOsgiLogger(logReaderService);
+               if (bundleContext != null)
+                       bundleContext.registerService(ArgeoLogger.class, logger, null);
+       }
+
+//     private void initNode() throws IOException {
+//             // Node state
+//             nodeState = new CmsStateImpl();
+//             registerService(CmsState.class, nodeState, null);
+//
+//             // Node deployment
+//             nodeDeployment = new CmsDeploymentImpl();
+////           registerService(NodeDeployment.class, nodeDeployment, null);
+//
+//             // Node instance
+//             nodeInstance = new CmsContextImpl();
+//             registerService(CmsContext.class, nodeInstance, null);
+//     }
+
+       public static <T> void registerService(Class<T> clss, T service, Dictionary<String, ?> properties) {
+               if (bundleContext != null) {
+                       bundleContext.registerService(clss, service, properties);
+               }
+
+       }
+
+       public static <T> T getService(Class<T> clss) {
+               if (bundleContext != null) {
+                       return bundleContext.getService(bundleContext.getServiceReference(clss));
+               } else {
+                       return null;
+               }
+       }
+
+       /*
+        * OSGi
+        */
+
+       @Override
+       public void start(BundleContext bc) throws Exception {
+               bundleContext = bc;
+//             if (!bc.getBundle().equals(bundleContext.getBundle()))
+//                     throw new IllegalStateException(
+//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+               init();
+//             userAdminSt = new ServiceTracker<>(bundleContext, UserAdmin.class, null);
+//             userAdminSt.open();
+
+               ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+
+                       @Override
+                       public HttpService addingService(ServiceReference<HttpService> sr) {
+                               Object httpPort = sr.getProperty("http.port");
+                               Object httpsPort = sr.getProperty("https.port");
+                               log.info(httpPortsMsg(httpPort, httpsPort));
+                               close();
+                               return super.addingService(sr);
+                       }
+               };
+               httpSt.open();
+       }
+
+       private String httpPortsMsg(Object httpPort, Object httpsPort) {
+               return (httpPort != null ? "HTTP " + httpPort + " " : " ") + (httpsPort != null ? "HTTPS " + httpsPort : "");
+       }
+
+       @Override
+       public void stop(BundleContext bc) throws Exception {
+//             if (!bc.getBundle().equals(bundleContext.getBundle()))
+//                     throw new IllegalStateException(
+//                                     "Bundle " + bc.getBundle() + " is not consistent with " + bundleContext.getBundle());
+               destroy();
+               bundleContext = null;
+       }
+
+//     private <T> T getService(Class<T> clazz) {
+//             ServiceReference<T> sr = bundleContext.getServiceReference(clazz);
+//             if (sr == null)
+//                     throw new IllegalStateException("No service available for " + clazz);
+//             return bundleContext.getService(sr);
+//     }
+
+//     public static GSSCredential getAcceptorCredentials() {
+//             return getNodeUserAdmin().getAcceptorCredentials();
+//     }
+//
+//     @Deprecated
+//     public static boolean isSingleUser() {
+//             return getNodeUserAdmin().isSingleUser();
+//     }
+//
+//     public static UserAdmin getUserAdmin() {
+//             return (UserAdmin) getNodeUserAdmin();
+//     }
+//
+//     public static String getHttpProxySslHeader() {
+//             return KernelUtils.getFrameworkProp(CmsConstants.HTTP_PROXY_SSL_DN);
+//     }
+//
+//     private static NodeUserAdmin getNodeUserAdmin() {
+//             NodeUserAdmin res;
+//             try {
+//                     res = instance.userAdminSt.waitForService(60000);
+//             } catch (InterruptedException e) {
+//                     throw new IllegalStateException("Cannot retrieve Node user admin", e);
+//             }
+//             if (res == null)
+//                     throw new IllegalStateException("No Node user admin found");
+//
+//             return res;
+//             // ServiceReference<UserAdmin> sr =
+//             // instance.bc.getServiceReference(UserAdmin.class);
+//             // NodeUserAdmin userAdmin = (NodeUserAdmin) instance.bc.getService(sr);
+//             // return userAdmin;
+//
+//     }
+
+//     public static ExecutorService getInternalExecutorService() {
+//             return instance.internalExecutorService;
+//     }
+
+       // static CmsSecurity getCmsSecurity() {
+       // return instance.nodeSecurity;
+       // }
+
+//     public String[] getLocales() {
+//             // TODO optimize?
+//             List<Locale> locales = CmsStateImpl.getNodeState().getLocales();
+//             String[] res = new String[locales.size()];
+//             for (int i = 0; i < locales.size(); i++)
+//                     res[i] = locales.get(i).toString();
+//             return res;
+//     }
+
+       public static BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsOsgiLogger.java
new file mode 100644 (file)
index 0000000..6898c43
--- /dev/null
@@ -0,0 +1,541 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ArgeoLogListener;
+import org.argeo.cms.ArgeoLogger;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogLevel;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+/** Not meant to be used directly in standard log4j config */
+public class CmsOsgiLogger implements ArgeoLogger, LogListener {
+       /** Internal debug for development purposes. */
+       private static Boolean debug = false;
+
+       private Boolean disabled = false;
+
+       private String level = null;
+
+//     private Level log4jLevel = null;
+
+       private Properties configuration;
+
+       private AppenderImpl appender;
+
+       private final List<ArgeoLogListener> everythingListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final List<ArgeoLogListener> allUsersListeners = Collections
+                       .synchronizedList(new ArrayList<ArgeoLogListener>());
+       private final Map<String, List<ArgeoLogListener>> userListeners = Collections
+                       .synchronizedMap(new HashMap<String, List<ArgeoLogListener>>());
+
+       private BlockingQueue<LogEvent> events;
+       private LogDispatcherThread logDispatcherThread = new LogDispatcherThread();
+
+       private Integer maxLastEventsCount = 10 * 1000;
+
+       /** Marker to prevent stack overflow */
+       private ThreadLocal<Boolean> dispatching = new ThreadLocal<Boolean>() {
+
+               @Override
+               protected Boolean initialValue() {
+                       return false;
+               }
+       };
+
+       public CmsOsgiLogger(LogReaderService lrs) {
+               if (lrs != null) {
+                       Enumeration<LogEntry> logEntries = lrs.getLog();
+                       while (logEntries.hasMoreElements())
+                               logged(logEntries.nextElement());
+                       lrs.addLogListener(this);
+
+                       // configure log4j watcher
+//                     String log4jConfiguration = KernelUtils.getFrameworkProp("log4j.configuration");
+//                     if (log4jConfiguration != null && log4jConfiguration.startsWith("file:")) {
+//                             if (log4jConfiguration.contains("..")) {
+//                                     if (log4jConfiguration.startsWith("file://"))
+//                                             log4jConfiguration = log4jConfiguration.substring("file://".length());
+//                                     else if (log4jConfiguration.startsWith("file:"))
+//                                             log4jConfiguration = log4jConfiguration.substring("file:".length());
+//                             }
+//                             try {
+//                                     Path log4jconfigPath;
+//                                     if (log4jConfiguration.startsWith("file:"))
+//                                             log4jconfigPath = Paths.get(new URI(log4jConfiguration));
+//                                     else
+//                                             log4jconfigPath = Paths.get(log4jConfiguration);
+//                                     Thread log4jConfWatcher = new Log4jConfWatcherThread(log4jconfigPath);
+//                                     log4jConfWatcher.start();
+//                             } catch (Exception e) {
+//                                     stdErr("Badly formatted log4j configuration URI " + log4jConfiguration + ": " + e.getMessage());
+//                             }
+//                     }
+               }
+       }
+
+       public void init() {
+               try {
+                       events = new LinkedBlockingQueue<LogEvent>();
+
+                       // if (layout != null)
+                       // setLayout(layout);
+                       // else
+                       // setLayout(new PatternLayout(pattern));
+                       appender = new AppenderImpl();
+                       reloadConfiguration();
+//                     Logger.getRootLogger().addAppender(appender);
+
+                       logDispatcherThread = new LogDispatcherThread();
+                       logDispatcherThread.start();
+               } catch (Exception e) {
+                       throw new CmsException("Cannot initialize log4j");
+               }
+       }
+
+       public void destroy() throws Exception {
+//             Logger.getRootLogger().removeAppender(appender);
+               allUsersListeners.clear();
+               for (List<ArgeoLogListener> lst : userListeners.values())
+                       lst.clear();
+               userListeners.clear();
+
+               events.clear();
+               events = null;
+               logDispatcherThread.interrupt();
+       }
+
+       // public void setLayout(Layout layout) {
+       // this.layout = layout;
+       // }
+
+       public String toString() {
+               return "Node Logger";
+       }
+
+       //
+       // OSGi LOGGER
+       //
+       @Override
+       public void logged(LogEntry status) {
+               CmsLog pluginLog = CmsLog.getLog(status.getBundle().getSymbolicName());
+               LogLevel severity = status.getLogLevel();
+               if (severity.equals(LogLevel.ERROR) && pluginLog.isErrorEnabled()) {
+                       // FIXME Fix Argeo TP
+                       if (status.getException() instanceof SignatureException)
+                               return;
+                       pluginLog.error(msg(status), status.getException());
+               } else if (severity.equals(LogLevel.WARN) && pluginLog.isWarnEnabled()) {
+                       if ("org.apache.felix.scr".equals(status.getBundle().getSymbolicName())
+                                       && (status.getException() != null && status.getException() instanceof InterruptedException)) {
+                               // do not print stacktraces by Felix SCR shutdown
+                               pluginLog.warn(msg(status));
+                       } else {
+                               pluginLog.warn(msg(status), status.getException());
+                       }
+               } else if (severity.equals(LogLevel.INFO) && pluginLog.isDebugEnabled())
+                       pluginLog.debug(msg(status), status.getException());
+               else if (severity.equals(LogLevel.DEBUG) && pluginLog.isTraceEnabled())
+                       pluginLog.trace(msg(status), status.getException());
+               else if (severity.equals(LogLevel.TRACE) && pluginLog.isTraceEnabled())
+                       pluginLog.trace(msg(status), status.getException());
+       }
+
+       private String msg(LogEntry status) {
+               StringBuilder sb = new StringBuilder();
+               sb.append(status.getMessage());
+               Bundle bundle = status.getBundle();
+               if (bundle != null) {
+                       sb.append(" '" + bundle.getSymbolicName() + "'");
+               }
+               ServiceReference<?> sr = status.getServiceReference();
+               if (sr != null) {
+                       sb.append(' ');
+                       String[] objectClasses = (String[]) sr.getProperty(Constants.OBJECTCLASS);
+                       if (isSpringApplicationContext(objectClasses)) {
+                               sb.append("{org.springframework.context.ApplicationContext}");
+                               Object symbolicName = sr.getProperty(Constants.BUNDLE_SYMBOLICNAME);
+                               if (symbolicName != null)
+                                       sb.append(" " + Constants.BUNDLE_SYMBOLICNAME + ": " + symbolicName);
+                       } else {
+                               sb.append(arrayToString(objectClasses));
+                       }
+                       Object cn = sr.getProperty(CmsConstants.CN);
+                       if (cn != null)
+                               sb.append(" " + CmsConstants.CN + ": " + cn);
+                       Object factoryPid = sr.getProperty(ConfigurationAdmin.SERVICE_FACTORYPID);
+                       if (factoryPid != null)
+                               sb.append(" " + ConfigurationAdmin.SERVICE_FACTORYPID + ": " + factoryPid);
+                       // else {
+                       // Object servicePid = sr.getProperty(Constants.SERVICE_PID);
+                       // if (servicePid != null)
+                       // sb.append(" " + Constants.SERVICE_PID + ": " + servicePid);
+                       // }
+                       // servlets
+                       Object whiteBoardPattern = sr.getProperty(KernelConstants.WHITEBOARD_PATTERN_PROP);
+                       if (whiteBoardPattern != null) {
+                               if (whiteBoardPattern instanceof String) {
+                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": " + whiteBoardPattern);
+                               } else {
+                                       sb.append(" " + KernelConstants.WHITEBOARD_PATTERN_PROP + ": "
+                                                       + arrayToString((String[]) whiteBoardPattern));
+                               }
+                       }
+                       // RWT
+                       Object contextName = sr.getProperty(KernelConstants.CONTEXT_NAME_PROP);
+                       if (contextName != null)
+                               sb.append(" " + KernelConstants.CONTEXT_NAME_PROP + ": " + contextName);
+
+                       // user directories
+                       Object baseDn = sr.getProperty(UserAdminConf.baseDn.name());
+                       if (baseDn != null)
+                               sb.append(" " + UserAdminConf.baseDn.name() + ": " + baseDn);
+
+               }
+               return sb.toString();
+       }
+
+       private String arrayToString(Object[] arr) {
+               StringBuilder sb = new StringBuilder();
+               sb.append('[');
+               for (int i = 0; i < arr.length; i++) {
+                       if (i != 0)
+                               sb.append(',');
+                       sb.append(arr[i]);
+               }
+               sb.append(']');
+               return sb.toString();
+       }
+
+       private boolean isSpringApplicationContext(String[] objectClasses) {
+               for (String clss : objectClasses) {
+                       if (clss.equals("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       //
+       // ARGEO LOGGER
+       //
+
+       public synchronized void register(ArgeoLogListener listener, Integer numberOfPreviousEvents) {
+               String username = CurrentUser.getUsername();
+               if (username == null)
+                       throw new CmsException("Only authenticated users can register a log listener");
+
+               if (!userListeners.containsKey(username)) {
+                       List<ArgeoLogListener> lst = Collections.synchronizedList(new ArrayList<ArgeoLogListener>());
+                       userListeners.put(username, lst);
+               }
+               userListeners.get(username).add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(username, numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       dispatchEvent(listener, evt);
+       }
+
+       public synchronized void registerForAll(ArgeoLogListener listener, Integer numberOfPreviousEvents,
+                       boolean everything) {
+               if (everything)
+                       everythingListeners.add(listener);
+               else
+                       allUsersListeners.add(listener);
+               List<LogEvent> lastEvents = logDispatcherThread.getLastEvents(null, numberOfPreviousEvents);
+               for (LogEvent evt : lastEvents)
+                       if (everything || evt.getUsername() != null)
+                               dispatchEvent(listener, evt);
+       }
+
+       public synchronized void unregister(ArgeoLogListener listener) {
+               String username = CurrentUser.getUsername();
+               if (username == null)// FIXME
+                       return;
+               if (!userListeners.containsKey(username))
+                       throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+               if (!userListeners.get(username).contains(listener))
+                       throw new IllegalStateException("No user listeners " + listener + " registered for user " + username);
+               userListeners.get(username).remove(listener);
+               if (userListeners.get(username).isEmpty())
+                       userListeners.remove(username);
+
+       }
+
+       public synchronized void unregisterForAll(ArgeoLogListener listener) {
+               everythingListeners.remove(listener);
+               allUsersListeners.remove(listener);
+       }
+
+       /** For development purpose, since using regular logging is not easy here */
+       private static void stdOut(Object obj) {
+               System.out.println(obj);
+       }
+
+       private static void stdErr(Object obj) {
+               System.err.println(obj);
+       }
+
+       private static void debug(Object obj) {
+               if (debug)
+                       System.out.println(obj);
+       }
+
+       private static boolean isInternalDebugEnabled() {
+               return debug;
+       }
+
+       // public void setPattern(String pattern) {
+       // this.pattern = pattern;
+       // }
+
+       public void setDisabled(Boolean disabled) {
+               this.disabled = disabled;
+       }
+
+       public void setLevel(String level) {
+               this.level = level;
+       }
+
+       public void setConfiguration(Properties configuration) {
+               this.configuration = configuration;
+       }
+
+       public void updateConfiguration(Properties configuration) {
+               setConfiguration(configuration);
+               reloadConfiguration();
+       }
+
+       public Properties getConfiguration() {
+               return configuration;
+       }
+
+       /**
+        * Reloads configuration (if the configuration {@link Properties} is set)
+        */
+       protected void reloadConfiguration() {
+               if (configuration != null) {
+//                     LogManager.resetConfiguration();
+//                     PropertyConfigurator.configure(configuration);
+               }
+       }
+
+       protected synchronized void processLoggingEvent(LogEvent event) {
+               if (disabled)
+                       return;
+
+               if (dispatching.get())
+                       return;
+
+               if (level != null && !level.trim().equals("")) {
+//                     if (log4jLevel == null || !log4jLevel.toString().equals(level))
+//                             try {
+//                                     log4jLevel = Level.toLevel(level);
+//                             } catch (Exception e) {
+//                                     System.err.println("Log4j level could not be set for level '" + level + "', resetting it to null.");
+//                                     e.printStackTrace();
+//                                     level = null;
+//                             }
+//
+//                     if (log4jLevel != null && !event.getLoggingEvent().getLevel().isGreaterOrEqual(log4jLevel)) {
+//                             return;
+//                     }
+               }
+
+               try {
+                       // admin listeners
+                       Iterator<ArgeoLogListener> everythingIt = everythingListeners.iterator();
+                       while (everythingIt.hasNext())
+                               dispatchEvent(everythingIt.next(), event);
+
+                       if (event.getUsername() != null) {
+                               Iterator<ArgeoLogListener> allUsersIt = allUsersListeners.iterator();
+                               while (allUsersIt.hasNext())
+                                       dispatchEvent(allUsersIt.next(), event);
+
+                               if (userListeners.containsKey(event.getUsername())) {
+                                       Iterator<ArgeoLogListener> userIt = userListeners.get(event.getUsername()).iterator();
+                                       while (userIt.hasNext())
+                                               dispatchEvent(userIt.next(), event);
+                               }
+                       }
+               } catch (Exception e) {
+                       stdOut("Cannot process logging event");
+                       e.printStackTrace();
+               }
+       }
+
+       protected void dispatchEvent(ArgeoLogListener logListener, LogEvent evt) {
+//             LoggingEvent event = evt.getLoggingEvent();
+//             logListener.appendLog(evt.getUsername(), event.getTimeStamp(), event.getLevel().toString(),
+//                             event.getLoggerName(), event.getThreadName(), event.getMessage(), event.getThrowableStrRep());
+       }
+
+       private class AppenderImpl { // extends AppenderSkeleton {
+               public boolean requiresLayout() {
+                       return false;
+               }
+
+               public void close() {
+               }
+
+//             @Override
+//             protected void append(LoggingEvent event) {
+//                     if (events != null) {
+//                             try {
+//                                     String username = CurrentUser.getUsername();
+//                                     events.put(new LogEvent(username, event));
+//                             } catch (InterruptedException e) {
+//                                     // silent
+//                             }
+//                     }
+//             }
+
+       }
+
+       private class LogDispatcherThread extends Thread {
+               /** encapsulated in order to simplify concurrency management */
+               private LinkedList<LogEvent> lastEvents = new LinkedList<LogEvent>();
+
+               public LogDispatcherThread() {
+                       super("Argeo Logging Dispatcher Thread");
+               }
+
+               public void run() {
+                       while (events != null) {
+                               try {
+                                       LogEvent loggingEvent = events.take();
+                                       processLoggingEvent(loggingEvent);
+                                       addLastEvent(loggingEvent);
+                               } catch (InterruptedException e) {
+                                       if (events == null)
+                                               return;
+                               }
+                       }
+               }
+
+               protected synchronized void addLastEvent(LogEvent loggingEvent) {
+                       if (lastEvents.size() >= maxLastEventsCount)
+                               lastEvents.poll();
+                       lastEvents.add(loggingEvent);
+               }
+
+               public synchronized List<LogEvent> getLastEvents(String username, Integer maxCount) {
+                       LinkedList<LogEvent> evts = new LinkedList<LogEvent>();
+                       ListIterator<LogEvent> it = lastEvents.listIterator(lastEvents.size());
+                       int count = 0;
+                       while (it.hasPrevious() && (count < maxCount)) {
+                               LogEvent evt = it.previous();
+                               if (username == null || username.equals(evt.getUsername())) {
+                                       evts.push(evt);
+                                       count++;
+                               }
+                       }
+                       return evts;
+               }
+       }
+
+       private class LogEvent {
+               private final String username;
+//             private final LoggingEvent loggingEvent;
+
+               public LogEvent(String username) {
+                       super();
+                       this.username = username;
+//                     this.loggingEvent = loggingEvent;
+               }
+
+//             @Override
+//             public int hashCode() {
+//                     return loggingEvent.hashCode();
+//             }
+//
+//             @Override
+//             public boolean equals(Object obj) {
+//                     return loggingEvent.equals(obj);
+//             }
+//
+//             @Override
+//             public String toString() {
+//                     return username + "@ " + loggingEvent.toString();
+//             }
+
+               public String getUsername() {
+                       return username;
+               }
+
+//             public LoggingEvent getLoggingEvent() {
+//                     return loggingEvent;
+//             }
+
+       }
+
+       private class Log4jConfWatcherThread extends Thread {
+               private Path log4jConfigurationPath;
+
+               public Log4jConfWatcherThread(Path log4jConfigurationPath) {
+                       super("Log4j Configuration Watcher");
+                       try {
+                               this.log4jConfigurationPath = log4jConfigurationPath.toRealPath();
+                       } catch (IOException e) {
+                               this.log4jConfigurationPath = log4jConfigurationPath.toAbsolutePath();
+                               stdOut("Cannot determine real path for " + log4jConfigurationPath + ": " + e.getMessage());
+                       }
+               }
+
+               public void run() {
+                       Path parentDir = log4jConfigurationPath.getParent();
+                       try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
+                               parentDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
+                               WatchKey wk;
+                               watching: while ((wk = watchService.take()) != null) {
+                                       for (WatchEvent<?> event : wk.pollEvents()) {
+                                               final Path changed = (Path) event.context();
+                                               if (log4jConfigurationPath.equals(parentDir.resolve(changed))) {
+                                                       if (isInternalDebugEnabled())
+                                                               debug(log4jConfigurationPath + " has changed, reloading.");
+//                                                     PropertyConfigurator.configure(log4jConfigurationPath.toUri().toURL());
+                                               }
+                                       }
+                                       // reset the key
+                                       boolean valid = wk.reset();
+                                       if (!valid) {
+                                               break watching;
+                                       }
+                               }
+                       } catch (IOException | InterruptedException e) {
+                               stdErr("Log4j configuration watcher failed: " + e.getMessage());
+                       }
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/CmsShutdown.java
new file mode 100644 (file)
index 0000000..324462d
--- /dev/null
@@ -0,0 +1,70 @@
+package org.argeo.cms.internal.osgi;
+
+import org.argeo.api.cms.CmsLog;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.launch.Framework;
+
+/** Shutdowns the OSGi framework */
+public class CmsShutdown extends Thread {
+       public final int EXIT_OK = 0;
+       public final int EXIT_ERROR = 1;
+       public final int EXIT_TIMEOUT = 2;
+       public final int EXIT_UNKNOWN = 3;
+
+       private final CmsLog log = CmsLog.getLog(CmsShutdown.class);
+       // private final BundleContext bc =
+       // FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext();
+       private final Framework framework;
+
+       /** Shutdown timeout in ms */
+       private long timeout = 10 * 60 * 1000;
+
+       public CmsShutdown() {
+               super("CMS Shutdown Hook");
+               framework = FrameworkUtil.getBundle(CmsShutdown.class) != null
+                               ? (Framework) FrameworkUtil.getBundle(CmsShutdown.class).getBundleContext().getBundle(0)
+                               : null;
+       }
+
+       @Override
+       public void run() {
+               if (framework != null && framework.getState() != Bundle.ACTIVE) {
+                       return;
+               }
+
+               if (log.isDebugEnabled())
+                       log.debug("Shutting down OSGi framework...");
+               try {
+                       if (framework != null) {
+                               // shutdown framework
+                               framework.stop();
+                               // wait for shutdown
+                               FrameworkEvent shutdownEvent = framework.waitForStop(timeout);
+                               int stoppedType = shutdownEvent.getType();
+                               Runtime runtime = Runtime.getRuntime();
+                               if (stoppedType == FrameworkEvent.STOPPED) {
+                                       // close VM
+                                       // System.exit(EXIT_OK);
+                               } else if (stoppedType == FrameworkEvent.ERROR) {
+                                       log.error("The OSGi framework stopped with an error");
+                                       runtime.halt(EXIT_ERROR);
+                               } else if (stoppedType == FrameworkEvent.WAIT_TIMEDOUT) {
+                                       log.error("The OSGi framework hasn't stopped after " + timeout + "ms."
+                                                       + " Forcibly terminating the JVM...");
+                                       runtime.halt(EXIT_TIMEOUT);
+                               } else {
+                                       log.error("Unknown state of OSGi framework after " + timeout + "ms."
+                                                       + " Forcibly terminating the JVM... (" + shutdownEvent + ")");
+                                       runtime.halt(EXIT_UNKNOWN);
+                               }
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       log.error("Unexpected exception " + e + " in shutdown hook. " + " Forcibly terminating the JVM...");
+                       Runtime.getRuntime().halt(EXIT_UNKNOWN);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/DeployConfig.java
new file mode 100644 (file)
index 0000000..deb3304
--- /dev/null
@@ -0,0 +1,377 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.runtime.InitUtils;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.util.naming.AttributesDictionary;
+import org.argeo.util.naming.LdifParser;
+import org.argeo.util.naming.LdifWriter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.cm.ConfigurationEvent;
+import org.osgi.service.cm.ConfigurationListener;
+
+/** Manages the LDIF-based deployment configuration. */
+public class DeployConfig implements ConfigurationListener {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+       private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
+       private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
+//     private final DataModels dataModels;
+
+       private boolean isFirstInit = false;
+
+       private final static String ROLES = "roles";
+
+       private ConfigurationAdmin configurationAdmin;
+
+       public DeployConfig() {
+//             this.dataModels = dataModels;
+               // ConfigurationAdmin configurationAdmin =
+//             // bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
+//             try {
+//                     if (!isInitialized()) { // first init
+//                             isFirstInit = true;
+//                             firstInit();
+//                     }
+//                     this.configurationAdmin = configurationAdmin;
+////                   init(configurationAdmin, isClean, isFirstInit);
+//             } catch (IOException e) {
+//                     throw new RuntimeException("Could not init deploy configs", e);
+//             }
+               // FIXME check race conditions during initialization
+               // bc.registerService(ConfigurationListener.class, this, null);
+       }
+
+       private void firstInit() throws IOException {
+               log.info("## FIRST INIT ##");
+               Files.createDirectories(deployConfigPath.getParent());
+
+               // FirstInit firstInit = new FirstInit();
+               InitUtils.prepareFirstInitInstanceArea();
+
+               if (!Files.exists(deployConfigPath))
+                       deployConfigs = new TreeMap<>();
+               else// config file could have juste been copied by preparation
+                       try (InputStream in = Files.newInputStream(deployConfigPath)) {
+                               deployConfigs = new LdifParser().read(in);
+                       }
+               save();
+       }
+
+       private void setFromFrameworkProperties(boolean isFirstInit) {
+
+               // user admin
+               List<Dictionary<String, Object>> userDirectoryConfigs = InitUtils.getUserDirectoryConfigs();
+               if (userDirectoryConfigs.size() != 0) {
+                       List<String> activeCns = new ArrayList<>();
+                       for (int i = 0; i < userDirectoryConfigs.size(); i++) {
+                               Dictionary<String, Object> userDirectoryConfig = userDirectoryConfigs.get(i);
+                               String baseDn = (String) userDirectoryConfig.get(UserAdminConf.baseDn.name());
+                               String cn;
+                               if (CmsConstants.ROLES_BASEDN.equals(baseDn))
+                                       cn = ROLES;
+                               else
+                                       cn = UserAdminConf.baseDnHash(userDirectoryConfig);
+                               activeCns.add(cn);
+                               userDirectoryConfig.put(CmsConstants.CN, cn);
+                               putFactoryDeployConfig(CmsConstants.NODE_USER_ADMIN_PID, userDirectoryConfig);
+                       }
+                       // disable others
+                       LdapName userAdminFactoryName = serviceFactoryDn(CmsConstants.NODE_USER_ADMIN_PID);
+                       for (LdapName name : deployConfigs.keySet()) {
+                               if (name.startsWith(userAdminFactoryName) && !name.equals(userAdminFactoryName)) {
+//                                     try {
+                                       Attributes attrs = deployConfigs.get(name);
+                                       String cn = name.getRdn(name.size() - 1).getValue().toString();
+                                       if (!activeCns.contains(cn)) {
+                                               attrs.put(UserAdminConf.disabled.name(), "true");
+                                       }
+//                                     } catch (Exception e) {
+//                                             throw new CmsException("Cannot disable user directory " + name, e);
+//                                     }
+                               }
+                       }
+               }
+
+               // http server
+               Dictionary<String, Object> webServerConfig = InitUtils
+                               .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
+               if (!webServerConfig.isEmpty()) {
+                       // TODO check for other customizers
+//                     webServerConfig.put("customizer.class", "org.argeo.equinox.jetty.CmsJettyCustomizer");
+                       putFactoryDeployConfig(KernelConstants.JETTY_FACTORY_PID, webServerConfig);
+               }
+//             LdapName defaultHttpServiceDn = serviceDn(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT);
+//             if (deployConfigs.containsKey(defaultHttpServiceDn)) {
+//                     // remove old default configs since we have now to start Jetty servlet bridge
+//                     // indirectly
+//                     deployConfigs.remove(defaultHttpServiceDn);
+//             }
+
+               // SAVE
+               save();
+               //
+
+//             Dictionary<String, Object> webServerConfig = InitUtils
+//                             .getHttpServerConfig(getProps(KernelConstants.JETTY_FACTORY_PID, CmsConstants.DEFAULT));
+       }
+
+       public void start() throws IOException {
+               if (!isInitialized()) { // first init
+                       isFirstInit = true;
+                       firstInit();
+               }
+
+               boolean isClean;
+               try {
+                       Configuration[] confs = configurationAdmin
+                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+                       isClean = confs == null || confs.length == 0;
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot analyse clean state", e);
+               }
+
+               try (InputStream in = Files.newInputStream(deployConfigPath)) {
+                       deployConfigs = new LdifParser().read(in);
+               }
+               if (isClean) {
+                       if (log.isDebugEnabled())
+                               log.debug("Clean state, loading from framework properties...");
+                       setFromFrameworkProperties(isFirstInit);
+                       loadConfigs();
+               }
+               // TODO check consistency if not clean
+       }
+
+       public void stop() {
+
+       }
+
+       public void loadConfigs() throws IOException {
+               // FIXME make it more robust
+               Configuration systemRolesConf = null;
+               LdapName systemRolesDn;
+               try {
+                       // FIXME make it more robust
+                       systemRolesDn = new LdapName("cn=roles,ou=org.argeo.api.userAdmin,ou=deploy,ou=node");
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException(e);
+               }
+               deployConfigs: for (LdapName dn : deployConfigs.keySet()) {
+                       Rdn lastRdn = dn.getRdn(dn.size() - 1);
+                       LdapName prefix = (LdapName) dn.getPrefix(dn.size() - 1);
+                       if (prefix.toString().equals(CmsConstants.DEPLOY_BASEDN)) {
+                               if (lastRdn.getType().equals(CmsConstants.CN)) {
+                                       // service
+                                       String pid = lastRdn.getValue().toString();
+                                       Configuration conf = configurationAdmin.getConfiguration(pid);
+                                       AttributesDictionary dico = new AttributesDictionary(deployConfigs.get(dn));
+                                       conf.update(dico);
+                               } else {
+                                       // service factory definition
+                               }
+                       } else {
+                               Attributes config = deployConfigs.get(dn);
+                               Attribute disabled = config.get(UserAdminConf.disabled.name());
+                               if (disabled != null)
+                                       continue deployConfigs;
+                               // service factory service
+                               Rdn beforeLastRdn = dn.getRdn(dn.size() - 2);
+                               assert beforeLastRdn.getType().equals(CmsConstants.OU);
+                               String factoryPid = beforeLastRdn.getValue().toString();
+                               Configuration conf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
+                               if (systemRolesDn.equals(dn)) {
+                                       systemRolesConf = configurationAdmin.createFactoryConfiguration(factoryPid.toString(), null);
+                               } else {
+                                       AttributesDictionary dico = new AttributesDictionary(config);
+                                       conf.update(dico);
+                               }
+                       }
+               }
+
+               // system roles must be last since it triggers node user admin publication
+               if (systemRolesConf == null)
+                       throw new IllegalStateException("System roles are not configured.");
+               systemRolesConf.update(new AttributesDictionary(deployConfigs.get(systemRolesDn)));
+
+       }
+
+       @Override
+       public void configurationEvent(ConfigurationEvent event) {
+               try {
+                       if (ConfigurationEvent.CM_UPDATED == event.getType()) {
+                               Configuration conf = configurationAdmin.getConfiguration(event.getPid(), null);
+                               LdapName serviceDn = null;
+                               String factoryPid = conf.getFactoryPid();
+                               if (factoryPid != null) {
+                                       LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
+                                       if (deployConfigs.containsKey(serviceFactoryDn)) {
+                                               for (LdapName dn : deployConfigs.keySet()) {
+                                                       if (dn.startsWith(serviceFactoryDn)) {
+                                                               Rdn lastRdn = dn.getRdn(dn.size() - 1);
+                                                               assert lastRdn.getType().equals(CmsConstants.CN);
+                                                               Object value = conf.getProperties().get(lastRdn.getType());
+                                                               assert value != null;
+                                                               if (value.equals(lastRdn.getValue())) {
+                                                                       serviceDn = dn;
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+
+                                               Object cn = conf.getProperties().get(CmsConstants.CN);
+                                               if (cn == null)
+                                                       throw new IllegalArgumentException("Properties must contain cn");
+                                               if (serviceDn == null) {
+                                                       putFactoryDeployConfig(factoryPid, conf.getProperties());
+                                               } else {
+                                                       Attributes attrs = deployConfigs.get(serviceDn);
+                                                       assert attrs != null;
+                                                       AttributesDictionary.copy(conf.getProperties(), attrs);
+                                               }
+                                               save();
+                                               if (log.isDebugEnabled())
+                                                       log.debug("Updated deploy config " + serviceDn(factoryPid, cn.toString()));
+                                       } else {
+                                               // ignore non config-registered service factories
+                                       }
+                               } else {
+                                       serviceDn = serviceDn(event.getPid());
+                                       if (deployConfigs.containsKey(serviceDn)) {
+                                               Attributes attrs = deployConfigs.get(serviceDn);
+                                               assert attrs != null;
+                                               AttributesDictionary.copy(conf.getProperties(), attrs);
+                                               save();
+                                               if (log.isDebugEnabled())
+                                                       log.debug("Updated deploy config " + serviceDn);
+                                       } else {
+                                               // ignore non config-registered services
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Could not handle configuration event", e);
+               }
+       }
+
+       public void putFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
+               Object cn = props.get(CmsConstants.CN);
+               if (cn == null)
+                       throw new IllegalArgumentException("cn must be set in properties");
+               LdapName serviceFactoryDn = serviceFactoryDn(factoryPid);
+               if (!deployConfigs.containsKey(serviceFactoryDn))
+                       deployConfigs.put(serviceFactoryDn, new BasicAttributes(CmsConstants.OU, factoryPid));
+               LdapName serviceDn = serviceDn(factoryPid, cn.toString());
+               Attributes attrs = new BasicAttributes();
+               AttributesDictionary.copy(props, attrs);
+               deployConfigs.put(serviceDn, attrs);
+       }
+
+       void putDeployConfig(String servicePid, Dictionary<String, Object> props) {
+               LdapName serviceDn = serviceDn(servicePid);
+               Attributes attrs = new BasicAttributes(CmsConstants.CN, servicePid);
+               AttributesDictionary.copy(props, attrs);
+               deployConfigs.put(serviceDn, attrs);
+       }
+
+       public void save() {
+               try (Writer writer = Files.newBufferedWriter(deployConfigPath)) {
+                       new LdifWriter(writer).write(deployConfigs);
+               } catch (IOException e) {
+                       // throw new CmsException("Cannot save deploy configs", e);
+                       log.error("Cannot save deploy configs", e);
+               }
+       }
+
+       public void setConfigurationAdmin(ConfigurationAdmin configurationAdmin) {
+               this.configurationAdmin = configurationAdmin;
+       }
+
+       public boolean hasDomain() {
+               Configuration[] configs;
+               try {
+                       configs = configurationAdmin
+                                       .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+               } catch (IOException | InvalidSyntaxException e) {
+                       throw new IllegalStateException("Cannot list user directories", e);
+               }
+
+               boolean hasDomain = false;
+               for (Configuration config : configs) {
+                       Object realm = config.getProperties().get(UserAdminConf.realm.name());
+                       if (realm != null) {
+                               log.debug("Found realm: " + realm);
+                               hasDomain = true;
+                       }
+               }
+               return hasDomain;
+       }
+
+       /*
+        * UTILITIES
+        */
+       private LdapName serviceFactoryDn(String factoryPid) {
+               try {
+                       return new LdapName(CmsConstants.OU + "=" + factoryPid + "," + CmsConstants.DEPLOY_BASEDN);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid, e);
+               }
+       }
+
+       private LdapName serviceDn(String servicePid) {
+               try {
+                       return new LdapName(CmsConstants.CN + "=" + servicePid + "," + CmsConstants.DEPLOY_BASEDN);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + servicePid, e);
+               }
+       }
+
+       private LdapName serviceDn(String factoryPid, String cn) {
+               try {
+                       return (LdapName) serviceFactoryDn(factoryPid).add(new Rdn(CmsConstants.CN, cn));
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot generate DN from " + factoryPid + " and " + cn, e);
+               }
+       }
+
+       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
+               Attributes attrs = deployConfigs.get(serviceDn(factoryPid, cn));
+               if (attrs != null)
+                       return new AttributesDictionary(attrs);
+               else
+                       return null;
+       }
+
+       private static boolean isInitialized() {
+               return Files.exists(deployConfigPath);
+       }
+
+       public boolean isFirstInit() {
+               return isFirstInit;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/GogoShellKiller.java
new file mode 100644 (file)
index 0000000..a8c9016
--- /dev/null
@@ -0,0 +1,65 @@
+package org.argeo.cms.internal.osgi;
+
+/**
+ * Workaround for killing Gogo shell by system shutdown.
+ * 
+ * @see https://issues.apache.org/jira/browse/FELIX-4208
+ */
+class GogoShellKiller extends Thread {
+
+       public GogoShellKiller() {
+               super("Gogo Shell Killer");
+               setDaemon(true);
+       }
+
+       @Override
+       public void run() {
+               ThreadGroup rootTg = getRootThreadGroup(null);
+               Thread gogoShellThread = findGogoShellThread(rootTg);
+               if (gogoShellThread == null) // no need to bother if it is not here
+                       return;
+               while (getNonDaemonCount(rootTg) > 2) {
+                       try {
+                               Thread.sleep(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+               }
+               gogoShellThread = findGogoShellThread(rootTg);
+               if (gogoShellThread == null)
+                       return;
+               System.exit(0);
+               // No non-deamon threads left, forcibly halting the VM
+               //Runtime.getRuntime().halt(0);
+       }
+
+       private ThreadGroup getRootThreadGroup(ThreadGroup tg) {
+               if (tg == null)
+                       tg = Thread.currentThread().getThreadGroup();
+               if (tg.getParent() == null)
+                       return tg;
+               else
+                       return getRootThreadGroup(tg.getParent());
+       }
+
+       private int getNonDaemonCount(ThreadGroup rootThreadGroup) {
+               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
+               rootThreadGroup.enumerate(threads);
+               int nonDameonCount = 0;
+               for (Thread t : threads)
+                       if (t != null && !t.isDaemon())
+                               nonDameonCount++;
+               return nonDameonCount;
+       }
+
+       private Thread findGogoShellThread(ThreadGroup rootThreadGroup) {
+               Thread[] threads = new Thread[rootThreadGroup.activeCount()];
+               rootThreadGroup.enumerate(threads, true);
+               for (Thread thread : threads) {
+                       if (thread.getName().equals("pipe-gosh --login --noshutdown"))
+                               return thread;
+               }
+               return null;
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/NodeUserAdmin.java
new file mode 100644 (file)
index 0000000..626a057
--- /dev/null
@@ -0,0 +1,403 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.httpclient.auth.AuthPolicy;
+import org.apache.commons.httpclient.auth.CredentialsProvider;
+import org.apache.commons.httpclient.params.DefaultHttpParams;
+import org.apache.commons.httpclient.params.HttpMethodParams;
+import org.apache.commons.httpclient.params.HttpParams;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.http.client.HttpCredentialProvider;
+import org.argeo.cms.internal.http.client.SpnegoAuthScheme;
+import org.argeo.cms.internal.runtime.KernelConstants;
+import org.argeo.cms.internal.runtime.KernelUtils;
+import org.argeo.osgi.transaction.WorkControl;
+import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.osgi.useradmin.AbstractUserDirectory;
+import org.argeo.osgi.useradmin.AggregatingUserAdmin;
+import org.argeo.osgi.useradmin.LdapUserAdmin;
+import org.argeo.osgi.useradmin.LdifUserAdmin;
+import org.argeo.osgi.useradmin.OsUserDirectory;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.argeo.util.naming.DnsBrowser;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class NodeUserAdmin extends AggregatingUserAdmin implements ManagedServiceFactory, KernelConstants {
+       private final static CmsLog log = CmsLog.getLog(NodeUserAdmin.class);
+
+       // OSGi
+       private Map<String, LdapName> pidToBaseDn = new HashMap<>();
+//     private Map<String, ServiceRegistration<UserDirectory>> pidToServiceRegs = new HashMap<>();
+//     private ServiceRegistration<UserAdmin> userAdminReg;
+
+       // JTA
+//     private final ServiceTracker<WorkControl, WorkControl> tmTracker;
+       // private final String cacheName = UserDirectory.class.getName();
+
+       // GSS API
+       private Path nodeKeyTab = KernelUtils.getOsgiInstancePath(KernelConstants.NODE_KEY_TAB_PATH);
+       private GSSCredential acceptorCredentials;
+
+       private boolean singleUser = false;
+//     private boolean systemRolesAvailable = false;
+
+//     CmsUserManagerImpl userManager;
+       private WorkControl transactionManager;
+       private WorkTransaction userTransaction;
+
+       public NodeUserAdmin() {
+               super(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+//             BundleContext bc = Activator.getBundleContext();
+//             if (bc != null) {
+//                     tmTracker = new ServiceTracker<>(bc, WorkControl.class, null) {
+//
+//                             @Override
+//                             public WorkControl addingService(ServiceReference<WorkControl> reference) {
+//                                     WorkControl workControl = super.addingService(reference);
+//                                     userManager = new CmsUserManagerImpl();
+//                                     userManager.setUserAdmin(NodeUserAdmin.this);
+//                                     // FIXME make it more robust
+//                                     userManager.setUserTransaction((WorkTransaction) workControl);
+//                                     bc.registerService(CmsUserManager.class, userManager, null);
+//                                     return workControl;
+//                             }
+//                     };
+//                     tmTracker.open();
+//             } else {
+//                     tmTracker = null;
+//             }
+       }
+
+       public void start() {
+       }
+
+       public void stop() {
+       }
+
+       @Override
+       public void updated(String pid, Dictionary<String, ?> properties) throws ConfigurationException {
+               String uri = (String) properties.get(UserAdminConf.uri.name());
+               Object realm = properties.get(UserAdminConf.realm.name());
+               URI u;
+               try {
+                       if (uri == null) {
+                               String baseDn = (String) properties.get(UserAdminConf.baseDn.name());
+                               u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + baseDn + ".ldif");
+                       } else if (realm != null) {
+                               u = null;
+                       } else {
+                               u = new URI(uri);
+                       }
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException("Badly formatted URI " + uri, e);
+               }
+
+               // Create
+               AbstractUserDirectory userDirectory;
+               if (realm != null || UserAdminConf.SCHEME_LDAP.equals(u.getScheme())
+                               || UserAdminConf.SCHEME_LDAPS.equals(u.getScheme())) {
+                       userDirectory = new LdapUserAdmin(properties);
+               } else if (UserAdminConf.SCHEME_FILE.equals(u.getScheme())) {
+                       userDirectory = new LdifUserAdmin(u, properties);
+               } else if (UserAdminConf.SCHEME_OS.equals(u.getScheme())) {
+                       userDirectory = new OsUserDirectory(u, properties);
+                       singleUser = true;
+               } else {
+                       throw new IllegalArgumentException("Unsupported scheme " + u.getScheme());
+               }
+               LdapName baseDn = userDirectory.getBaseDn();
+
+               // FIXME make updates more robust
+               if (pidToBaseDn.containsValue(baseDn)) {
+                       if (log.isDebugEnabled())
+                               log.debug("Ignoring user directory update of " + baseDn);
+                       return;
+               }
+
+               addUserDirectory(userDirectory);
+
+               // OSGi
+               Hashtable<String, Object> regProps = new Hashtable<>();
+               regProps.put(Constants.SERVICE_PID, pid);
+               if (isSystemRolesBaseDn(baseDn))
+                       regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+               regProps.put(UserAdminConf.baseDn.name(), baseDn);
+               // ServiceRegistration<UserDirectory> reg =
+               // bc.registerService(UserDirectory.class, userDirectory, regProps);
+               CmsActivator.getBundleContext().registerService(UserDirectory.class, userDirectory, regProps);
+//             userManager.addUserDirectory(userDirectory, regProps);
+               pidToBaseDn.put(pid, baseDn);
+               // pidToServiceRegs.put(pid, reg);
+
+               if (log.isDebugEnabled()) {
+                       log.debug("User directory " + userDirectory.getBaseDn() + (u != null ? " [" + u.getScheme() + "]" : "")
+                                       + " enabled." + (realm != null ? " " + realm + " realm." : ""));
+               }
+
+               if (isSystemRolesBaseDn(baseDn)) {
+                       addStandardSystemRoles();
+
+                       // publishes itself as user admin only when system roles are available
+                       Dictionary<String, Object> userAdminregProps = new Hashtable<>();
+                       userAdminregProps.put(CmsConstants.CN, CmsConstants.DEFAULT);
+                       userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+                       CmsActivator.getBundleContext().registerService(UserAdmin.class, this, userAdminregProps);
+               }
+
+//             if (isSystemRolesBaseDn(baseDn))
+//                     systemRolesAvailable = true;
+//
+//             // start publishing only when system roles are available
+//             if (systemRolesAvailable) {
+//                     // The list of baseDns is published as properties
+//                     // TODO clients should rather reference USerDirectory services
+//                     if (userAdminReg != null)
+//                             userAdminReg.unregister();
+//                     // register self as main user admin
+//                     Dictionary<String, Object> userAdminregProps = currentState();
+//                     userAdminregProps.put(NodeConstants.CN, NodeConstants.DEFAULT);
+//                     userAdminregProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
+//                     userAdminReg = bc.registerService(UserAdmin.class, this, userAdminregProps);
+//             }
+       }
+
+       private void addStandardSystemRoles() {
+               // we assume UserTransaction is already available (TODO make it more robust)
+               try {
+                       userTransaction.begin();
+                       Role adminRole = getRole(CmsConstants.ROLE_ADMIN);
+                       if (adminRole == null) {
+                               adminRole = createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+                       }
+                       if (getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+                               Group userAdminRole = (Group) createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+                               userAdminRole.addMember(adminRole);
+                       }
+                       userTransaction.commit();
+               } catch (Exception e) {
+                       try {
+                               userTransaction.rollback();
+                       } catch (Exception e1) {
+                               // silent
+                       }
+                       throw new IllegalStateException("Cannot add standard system roles", e);
+               }
+       }
+
+       @Override
+       public void deleted(String pid) {
+               // assert pidToServiceRegs.get(pid) != null;
+               assert pidToBaseDn.get(pid) != null;
+               // pidToServiceRegs.remove(pid).unregister();
+               LdapName baseDn = pidToBaseDn.remove(pid);
+               removeUserDirectory(baseDn);
+       }
+
+       @Override
+       public String getName() {
+               return "Node User Admin";
+       }
+
+       @Override
+       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+               if (rawAuthorization.getName() == null) {
+                       sysRoles.add(CmsConstants.ROLE_ANONYMOUS);
+               } else {
+                       sysRoles.add(CmsConstants.ROLE_USER);
+               }
+       }
+
+       protected void postAdd(AbstractUserDirectory userDirectory) {
+               // JTA
+//             WorkControl tm = tmTracker != null ? tmTracker.getService() : null;
+//             if (tm == null)
+//                     throw new IllegalStateException("A JTA transaction manager must be available.");
+               userDirectory.setTransactionControl(transactionManager);
+//             if (tmTracker.getService() instanceof BitronixTransactionManager)
+//                     EhCacheXAResourceProducer.registerXAResource(cacheName, userDirectory.getXaResource());
+
+               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
+               if (realm != null) {
+                       if (Files.exists(nodeKeyTab)) {
+                               String servicePrincipal = getKerberosServicePrincipal(realm.toString());
+                               if (servicePrincipal != null) {
+                                       CallbackHandler callbackHandler = new CallbackHandler() {
+                                               @Override
+                                               public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                                       for (Callback callback : callbacks)
+                                                               if (callback instanceof NameCallback)
+                                                                       ((NameCallback) callback).setName(servicePrincipal);
+
+                                               }
+                                       };
+                                       try {
+                                               LoginContext nodeLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_NODE, callbackHandler);
+                                               nodeLc.login();
+                                               acceptorCredentials = logInAsAcceptor(nodeLc.getSubject(), servicePrincipal);
+                                       } catch (LoginException e) {
+                                               throw new IllegalStateException("Cannot log in kernel", e);
+                                       }
+                               }
+                       }
+
+                       // Register client-side SPNEGO auth scheme
+                       AuthPolicy.registerAuthScheme(SpnegoAuthScheme.NAME, SpnegoAuthScheme.class);
+                       HttpParams params = DefaultHttpParams.getDefaultParams();
+                       ArrayList<String> schemes = new ArrayList<>();
+                       schemes.add(SpnegoAuthScheme.NAME);// SPNEGO preferred
+                       // schemes.add(AuthPolicy.BASIC);// incompatible with Basic
+                       params.setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, schemes);
+                       params.setParameter(CredentialsProvider.PROVIDER, new HttpCredentialProvider());
+                       params.setParameter(HttpMethodParams.COOKIE_POLICY, KernelConstants.COOKIE_POLICY_BROWSER_COMPATIBILITY);
+                       // params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
+               }
+       }
+
+       protected void preDestroy(AbstractUserDirectory userDirectory) {
+//             if (tmTracker.getService() instanceof BitronixTransactionManager)
+//                     EhCacheXAResourceProducer.unregisterXAResource(cacheName, userDirectory.getXaResource());
+
+               Object realm = userDirectory.getProperties().get(UserAdminConf.realm.name());
+               if (realm != null) {
+                       if (acceptorCredentials != null) {
+                               try {
+                                       acceptorCredentials.dispose();
+                               } catch (GSSException e) {
+                                       // silent
+                               }
+                               acceptorCredentials = null;
+                       }
+               }
+       }
+
+       private String getKerberosServicePrincipal(String realm) {
+               String hostname;
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       InetAddress localhost = InetAddress.getLocalHost();
+                       hostname = localhost.getHostName();
+                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+                       String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
+                       boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+                       String kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       if (consistentIp && kerberosDomain != null && kerberosDomain.equals(realm) && Files.exists(nodeKeyTab)) {
+                               return KernelConstants.DEFAULT_KERBEROS_SERVICE + "/" + hostname + "@" + kerberosDomain;
+                       } else
+                               return null;
+               } catch (Exception e) {
+                       log.warn("Exception when determining kerberos principal", e);
+                       return null;
+               }
+       }
+
+       private GSSCredential logInAsAcceptor(Subject subject, String servicePrincipal) {
+               // GSS
+               Iterator<KerberosPrincipal> krb5It = subject.getPrincipals(KerberosPrincipal.class).iterator();
+               if (!krb5It.hasNext())
+                       return null;
+               KerberosPrincipal krb5Principal = null;
+               while (krb5It.hasNext()) {
+                       KerberosPrincipal principal = krb5It.next();
+                       if (principal.getName().equals(servicePrincipal))
+                               krb5Principal = principal;
+               }
+
+               if (krb5Principal == null)
+                       return null;
+
+               GSSManager manager = GSSManager.getInstance();
+               try {
+                       GSSName gssName = manager.createName(krb5Principal.getName(), null);
+                       GSSCredential serverCredentials = Subject.doAs(subject, new PrivilegedExceptionAction<GSSCredential>() {
+
+                               @Override
+                               public GSSCredential run() throws GSSException {
+                                       return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
+                                                       GSSCredential.ACCEPT_ONLY);
+
+                               }
+
+                       });
+                       if (log.isDebugEnabled())
+                               log.debug("GSS acceptor configured for " + krb5Principal);
+                       return serverCredentials;
+               } catch (Exception gsse) {
+                       throw new IllegalStateException("Cannot create acceptor credentials for " + krb5Principal, gsse);
+               }
+       }
+
+       public GSSCredential getAcceptorCredentials() {
+               return acceptorCredentials;
+       }
+
+       public boolean hasAcceptorCredentials() {
+               return acceptorCredentials != null;
+       }
+
+       public boolean isSingleUser() {
+               return singleUser;
+       }
+
+       public void setTransactionManager(WorkControl transactionManager) {
+               this.transactionManager = transactionManager;
+       }
+
+       public void setUserTransaction(WorkTransaction userTransaction) {
+               this.userTransaction = userTransaction;
+       }
+
+       /*
+        * STATIC
+        */
+
+       public final static Oid KERBEROS_OID;
+       static {
+               try {
+                       KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+               } catch (GSSException e) {
+                       throw new IllegalStateException("Cannot create Kerberos OID", e);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java b/org.argeo.cms/src/org/argeo/cms/internal/osgi/SecurityProfile.java
new file mode 100644 (file)
index 0000000..7055538
--- /dev/null
@@ -0,0 +1,324 @@
+package org.argeo.cms.internal.osgi;
+
+import java.io.FilePermission;
+import java.lang.reflect.ReflectPermission;
+import java.net.SocketPermission;
+import java.security.AllPermission;
+import java.util.PropertyPermission;
+
+import javax.security.auth.AuthPermission;
+
+import org.osgi.framework.AdminPermission;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServicePermission;
+import org.osgi.service.cm.ConfigurationPermission;
+import org.osgi.service.condpermadmin.BundleLocationCondition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+/** Security profile based on OSGi {@link PermissionAdmin}. */
+public interface SecurityProfile {
+       BundleContext bc = FrameworkUtil.getBundle(SecurityProfile.class).getBundleContext();
+
+       default void applySystemPermissions(ConditionalPermissionAdmin permissionAdmin) {
+               ConditionalPermissionUpdate update = permissionAdmin.newConditionalPermissionUpdate();
+               // Self
+//             String nodeAPiBundleLocation = locate(NodeUtils.class);
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { nodeAPiBundleLocation }) },
+//                                             new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+//                                             ConditionalPermissionInfo.ALLOW));
+               String cmsBundleLocation = locate(SecurityProfile.class);
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { cmsBundleLocation }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+               String frameworkBundleLocation = bc.getBundle(0).getLocation();
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { frameworkBundleLocation }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null) },
+                                               ConditionalPermissionInfo.ALLOW));
+               // All
+               // FIXME understand why Jetty and Jackrabbit require that
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null, null, new PermissionInfo[] {
+                                               new PermissionInfo(SocketPermission.class.getName(), "localhost:7070", "listen,resolve"),
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "DEBUG", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "STOP.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "org.apache.jackrabbit.*", "read"),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Eclipse
+               // update.getConditionalPermissionInfos()
+               // .add(permissionAdmin.newConditionalPermissionInfo(null,
+               // new ConditionInfo[] { new
+               // ConditionInfo(BundleLocationCondition.class.getName(),
+               // new String[] { "*/org.eclipse.*" }) },
+               // new PermissionInfo[] { new
+               // PermissionInfo(RuntimePermission.class.getName(), "*", "*"),
+               // new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+               // new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+               // new PermissionInfo(ServicePermission.class.getName(), "*",
+               // "register"),
+               // new PermissionInfo(TopicPermission.class.getName(), "*", "publish"),
+               // new PermissionInfo(TopicPermission.class.getName(), "*",
+               // "subscribe"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "osgi.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(),
+               // "org.eclipse.*", "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "equinox.*",
+               // "read"),
+               // new PermissionInfo(PropertyPermission.class.getName(), "xml.*",
+               // "read"),
+               // new PermissionInfo("org.eclipse.equinox.log.LogPermission", "*",
+               // "log"), },
+               // ConditionalPermissionInfo.ALLOW));
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { "*/org.eclipse.*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
+                                               ConditionalPermissionInfo.ALLOW));
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { "*/org.apache.felix.*" }) },
+                                               new PermissionInfo[] { new PermissionInfo(AllPermission.class.getName(), null, null), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Configuration admin
+//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                             new String[] { locate(configurationAdmin.getService().getClass()) }) },
+//                             new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
+//                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+//                                             new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"), },
+//                             ConditionalPermissionInfo.ALLOW));
+
+               // Bitronix
+//             update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+//                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                             new String[] { locate(BitronixTransactionManager.class) }) },
+//                             new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "bitronix.tm.*", "read"),
+//                                             new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
+//                                             new PermissionInfo(MBeanServerPermission.class.getName(), "createMBeanServer", null),
+//                                             new PermissionInfo(MBeanPermission.class.getName(), "bitronix.tm.*", "registerMBean"),
+//                                             new PermissionInfo(MBeanTrustPermission.class.getName(), "register", null) },
+//                             ConditionalPermissionInfo.ALLOW));
+
+               // DS
+               Bundle dsBundle = findBundle("org.eclipse.equinox.ds");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { dsBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(ConfigurationPermission.class.getName(), "*", "configure"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "xml.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "accessDeclaredMembers", null),
+                                               new PermissionInfo(RuntimePermission.class.getName(), "getClassLoader", null),
+                                               new PermissionInfo(ReflectPermission.class.getName(), "suppressAccessChecks", null), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // Jetty
+               // Bundle jettyUtilBundle = findBundle("org.eclipse.equinox.http.jetty");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { "*/org.eclipse.jetty.*" }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle servletBundle = findBundle("javax.servlet");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { servletBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(),
+                                               "org.glassfish.web.rfc2109_cookie_names_enforced", "read") },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // required to be able to get the BundleContext in the customizer
+               Bundle jettyCustomizerBundle = findBundle("org.argeo.ext.equinox.jetty");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { jettyCustomizerBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Blueprint
+//             Bundle blueprintBundle = findBundle("org.eclipse.gemini.blueprint.core");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { blueprintBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle blueprintExtenderBundle = findBundle("org.eclipse.gemini.blueprint.extender");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin
+//                                             .newConditionalPermissionInfo(null,
+//                                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                                             new String[] { blueprintExtenderBundle.getLocation() }) },
+//                                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                                             new PermissionInfo(PropertyPermission.class.getName(), "org.eclipse.gemini.*",
+//                                                                                             "read"),
+//                                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"),
+//                                                                             new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
+//                                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle springCoreBundle = findBundle("org.springframework.core");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { springCoreBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+//             Bundle blueprintIoBundle = findBundle("org.eclipse.gemini.blueprint.io");
+//             update.getConditionalPermissionInfos()
+//                             .add(permissionAdmin.newConditionalPermissionInfo(null,
+//                                             new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+//                                                             new String[] { blueprintIoBundle.getLocation() }) },
+//                                             new PermissionInfo[] { new PermissionInfo(RuntimePermission.class.getName(), "*", null),
+//                                                             new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+//                                             ConditionalPermissionInfo.ALLOW));
+
+               // Equinox
+               Bundle registryBundle = findBundle("org.eclipse.equinox.registry");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { registryBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "eclipse.*", "read"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "osgi.*", "read"),
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle equinoxUtilBundle = findBundle("org.eclipse.equinox.util");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { equinoxUtilBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "equinox.*", "read"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "get"),
+                                               new PermissionInfo(ServicePermission.class.getName(), "*", "register"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle equinoxCommonBundle = findBundle("org.eclipse.equinox.common");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { equinoxCommonBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AdminPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle consoleBundle = findBundle("org.eclipse.equinox.console");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { consoleBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(ServicePermission.class.getName(), "*", "register"),
+                                                               new PermissionInfo(AdminPermission.class.getName(), "*", "listener") },
+                                               ConditionalPermissionInfo.ALLOW));
+               Bundle preferencesBundle = findBundle("org.eclipse.equinox.preferences");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { preferencesBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle appBundle = findBundle("org.eclipse.equinox.app");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { appBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // Jackrabbit
+               Bundle jackrabbitCoreBundle = findBundle("org.apache.jackrabbit.core");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitCoreBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
+                                               new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "getLoginConfiguration", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle jackrabbitDataBundle = findBundle("org.apache.jackrabbit.data");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitDataBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write") },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle jackrabbitCommonBundle = findBundle("org.apache.jackrabbit.jcr.commons");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { jackrabbitCommonBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "getSubject", null),
+                                               new PermissionInfo(AuthPermission.class.getName(), "createLoginContext.Jackrabbit", null), },
+                               ConditionalPermissionInfo.ALLOW));
+
+               Bundle jackrabbitExtBundle = findBundle("org.argeo.ext.jackrabbit");
+               update.getConditionalPermissionInfos()
+                               .add(permissionAdmin.newConditionalPermissionInfo(null,
+                                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                                               new String[] { jackrabbitExtBundle.getLocation() }) },
+                                               new PermissionInfo[] { new PermissionInfo(AuthPermission.class.getName(), "*", "*"), },
+                                               ConditionalPermissionInfo.ALLOW));
+
+               // Tika
+               Bundle tikaCoreBundle = findBundle("org.apache.tika.core");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { tikaCoreBundle.getLocation() }) },
+                               new PermissionInfo[] { new PermissionInfo(PropertyPermission.class.getName(), "*", "read,write"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
+                               ConditionalPermissionInfo.ALLOW));
+               Bundle luceneBundle = findBundle("org.apache.lucene");
+               update.getConditionalPermissionInfos().add(permissionAdmin.newConditionalPermissionInfo(null,
+                               new ConditionInfo[] { new ConditionInfo(BundleLocationCondition.class.getName(),
+                                               new String[] { luceneBundle.getLocation() }) },
+                               new PermissionInfo[] {
+                                               new PermissionInfo(FilePermission.class.getName(), "<<ALL FILES>>", "read,write,delete"),
+                                               new PermissionInfo(PropertyPermission.class.getName(), "*", "read"),
+                                               new PermissionInfo(AdminPermission.class.getName(), "*", "*") },
+                               ConditionalPermissionInfo.ALLOW));
+
+               // COMMIT
+               update.commit();
+       }
+
+       /** @return bundle location */
+       default String locate(Class<?> clzz) {
+               return FrameworkUtil.getBundle(clzz).getLocation();
+       }
+
+       /** Can be null */
+       default Bundle findBundle(String symbolicName) {
+               for (Bundle b : bc.getBundles())
+                       if (b.getSymbolicName().equals(symbolicName))
+                               return b;
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore b/org.argeo.cms/src/org/argeo/cms/internal/runtime/.gitignore
new file mode 100644 (file)
index 0000000..50e1322
--- /dev/null
@@ -0,0 +1 @@
+/*.log
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsContextImpl.java
new file mode 100644 (file)
index 0000000..8e29f66
--- /dev/null
@@ -0,0 +1,201 @@
+package org.argeo.cms.internal.runtime;
+
+import static java.util.Locale.ENGLISH;
+
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsContext;
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.internal.osgi.NodeUserAdmin;
+import org.ietf.jgss.GSSCredential;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class CmsContextImpl implements CmsContext {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+//     private EgoRepository egoRepository;
+       private static CompletableFuture<CmsContextImpl> instance = new CompletableFuture<CmsContextImpl>();
+
+       private CmsState cmsState;
+       private CmsDeployment cmsDeployment;
+       private UserAdmin userAdmin;
+
+       // i18n
+       private Locale defaultLocale;
+       private List<Locale> locales = null;
+
+       private Long availableSince;
+
+//     public CmsContextImpl() {
+//             initTrackers();
+//     }
+
+       public void start() {
+               Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
+               defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
+                               : new Locale(ENGLISH.getLanguage());
+               locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
+               // node repository
+//             new ServiceTracker<Repository, Repository>(bc, Repository.class, null) {
+//                     @Override
+//                     public Repository addingService(ServiceReference<Repository> reference) {
+//                             Object cn = reference.getProperty(NodeConstants.CN);
+//                             if (cn != null && cn.equals(NodeConstants.EGO_REPOSITORY)) {
+////                                   egoRepository = (EgoRepository) bc.getService(reference);
+//                                     if (log.isTraceEnabled())
+//                                             log.trace("Home repository is available");
+//                             }
+//                             return super.addingService(reference);
+//                     }
+//
+//                     @Override
+//                     public void removedService(ServiceReference<Repository> reference, Repository service) {
+//                             super.removedService(reference, service);
+////                           egoRepository = null;
+//                     }
+//
+//             }.open();
+
+               checkReadiness();
+
+               setInstance(this);
+       }
+
+       public void stop() {
+               setInstance(null);
+       }
+
+       /**
+        * Checks whether the deployment is available according to expectations, and
+        * mark it as available.
+        */
+       private void checkReadiness() {
+               if (isAvailable())
+                       return;
+               if (cmsDeployment != null && userAdmin != null) {
+                       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 CMS AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##");
+                       if (log.isDebugEnabled()) {
+                               log.debug("## state: " + state);
+                               if (data != null)
+                                       log.debug("## data: " + data);
+                       }
+                       long begin = cmsState.getAvailableSince();
+                       long initDuration = System.currentTimeMillis() - begin;
+                       if (log.isTraceEnabled())
+                               log.trace("Kernel initialization took " + initDuration + "ms");
+                       tributeToFreeSoftware(initDuration);
+               } else {
+                       throw new IllegalStateException("Deployment is not available");
+               }
+       }
+
+       final private void tributeToFreeSoftware(long initDuration) {
+               if (log.isTraceEnabled()) {
+                       long ms = initDuration / 100;
+                       log.trace("Spend " + ms + "ms" + " reflecting on the progress brought to mankind" + " by Free Software...");
+                       long beginNano = System.nanoTime();
+                       try {
+                               Thread.sleep(ms, 0);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       long durationNano = System.nanoTime() - beginNano;
+                       final double M = 1000d * 1000d;
+                       double sleepAccuracy = ((double) durationNano) / (ms * M);
+                       log.trace("Sleep accuracy: " + String.format("%.2f", 100 - (sleepAccuracy * 100 - 100)) + " %");
+               }
+       }
+
+       @Override
+       public void createWorkgroup(String dn) {
+//             if (egoRepository == null)
+//                     throw new CmsException("Ego repository is not available");
+//             // TODO add check that the group exists
+//             egoRepository.createWorkgroup(dn);
+               throw new UnsupportedOperationException();
+       }
+
+       public void setCmsDeployment(CmsDeployment cmsDeployment) {
+               this.cmsDeployment = cmsDeployment;
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+       public void setUserAdmin(UserAdmin userAdmin) {
+               this.userAdmin = userAdmin;
+       }
+
+       @Override
+       public Locale getDefaultLocale() {
+               return defaultLocale;
+       }
+
+       @Override
+       public List<Locale> getLocales() {
+               return locales;
+       }
+
+       @Override
+       public synchronized Long getAvailableSince() {
+               return availableSince;
+       }
+
+       public synchronized boolean isAvailable() {
+               return availableSince != null;
+       }
+
+       /*
+        * STATIC
+        */
+
+       public synchronized static CmsContext getCmsContext() {
+               return getInstance();
+       }
+
+       /** Required by USER login module. */
+       public synchronized static UserAdmin getUserAdmin() {
+               return getInstance().userAdmin;
+       }
+
+       /** Required by SPNEGO login module. */
+       @Deprecated
+       public synchronized static GSSCredential getAcceptorCredentials() {
+               // FIXME find a cleaner way
+               return ((NodeUserAdmin) getInstance().userAdmin).getAcceptorCredentials();
+       }
+
+       private synchronized static void setInstance(CmsContextImpl cmsContextImpl) {
+               if (cmsContextImpl != null) {
+                       if (instance.isDone())
+                               throw new IllegalStateException("CMS Context is already set");
+                       instance.complete(cmsContextImpl);
+               } else {
+                       instance = new CompletableFuture<CmsContextImpl>();
+               }
+       }
+
+       private synchronized static CmsContextImpl getInstance() {
+               try {
+                       return instance.get();
+               } catch (InterruptedException | ExecutionException e) {
+                       throw new IllegalStateException("Cannot retrieve CMS Context", e);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsDeploymentImpl.java
new file mode 100644 (file)
index 0000000..4ffa03a
--- /dev/null
@@ -0,0 +1,207 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Dictionary;
+
+import org.argeo.api.cms.CmsDeployment;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.internal.osgi.DeployConfig;
+import org.osgi.service.http.HttpService;
+
+/** Implementation of a CMS deployment. */
+public class CmsDeploymentImpl implements CmsDeployment {
+       private final CmsLog log = CmsLog.getLog(getClass());
+//     private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+//     private Long availableSince;
+
+       // Readiness
+//     private boolean nodeAvailable = false;
+//     private boolean userAdminAvailable = false;
+       private boolean httpExpected = false;
+//     private boolean httpAvailable = false;
+       private HttpService httpService;
+
+       private CmsState cmsState;
+       private DeployConfig deployConfig;
+
+       public CmsDeploymentImpl() {
+//             ServiceReference<NodeState> nodeStateSr = bc.getServiceReference(NodeState.class);
+//             if (nodeStateSr == null)
+//                     throw new CmsException("No node state available");
+
+//             NodeState nodeState = bc.getService(nodeStateSr);
+//             cleanState = nodeState.isClean();
+
+//             nodeHttp = new NodeHttp();
+               initTrackers();
+       }
+
+       private void initTrackers() {
+//             ServiceTracker<?, ?> httpSt = new ServiceTracker<HttpService, HttpService>(bc, HttpService.class, null) {
+//
+//                     @Override
+//                     public HttpService addingService(ServiceReference<HttpService> sr) {
+//                             httpAvailable = true;
+//                             Object httpPort = sr.getProperty("http.port");
+//                             Object httpsPort = sr.getProperty("https.port");
+//                             log.info(httpPortsMsg(httpPort, httpsPort));
+//                             checkReadiness();
+//                             return super.addingService(sr);
+//                     }
+//             };
+//             // httpSt.open();
+//             KernelUtils.asyncOpen(httpSt);
+
+//             ServiceTracker<?, ?> userAdminSt = new ServiceTracker<UserAdmin, UserAdmin>(bc, UserAdmin.class, null) {
+//                     @Override
+//                     public UserAdmin addingService(ServiceReference<UserAdmin> reference) {
+//                             UserAdmin userAdmin = super.addingService(reference);
+//                             addStandardSystemRoles(userAdmin);
+//                             userAdminAvailable = true;
+//                             checkReadiness();
+//                             return userAdmin;
+//                     }
+//             };
+//             // userAdminSt.open();
+//             KernelUtils.asyncOpen(userAdminSt);
+
+//             ServiceTracker<?, ?> confAdminSt = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(bc,
+//                             ConfigurationAdmin.class, null) {
+//                     @Override
+//                     public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) {
+//                             ConfigurationAdmin configurationAdmin = bc.getService(reference);
+////                           boolean isClean;
+////                           try {
+////                                   Configuration[] confs = configurationAdmin
+////                                                   .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+////                                   isClean = confs == null || confs.length == 0;
+////                           } catch (Exception e) {
+////                                   throw new IllegalStateException("Cannot analyse clean state", e);
+////                           }
+//                             deployConfig = new DeployConfig(configurationAdmin, isClean);
+//                             Activator.registerService(CmsDeployment.class, CmsDeploymentImpl.this, null);
+////                           JcrInitUtils.addToDeployment(CmsDeployment.this);
+//                             httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
+//                             try {
+//                                     Configuration[] configs = configurationAdmin
+//                                                     .listConfigurations("(service.factoryPid=" + CmsConstants.NODE_USER_ADMIN_PID + ")");
+//
+//                                     boolean hasDomain = false;
+//                                     for (Configuration config : configs) {
+//                                             Object realm = config.getProperties().get(UserAdminConf.realm.name());
+//                                             if (realm != null) {
+//                                                     log.debug("Found realm: " + realm);
+//                                                     hasDomain = true;
+//                                             }
+//                                     }
+//                                     if (hasDomain) {
+//                                             loadIpaJaasConfiguration();
+//                                     }
+//                             } catch (Exception e) {
+//                                     throw new IllegalStateException("Cannot initialize config", e);
+//                             }
+//                             return super.addingService(reference);
+//                     }
+//             };
+//             // confAdminSt.open();
+//             KernelUtils.asyncOpen(confAdminSt);
+       }
+
+       public void start() {
+               httpExpected = deployConfig.getProps(KernelConstants.JETTY_FACTORY_PID, "default") != null;
+               if (deployConfig.hasDomain()) {
+                       loadIpaJaasConfiguration();
+               }
+
+//             while (!isHttpAvailableOrNotExpected()) {
+//                     try {
+//                             Thread.sleep(100);
+//                     } catch (InterruptedException e) {
+//                             log.error("Interrupted while waiting for http");
+//                     }
+//             }
+       }
+
+       public void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props) {
+               deployConfig.putFactoryDeployConfig(factoryPid, props);
+               deployConfig.save();
+               try {
+                       deployConfig.loadConfigs();
+               } catch (IOException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public Dictionary<String, Object> getProps(String factoryPid, String cn) {
+               return deployConfig.getProps(factoryPid, cn);
+       }
+
+//     private void addStandardSystemRoles(UserAdmin userAdmin) {
+//             // we assume UserTransaction is already available (TODO make it more robust)
+//             WorkTransaction userTransaction = bc.getService(bc.getServiceReference(WorkTransaction.class));
+//             try {
+//                     userTransaction.begin();
+//                     Role adminRole = userAdmin.getRole(CmsConstants.ROLE_ADMIN);
+//                     if (adminRole == null) {
+//                             adminRole = userAdmin.createRole(CmsConstants.ROLE_ADMIN, Role.GROUP);
+//                     }
+//                     if (userAdmin.getRole(CmsConstants.ROLE_USER_ADMIN) == null) {
+//                             Group userAdminRole = (Group) userAdmin.createRole(CmsConstants.ROLE_USER_ADMIN, Role.GROUP);
+//                             userAdminRole.addMember(adminRole);
+//                     }
+//                     userTransaction.commit();
+//             } catch (Exception e) {
+//                     try {
+//                             userTransaction.rollback();
+//                     } catch (Exception e1) {
+//                             // silent
+//                     }
+//                     throw new IllegalStateException("Cannot add standard system roles", e);
+//             }
+//     }
+
+       public boolean isHttpAvailableOrNotExpected() {
+               return (httpExpected ? httpService != null : true);
+       }
+
+       private void loadIpaJaasConfiguration() {
+               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+                       String jaasConfig = KernelConstants.JAAS_CONFIG_IPA;
+                       URL url = getClass().getClassLoader().getResource(jaasConfig);
+                       KernelUtils.setJaasConfiguration(url);
+                       log.debug("Set IPA JAAS configuration.");
+               }
+       }
+
+       public void stop() {
+//             if (nodeHttp != null)
+//                     nodeHttp.destroy();
+
+//             try {
+//                     JettyConfigurator.stopServer(KernelConstants.DEFAULT_JETTY_SERVER);
+//             } catch (Exception e) {
+//                     log.error("Cannot stop default Jetty server.", e);
+//             }
+
+               if (deployConfig != null) {
+                       deployConfig.save();
+                       // new Thread(() -> deployConfig.save(), "Save Argeo Deploy Config").start();
+               }
+       }
+
+       public void setDeployConfig(DeployConfig deployConfig) {
+               this.deployConfig = deployConfig;
+       }
+
+       public void setCmsState(CmsState cmsState) {
+               this.cmsState = cmsState;
+       }
+
+       public void setHttpService(HttpService httpService) {
+               this.httpService = httpService;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
new file mode 100644 (file)
index 0000000..b493c08
--- /dev/null
@@ -0,0 +1,239 @@
+package org.argeo.cms.internal.runtime;
+
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+
+import javax.security.auth.login.Configuration;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.auth.ident.IdentClient;
+import org.argeo.cms.internal.osgi.CmsShutdown;
+import org.osgi.framework.Constants;
+
+/**
+ * Implementation of a {@link CmsState}, initialising the required services.
+ */
+public class CmsStateImpl implements CmsState {
+       private final static CmsLog log = CmsLog.getLog(CmsStateImpl.class);
+//     private final BundleContext bc = FrameworkUtil.getBundle(CmsState.class).getBundleContext();
+
+//     private static CmsStateImpl instance;
+
+//     private ExecutorService internalExecutorService;
+
+       // REFERENCES
+       private Long availableSince;
+
+//     private ThreadGroup threadGroup = new ThreadGroup("CMS");
+//     private List<Runnable> stopHooks = new ArrayList<>();
+
+       private String stateUuid;
+//     private final boolean cleanState;
+       private String hostname;
+
+       public void start() {
+//             instance = this;
+
+               Runtime.getRuntime().addShutdownHook(new CmsShutdown());
+//             this.internalExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+               try {
+                       initSecurity();
+//                     initArgeoLogger();
+//                     initNode();
+
+                       if (log.isTraceEnabled())
+                               log.trace("CMS State started");
+
+                       this.stateUuid = KernelUtils.getFrameworkProp(Constants.FRAMEWORK_UUID);
+//             this.cleanState = stateUuid.equals(frameworkUuid);
+                       try {
+                               this.hostname = InetAddress.getLocalHost().getHostName();
+                       } catch (UnknownHostException e) {
+                               log.error("Cannot set hostname: " + e);
+                       }
+
+                       availableSince = System.currentTimeMillis();
+                       if (log.isDebugEnabled())
+                               // log.debug("## CMS starting... stateUuid=" + this.stateUuid + (cleanState ? "
+                               // (clean state) " : " "));
+                               log.debug("## CMS starting... (" + stateUuid + ")");
+
+//             initI18n();
+//             initServices();
+
+               } catch (RuntimeException e) {
+                       log.error("## FATAL: CMS activator failed", e);
+               }
+       }
+
+       private void initSecurity() {
+               if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
+                       String jaasConfig = KernelConstants.JAAS_CONFIG;
+                       URL url = getClass().getResource(jaasConfig);
+                       // System.setProperty(KernelConstants.JAAS_CONFIG_PROP,
+                       // url.toExternalForm());
+                       KernelUtils.setJaasConfiguration(url);
+               }
+               // explicitly load JAAS configuration
+               Configuration.getConfiguration();
+       }
+
+//     private void initI18n() {
+//             Object defaultLocaleValue = KernelUtils.getFrameworkProp(CmsConstants.I18N_DEFAULT_LOCALE);
+//             defaultLocale = defaultLocaleValue != null ? new Locale(defaultLocaleValue.toString())
+//                             : new Locale(ENGLISH.getLanguage());
+//             locales = LocaleUtils.asLocaleList(KernelUtils.getFrameworkProp(CmsConstants.I18N_LOCALES));
+//     }
+
+       private void initServices() {
+               // JTA
+//             String tmType = KernelUtils.getFrameworkProp(CmsConstants.TRANSACTION_MANAGER,
+//                             CmsConstants.TRANSACTION_MANAGER_SIMPLE);
+//             if (CmsConstants.TRANSACTION_MANAGER_SIMPLE.equals(tmType)) {
+//                     initSimpleTransactionManager();
+//             } else if (CmsConstants.TRANSACTION_MANAGER_BITRONIX.equals(tmType)) {
+////                   initBitronixTransactionManager();
+//                     throw new UnsupportedOperationException(
+//                                     "Bitronix is not supported anymore, but could be again if there is enough interest.");
+//             } else {
+//                     throw new IllegalArgumentException("Usupported transaction manager type " + tmType);
+//             }
+
+               // POI
+//             POIXMLTypeLoader.setClassLoader(CTConnection.class.getClassLoader());
+
+               // Tika
+//             OpenDocumentParser odfParser = new OpenDocumentParser();
+//             bc.registerService(Parser.class, odfParser, new Hashtable());
+//             PDFParser pdfParser = new PDFParser();
+//             bc.registerService(Parser.class, pdfParser, new Hashtable());
+//             OOXMLParser ooxmlParser = new OOXMLParser();
+//             bc.registerService(Parser.class, ooxmlParser, new Hashtable());
+//             TesseractOCRParser ocrParser = new TesseractOCRParser();
+//             ocrParser.setLanguage("ara");
+//             bc.registerService(Parser.class, ocrParser, new Hashtable());
+
+//             // JCR
+//             RepositoryServiceFactory repositoryServiceFactory = new RepositoryServiceFactory();
+//             stopHooks.add(() -> repositoryServiceFactory.shutdown());
+//             Activator.registerService(ManagedServiceFactory.class, repositoryServiceFactory,
+//                             LangUtils.dict(Constants.SERVICE_PID, NodeConstants.NODE_REPOS_FACTORY_PID));
+//
+//             NodeRepositoryFactory repositoryFactory = new NodeRepositoryFactory();
+//             Activator.registerService(RepositoryFactory.class, repositoryFactory, null);
+
+               // Security
+//             NodeUserAdmin userAdmin = new NodeUserAdmin(CmsConstants.ROLES_BASEDN, CmsConstants.TOKENS_BASEDN);
+//             stopHooks.add(() -> userAdmin.destroy());
+//             Activator.registerService(ManagedServiceFactory.class, userAdmin,
+//                             LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_USER_ADMIN_PID));
+
+       }
+
+//     private void initSimpleTransactionManager() {
+//             SimpleTransactionManager transactionManager = new SimpleTransactionManager();
+//             Activator.registerService(WorkControl.class, transactionManager, null);
+//             Activator.registerService(WorkTransaction.class, transactionManager, null);
+////           Activator.registerService(TransactionManager.class, transactionManager, null);
+////           Activator.registerService(UserTransaction.class, transactionManager, null);
+//             // TODO TransactionSynchronizationRegistry
+//     }
+
+//     private void initBitronixTransactionManager() {
+//             // TODO manage it in a managed service, as startup could be long
+//             ServiceReference<TransactionManager> existingTm = bc.getServiceReference(TransactionManager.class);
+//             if (existingTm != null) {
+//                     if (log.isDebugEnabled())
+//                             log.debug("Using provided transaction manager " + existingTm);
+//                     return;
+//             }
+//
+//             if (!TransactionManagerServices.isTransactionManagerRunning()) {
+//                     bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
+//                     tmConf.setServerId(UUID.randomUUID().toString());
+//
+//                     Bundle bitronixBundle = FrameworkUtil.getBundle(bitronix.tm.Configuration.class);
+//                     File tmBaseDir = bitronixBundle.getDataFile(KernelConstants.DIR_TRANSACTIONS);
+//                     File tmDir1 = new File(tmBaseDir, "btm1");
+//                     tmDir1.mkdirs();
+//                     tmConf.setLogPart1Filename(new File(tmDir1, tmDir1.getName() + ".tlog").getAbsolutePath());
+//                     File tmDir2 = new File(tmBaseDir, "btm2");
+//                     tmDir2.mkdirs();
+//                     tmConf.setLogPart2Filename(new File(tmDir2, tmDir2.getName() + ".tlog").getAbsolutePath());
+//             }
+//             BitronixTransactionManager transactionManager = getTransactionManager();
+//             stopHooks.add(() -> transactionManager.shutdown());
+//             BitronixTransactionSynchronizationRegistry transactionSynchronizationRegistry = getTransactionSynchronizationRegistry();
+//             // register
+//             bc.registerService(TransactionManager.class, transactionManager, null);
+//             bc.registerService(UserTransaction.class, transactionManager, null);
+//             bc.registerService(TransactionSynchronizationRegistry.class, transactionSynchronizationRegistry, null);
+//             if (log.isDebugEnabled())
+//                     log.debug("Initialised default Bitronix transaction manager");
+//     }
+
+       public void stop() {
+               if (log.isDebugEnabled())
+                       log.debug("CMS stopping...  (" + this.stateUuid + ")");
+//             new GogoShellKiller().start();
+
+               // In a different thread in order to avoid interruptions
+//             Thread stopHookThread = new Thread(() -> applyStopHooks(), "Apply Argeo Stop Hooks");
+//             stopHookThread.start();
+//             try {
+//                     stopHookThread.join(10 * 60 * 1000);
+//             } catch (InterruptedException e) {
+//                     // silent
+//             }
+
+//             internalExecutorService.shutdown();
+
+               long duration = ((System.currentTimeMillis() - availableSince) / 1000) / 60;
+               log.info("## ARGEO CMS STOPPED after " + (duration / 60) + "h " + (duration % 60) + "min uptime ##");
+       }
+
+       /** Apply shutdown hoos in reverse order. */
+//     private void applyStopHooks() {
+////           for (int i = stopHooks.size() - 1; i >= 0; i--) {
+////                   try {
+////                           stopHooks.get(i).run();
+////                   } catch (Exception e) {
+////                           log.error("Could not run shutdown hook #" + i);
+////                   }
+////           }
+//             // Clean hanging Gogo shell thread
+//             new GogoShellKiller().start();
+//
+////           instance = null;
+//     }
+
+//     @Override
+//     public boolean isClean() {
+//             return cleanState;
+//     }
+
+       @Override
+       public Long getAvailableSince() {
+               return availableSince;
+       }
+
+       /*
+        * ACCESSORS
+        */
+       public String getHostname() {
+               return hostname;
+       }
+
+       /*
+        * STATIC
+        */
+       public static IdentClient getIdentClient(String remoteAddr) {
+               if (!IdentClient.isDefaultAuthdPassphraseFileAvailable())
+                       return null;
+               // TODO make passphrase more configurable
+               return new IdentClient(remoteAddr);
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/InitUtils.java
new file mode 100644 (file)
index 0000000..70ea9ec
--- /dev/null
@@ -0,0 +1,287 @@
+package org.argeo.cms.internal.runtime;
+
+import static org.argeo.cms.internal.runtime.KernelUtils.getFrameworkProp;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.InetAddress;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.io.FileUtils;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.http.InternalHttpConstants;
+import org.argeo.osgi.useradmin.UserAdminConf;
+
+/**
+ * Interprets framework properties in order to generate the initial deploy
+ * configuration.
+ */
+public class InitUtils {
+       private final static CmsLog log = CmsLog.getLog(InitUtils.class);
+
+       /** Override the provided config with the framework properties */
+       public static Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
+               String httpPort = getFrameworkProp("org.osgi.service.http.port");
+               String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
+               /// TODO make it more generic
+               String httpHost = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTP_HOST);
+               String httpsHost = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.HTTPS_HOST);
+               String webSocketEnabled = getFrameworkProp(
+                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.WEBSOCKET_ENABLED);
+
+               final Hashtable<String, Object> props = new Hashtable<String, Object>();
+               // try {
+               if (httpPort != null || httpsPort != null) {
+                       boolean httpEnabled = httpPort != null;
+                       props.put(InternalHttpConstants.HTTP_ENABLED, httpEnabled);
+                       boolean httpsEnabled = httpsPort != null;
+                       props.put(InternalHttpConstants.HTTPS_ENABLED, httpsEnabled);
+
+                       if (httpEnabled) {
+                               props.put(InternalHttpConstants.HTTP_PORT, httpPort);
+                               if (httpHost != null)
+                                       props.put(InternalHttpConstants.HTTP_HOST, httpHost);
+                       }
+
+                       if (httpsEnabled) {
+                               props.put(InternalHttpConstants.HTTPS_PORT, httpsPort);
+                               if (httpsHost != null)
+                                       props.put(InternalHttpConstants.HTTPS_HOST, httpsHost);
+
+                               // server certificate
+                               Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
+                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
+                               String keyStorePasswordStr = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
+                               char[] keyStorePassword;
+                               if (keyStorePasswordStr == null)
+                                       keyStorePassword = "changeit".toCharArray();
+                               else
+                                       keyStorePassword = keyStorePasswordStr.toCharArray();
+
+                               // if PEM files both exists, update the PKCS12 file
+                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
+                                       // TODO check certificate update time? monitor changes?
+                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
+                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
+                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
+                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
+                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                                               if (log.isDebugEnabled())
+                                                       log.debug("PEM certificate stored in " + keyStorePath);
+                                       } catch (IOException e) {
+                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
+                                       }
+                               }
+
+                               if (!Files.exists(keyStorePath))
+                                       createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
+                               props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
+                               props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
+                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
+
+//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
+//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
+//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
+
+                               // client certificate authentication
+                               String wantClientAuth = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_WANTCLIENTAUTH);
+                               if (wantClientAuth != null)
+                                       props.put(InternalHttpConstants.SSL_WANTCLIENTAUTH, Boolean.parseBoolean(wantClientAuth));
+                               String needClientAuth = getFrameworkProp(
+                                               InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_NEEDCLIENTAUTH);
+                               if (needClientAuth != null)
+                                       props.put(InternalHttpConstants.SSL_NEEDCLIENTAUTH, Boolean.parseBoolean(needClientAuth));
+                       }
+
+                       // web socket
+                       if (webSocketEnabled != null && webSocketEnabled.equals("true"))
+                               props.put(InternalHttpConstants.WEBSOCKET_ENABLED, true);
+
+                       props.put(CmsConstants.CN, CmsConstants.DEFAULT);
+               }
+               return props;
+       }
+
+       public static List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+               List<Dictionary<String, Object>> res = new ArrayList<>();
+               File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
+               List<String> uris = new ArrayList<>();
+
+               // node roles
+               String nodeRolesUri = getFrameworkProp(CmsConstants.ROLES_URI);
+               String baseNodeRoleDn = CmsConstants.ROLES_BASEDN;
+               if (nodeRolesUri == null) {
+                       nodeRolesUri = baseNodeRoleDn + ".ldif";
+                       File nodeRolesFile = new File(nodeBaseDir, nodeRolesUri);
+                       if (!nodeRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeRoleDn + ".ldif"),
+                                                       nodeRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               uris.add(nodeRolesUri);
+
+               // node tokens
+               String nodeTokensUri = getFrameworkProp(CmsConstants.TOKENS_URI);
+               String baseNodeTokensDn = CmsConstants.TOKENS_BASEDN;
+               if (nodeTokensUri == null) {
+                       nodeTokensUri = baseNodeTokensDn + ".ldif";
+                       File nodeTokensFile = new File(nodeBaseDir, nodeTokensUri);
+                       if (!nodeTokensFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(baseNodeTokensDn + ".ldif"),
+                                                       nodeTokensFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resource", e);
+                               }
+                       // nodeRolesUri = nodeRolesFile.toURI().toString();
+               }
+               uris.add(nodeTokensUri);
+
+               // Business roles
+               String userAdminUris = getFrameworkProp(CmsConstants.USERADMIN_URIS);
+               if (userAdminUris == null) {
+                       String demoBaseDn = "dc=example,dc=com";
+                       userAdminUris = demoBaseDn + ".ldif";
+                       File businessRolesFile = new File(nodeBaseDir, userAdminUris);
+                       File systemRolesFile = new File(nodeBaseDir, "ou=roles,ou=node.ldif");
+                       if (!businessRolesFile.exists())
+                               try {
+                                       FileUtils.copyInputStreamToFile(InitUtils.class.getResourceAsStream(demoBaseDn + ".ldif"),
+                                                       businessRolesFile);
+                                       if (!systemRolesFile.exists())
+                                               FileUtils.copyInputStreamToFile(
+                                                               InitUtils.class.getResourceAsStream("example-ou=roles,ou=node.ldif"), systemRolesFile);
+                               } catch (IOException e) {
+                                       throw new RuntimeException("Cannot copy demo resources", e);
+                               }
+                       // userAdminUris = businessRolesFile.toURI().toString();
+                       log.warn("## DEV Using dummy base DN " + demoBaseDn);
+                       // TODO downgrade security level
+               }
+               for (String userAdminUri : userAdminUris.split(" "))
+                       uris.add(userAdminUri);
+
+               // Interprets URIs
+               for (String uri : uris) {
+                       URI u;
+                       try {
+                               u = new URI(uri);
+                               if (u.getPath() == null)
+                                       throw new IllegalArgumentException(
+                                                       "URI " + uri + " must have a path in order to determine base DN");
+                               if (u.getScheme() == null) {
+                                       if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+                                               u = new File(uri).getCanonicalFile().toURI();
+                                       else if (!uri.contains("/")) {
+                                               // u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+                                               u = new URI(uri);
+                                       } else
+                                               throw new IllegalArgumentException("Cannot interpret " + uri + " as an uri");
+                               } else if (u.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
+                                       u = new File(u).getCanonicalFile().toURI();
+                               }
+                       } catch (Exception e) {
+                               throw new RuntimeException("Cannot interpret " + uri + " as an uri", e);
+                       }
+                       Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
+                       res.add(properties);
+               }
+
+               return res;
+       }
+
+       /**
+        * Called before node initialisation, in order populate OSGi instance are with
+        * some files (typically LDIF, etc).
+        */
+       public static void prepareFirstInitInstanceArea() {
+               String nodeInits = getFrameworkProp(CmsConstants.NODE_INIT);
+               if (nodeInits == null)
+                       nodeInits = "../../init";
+
+               for (String nodeInit : nodeInits.split(",")) {
+
+                       if (nodeInit.startsWith("http")) {
+                               // TODO reconnect it
+                               // registerRemoteInit(nodeInit);
+                       } else {
+
+                               // TODO use java.nio.file
+                               File initDir;
+                               if (nodeInit.startsWith("."))
+                                       initDir = KernelUtils.getExecutionDir(nodeInit);
+                               else
+                                       initDir = new File(nodeInit);
+                               // TODO also uncompress archives
+                               if (initDir.exists())
+                                       try {
+                                               FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
+
+                                                       @Override
+                                                       public boolean accept(File pathname) {
+                                                               if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
+                                                                       return false;
+                                                               return true;
+                                                       }
+                                               });
+                                               log.info("CMS initialized from " + initDir.getCanonicalPath());
+                                       } catch (IOException e) {
+                                               throw new RuntimeException("Cannot initialize from " + initDir, e);
+                                       }
+                       }
+               }
+       }
+
+       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
+               // for (Provider provider : Security.getProviders())
+               // System.out.println(provider.getName());
+//             File keyStoreFile = keyStorePath.toFile();
+               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+               if (!Files.exists(keyStorePath)) {
+                       try {
+                               Files.createDirectories(keyStorePath.getParent());
+                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
+                               PkiUtils.generateSelfSignedCertificate(keyStore,
+                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
+                                               1024, keyPwd);
+                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
+                       } catch (Exception e) {
+                               try {
+                                       if (Files.size(keyStorePath) == 0)
+                                               Files.delete(keyStorePath);
+                               } catch (IOException e1) {
+                                       // silent
+                               }
+                               log.error("Cannot create keystore " + keyStorePath, e);
+                       }
+               } else {
+                       throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java
new file mode 100644 (file)
index 0000000..dfe86cf
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.cms.internal.runtime;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** Internal CMS constants. */
+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/runtime/jaas.cfg";
+       String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/runtime/jaas-ipa.cfg";
+
+       // Java
+       String JAAS_CONFIG_PROP = "java.security.auth.login.config";
+
+       // DEFAULTS JCR PATH
+       String DEFAULT_HOME_BASE_PATH = "/home";
+       String DEFAULT_USERS_BASE_PATH = "/users";
+       String DEFAULT_GROUPS_BASE_PATH = "/groups";
+       
+       // KERBEROS
+       String DEFAULT_KERBEROS_SERVICE = "HTTP";
+
+       // HTTP client
+       String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
+
+       // RWT / RAP
+       // String PATH_WORKBENCH = "/ui";
+       // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public";
+
+//     String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config";
+       String JETTY_FACTORY_PID = "org.argeo.equinox.jetty.config";
+       String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern";
+       // default Jetty server configured via JettyConfigurator
+       String DEFAULT_JETTY_SERVER = "default";
+       String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer";
+
+       // avoid dependencies
+       String CONTEXT_NAME_PROP = "contextName";
+       String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri";
+       String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelUtils.java
new file mode 100644 (file)
index 0000000..afcb9ff
--- /dev/null
@@ -0,0 +1,247 @@
+package org.argeo.cms.internal.runtime;
+
+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.URIParameter;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.internal.osgi.CmsActivator;
+
+/** Package utilities */
+public 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(CmsActivator.getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length()))
+                               .getAbsoluteFile();
+       }
+
+       public static Path getOsgiInstancePath(String relativePath) {
+               return Paths.get(getOsgiInstanceUri(relativePath));
+       }
+
+       public static URI getOsgiInstanceUri(String relativePath) {
+               String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA);
+               if (osgiInstanceBaseUri != null)
+                       return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : ""));
+               else
+                       return Paths.get(System.getProperty("user.dir")).toUri();
+       }
+
+       static File getOsgiConfigurationFile(String relativePath) {
+               try {
+                       return new File(new URI(CmsActivator.getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath))
+                                       .getCanonicalFile();
+               } catch (Exception e) {
+                       throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e);
+               }
+       }
+
+       static String getFrameworkProp(String key, String def) {
+               String value;
+               if (CmsActivator.getBundleContext() != null)
+                       value = CmsActivator.getBundleContext().getProperty(key);
+               else
+                       value = System.getProperty(key);
+               if (value == null)
+                       return def;
+               return value;
+       }
+
+       public static String getFrameworkProp(String key) {
+               return getFrameworkProp(key, null);
+       }
+
+       // Security
+       // static Subject anonymousLogin() {
+       // Subject subject = new Subject();
+       // LoginContext lc;
+       // try {
+       // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject);
+       // lc.login();
+       // return subject;
+       // } catch (LoginException e) {
+       // throw new CmsException("Cannot login as anonymous", e);
+       // }
+       // }
+
+       static void logFrameworkProperties(CmsLog log) {
+               for (Object sysProp : new TreeSet<Object>(System.getProperties().keySet())) {
+                       log.debug(sysProp + "=" + getFrameworkProp(sysProp.toString()));
+               }
+               // String[] keys = { Constants.FRAMEWORK_STORAGE,
+               // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION,
+               // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY,
+               // Constants.FRAMEWORK_TRUST_REPOSITORIES,
+               // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR,
+               // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN,
+               // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID };
+               // for (String key : keys)
+               // log.debug(key + "=" + bc.getProperty(key));
+       }
+
+       static void printSystemProperties(PrintStream out) {
+               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(NodeConstants.LOGIN_CONTEXT_DATA_ADMIN);
+//                     loginContext.login();
+//             } catch (LoginException e1) {
+//                     throw new IllegalStateException("Could not login as data admin", e1);
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentCl);
+//             }
+//             return loginContext;
+//     }
+
+//     static void doAsDataAdmin(Runnable action) {
+//             LoginContext loginContext = loginAsDataAdmin();
+//             Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+//
+//                     @Override
+//                     public Void run() {
+//                             try {
+//                                     action.run();
+//                                     return null;
+//                             } finally {
+//                                     try {
+//                                             loginContext.logout();
+//                                     } catch (LoginException e) {
+//                                             throw new IllegalStateException(e);
+//                                     }
+//                             }
+//                     }
+//
+//             });
+//     }
+
+//     public static void asyncOpen(ServiceTracker<?, ?> st) {
+//             Runnable run = new Runnable() {
+//
+//                     @Override
+//                     public void run() {
+//                             st.open();
+//                     }
+//             };
+//             Activator.getInternalExecutorService().execute(run);
+////           new Thread(run, "Open service tracker " + st).start();
+//     }
+
+//     static BundleContext getBundleContext() {
+//             return Activator.getBundleContext();
+//     }
+
+       static boolean asBoolean(String value) {
+               if (value == null)
+                       return false;
+               switch (value) {
+               case "true":
+                       return true;
+               case "false":
+                       return false;
+               default:
+                       throw new IllegalArgumentException("Unsupported value for boolean attribute : " + 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/src/org/argeo/cms/internal/runtime/PkiUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java
new file mode 100644 (file)
index 0000000..474a899
--- /dev/null
@@ -0,0 +1,259 @@
+package org.argeo.cms.internal.runtime;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCSException;
+
+/**
+ * Utilities around private keys and certificate, mostly wrapping BouncyCastle
+ * implementations.
+ */
+class PkiUtils {
+       final static String PKCS12 = "PKCS12";
+
+       private final static String SECURITY_PROVIDER;
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+               SECURITY_PROVIDER = "BC";
+       }
+
+       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
+                       int keySize, char[] keyPassword) {
+               try {
+                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", SECURITY_PROVIDER);
+                       kpGen.initialize(keySize, new SecureRandom());
+                       KeyPair pair = kpGen.generateKeyPair();
+                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
+                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
+                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
+                                       notAfter, x500Principal, pair.getPublic());
+                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(SECURITY_PROVIDER)
+                                       .build(pair.getPrivate());
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
+                                       .getCertificate(certGen.build(sigGen));
+                       cert.checkValidity(new Date());
+                       cert.verify(cert.getPublicKey());
+
+                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
+                       return cert;
+               } catch (GeneralSecurityException | OperatorCreationException e) {
+                       throw new RuntimeException("Cannot generate self-signed certificate", e);
+               }
+       }
+
+       public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+               try {
+                       KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
+                       if (Files.exists(keyStoreFile)) {
+                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
+                                       store.load(fis, keyStorePassword);
+                               }
+                       } else {
+                               store.load(null);
+                       }
+                       return store;
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
+               }
+       }
+
+       public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+               try {
+                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
+                               keyStore.store(fis, keyStorePassword);
+                       }
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
+               }
+       }
+
+//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
+//                     throws Exception {
+//             // Get the private key
+//             FileReader reader = new FileReader(keyFile);
+//
+//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
+//                     @Override
+//                     public char[] getPassword() {
+//                             return password.toCharArray();
+//                     }
+//             });
+//
+//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Get the certificate
+//             reader = new FileReader(cerFile);
+//             pem = new PEMReader(reader);
+//
+//             X509Certificate cert = (X509Certificate) pem.readObject();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Put them into a PKCS12 keystore and write it to a byte[]
+//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
+//             KeyStore ks = KeyStore.getInstance("PKCS12");
+//             ks.load(null);
+//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
+//             ks.store(bos, password.toCharArray());
+//             bos.close();
+//             return bos.toByteArray();
+//     }
+
+       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
+               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
+               X509Certificate certificate = loadPemCertificate(cert);
+               try {
+                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
+                                       new java.security.cert.Certificate[] { certificate });
+               } catch (KeyStoreException e) {
+                       throw new RuntimeException("Cannot store PEM certificate", e);
+               }
+       }
+
+       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
+                       Object object = pemParser.readObject();
+                       PrivateKeyInfo privateKeyInfo;
+                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               if (keyPassword == null)
+                                       throw new IllegalArgumentException("A key password is required");
+                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
+                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
+                       } else if (object instanceof PrivateKeyInfo) {
+                               privateKeyInfo = (PrivateKeyInfo) object;
+                       } else {
+                               throw new IllegalArgumentException("Unsupported format for private key");
+                       }
+                       return converter.getPrivateKey(privateKeyInfo);
+               } catch (IOException | OperatorCreationException | PKCSException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static X509Certificate loadPemCertificate(Reader reader) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
+                                       .getCertificate(certHolder);
+                       return cert;
+               } catch (IOException | CertificateException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static void main(String[] args) throws Exception {
+               final String ALGORITHM = "RSA";
+               final String provider = "BC";
+               SecureRandom secureRandom = new SecureRandom();
+               long begin = System.currentTimeMillis();
+               for (int i = 512; i < 1024; i = i + 2) {
+                       try {
+                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
+                               keyGen.initialize(i, secureRandom);
+                               keyGen.generateKeyPair();
+                       } catch (Exception e) {
+                               System.err.println(i + " : " + e.getMessage());
+                       }
+               }
+               System.out.println((System.currentTimeMillis() - begin) + " ms");
+
+               // // String text = "a";
+               // String text =
+               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
+               // try {
+               // System.out.println(text);
+               // PrivateKey privateKey;
+               // PublicKey publicKey;
+               // char[] password = "changeit".toCharArray();
+               // String alias = "CN=test";
+               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
+               // File p12file = new File("test.p12");
+               // p12file.delete();
+               // if (!p12file.exists()) {
+               // keyStore.load(null);
+               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
+               // 513, password);
+               // try (OutputStream out = new FileOutputStream(p12file)) {
+               // keyStore.store(out, password);
+               // }
+               // }
+               // try (InputStream in = new FileInputStream(p12file)) {
+               // keyStore.load(in, password);
+               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
+               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
+               // }
+               // // KeyPair key;
+               // // final KeyPairGenerator keyGen =
+               // // KeyPairGenerator.getInstance(ALGORITHM);
+               // // keyGen.initialize(4096, new SecureRandom());
+               // // long begin = System.currentTimeMillis();
+               // // key = keyGen.generateKeyPair();
+               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
+               // // keyStore.load(null);
+               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
+               // // try(OutputStream out=new FileOutputStream(p12file)) {
+               // // keyStore.store(out, password);
+               // // }
+               // // privateKey = key.getPrivate();
+               // // publicKey = key.getPublic();
+               //
+               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
+               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
+               // byte[] encrypted = encrypt.doFinal(text.getBytes());
+               // String encryptedBase64 =
+               // Base64.getEncoder().encodeToString(encrypted);
+               // System.out.println(encryptedBase64);
+               // byte[] encryptedFromBase64 =
+               // Base64.getDecoder().decode(encryptedBase64);
+               //
+               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
+               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
+               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
+               // System.out.println(new String(decrypted));
+               // } catch (Exception e) {
+               // e.printStackTrace();
+               // }
+
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/dc=example,dc=com.ldif
new file mode 100644 (file)
index 0000000..43e7ade
--- /dev/null
@@ -0,0 +1,41 @@
+dn: dc=example,dc=com
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: Groups
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: top
+ou: People
+
+dn: uid=demo,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+objectClass: top
+cn: Demo User
+description: Demo user
+givenName: Demo
+mail: demo@localhost
+sn: User
+uid: demo
+userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: uid=root,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: person
+objectClass: organizationalPerson
+objectClass: top
+cn: Super User
+description: Superuser
+givenName: Super
+mail: root@localhost
+sn: User
+uid: root
+userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/example-ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..ffa9073
--- /dev/null
@@ -0,0 +1,12 @@
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=userAdmin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=roles,ou=node
+cn: userAdmin
+
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas-ipa.cfg
new file mode 100644 (file)
index 0000000..c7c804c
--- /dev/null
@@ -0,0 +1,40 @@
+USER {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.SpnegoLoginModule optional;
+    com.sun.security.auth.module.Krb5LoginModule optional tryFirstPass=true;
+    org.argeo.cms.auth.UserAdminLoginModule sufficient;
+};
+
+ANONYMOUS {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.AnonymousLoginModule sufficient;
+};
+
+DATA_ADMIN {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+NODE {
+    com.sun.security.auth.module.Krb5LoginModule optional
+     keyTab="${osgi.instance.area}node/krb5.keytab" 
+     useKeyTab=true
+     storeKey=true;
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+KEYRING {
+    org.argeo.cms.auth.KeyringLoginModule required;
+};
+
+SINGLE_USER {
+    com.sun.security.auth.module.Krb5LoginModule optional
+     principal="${user.name}"
+     storeKey=true
+     useTicketCache=true
+     debug=true;
+    org.argeo.cms.auth.SingleUserLoginModule requisite;
+};
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/internal/runtime/jaas.cfg
new file mode 100644 (file)
index 0000000..364977d
--- /dev/null
@@ -0,0 +1,30 @@
+USER {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.IdentLoginModule optional;
+    org.argeo.cms.auth.UserAdminLoginModule requisite;
+};
+
+ANONYMOUS {
+    org.argeo.cms.auth.RemoteSessionLoginModule sufficient;
+    org.argeo.cms.auth.AnonymousLoginModule requisite;
+};
+
+DATA_ADMIN {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+NODE {
+    org.argeo.cms.auth.DataAdminLoginModule requisite;
+};
+
+KEYRING {
+    org.argeo.cms.auth.KeyringLoginModule required;
+};
+
+SINGLE_USER {
+    org.argeo.cms.auth.SingleUserLoginModule requisite;
+};
+
+Jackrabbit {
+   org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..85247ed
--- /dev/null
@@ -0,0 +1,9 @@
+dn: ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: node
+
+dn: ou=roles,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: roles
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif b/org.argeo.cms/src/org/argeo/cms/internal/runtime/ou=tokens,ou=node.ldif
new file mode 100644 (file)
index 0000000..4ae9b88
--- /dev/null
@@ -0,0 +1,4 @@
+dn: ou=tokens,ou=node
+objectClass: organizationalUnit
+objectClass: top
+ou: tokens
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java b/org.argeo.cms/src/org/argeo/cms/osgi/BundleCmsTheme.java
new file mode 100644 (file)
index 0000000..6c195d4
--- /dev/null
@@ -0,0 +1,366 @@
+package org.argeo.cms.osgi;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.api.cms.CmsTheme;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Simplifies the theming of an app (only RAP is supported at this stage).<br>
+ * 
+ * Additional fonts listed in <code>/fonts.txt</code>.<br>
+ * Additional (standard CSS) header in <code>/header.css</code>.<br>
+ * RAP specific CSS files in <code>/rap/*.css</code>.<br>
+ * All images added as additional resources based on extensions
+ * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
+ */
+public class BundleCmsTheme implements CmsTheme {
+       public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
+
+       public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
+       public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
+
+       private final static String HEADER_CSS = "header.css";
+       private final static String FONTS_TXT = "fonts.txt";
+       private final static String BODY_HTML = "body.html";
+
+//     private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
+
+       private CmsTheme parentTheme;
+
+       private String themeId;
+       private Set<String> webCssPaths = new TreeSet<>();
+       private Set<String> rapCssPaths = new TreeSet<>();
+       private Set<String> swtCssPaths = new TreeSet<>();
+       private Set<String> imagesPaths = new TreeSet<>();
+       private Set<String> fontsPaths = new TreeSet<>();
+
+       private String headerCss;
+       private List<String> fonts = new ArrayList<>();
+
+       private String bodyHtml = "<body></body>";
+
+       private String basePath;
+       private String styleCssPath;
+//     private String webCssPath;
+//     private String rapCssPath;
+//     private String swtCssPath;
+       private Bundle themeBundle;
+
+       private Integer defaultIconSize = 16;
+
+       public BundleCmsTheme() {
+
+       }
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               initResources(bundleContext, null);
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+
+       }
+
+       @Deprecated
+       public BundleCmsTheme(BundleContext bundleContext) {
+               this(bundleContext, null);
+       }
+
+       @Deprecated
+       public BundleCmsTheme(BundleContext bundleContext, String symbolicName) {
+               initResources(bundleContext, symbolicName);
+       }
+
+       private void initResources(BundleContext bundleContext, String symbolicName) {
+               if (symbolicName == null) {
+                       themeBundle = bundleContext.getBundle();
+//                     basePath = "/theme/";
+//                     cssPath = basePath;
+               } else {
+                       themeBundle = findThemeBundle(bundleContext, symbolicName);
+               }
+               basePath = "/";
+               styleCssPath = "/style/";
+//             webCssPath = "/css/";
+//             rapCssPath = "/rap/";
+//             swtCssPath = "/swt/";
+//             this.themeId = RWT.DEFAULT_THEME_ID;
+               this.themeId = themeBundle.getSymbolicName();
+               webCssPaths = addCss(themeBundle, "/css/");
+               rapCssPaths = addCss(themeBundle, "/rap/");
+               swtCssPaths = addCss(themeBundle, "/swt/");
+               addImages("*.png");
+               addImages("*.gif");
+               addImages("*.jpg");
+               addImages("*.jpeg");
+               addImages("*.svg");
+               addImages("*.ico");
+
+               addFonts("*.woff");
+               addFonts("*.woff2");
+
+               // fonts
+               URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT);
+               if (fontsUrl != null) {
+                       loadFontsUrl(fontsUrl);
+               }
+
+               // common CSS header (plain CSS)
+               URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS);
+               if (headerCssUrl != null) {
+                       // added to plain Web CSS
+                       webCssPaths.add(basePath + HEADER_CSS);
+                       // and it will also be used by RAP:
+                       try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
+                               headerCss = buffer.lines().collect(Collectors.joining("\n"));
+                       } catch (IOException e) {
+                               throw new IllegalArgumentException("Cannot read " + headerCssUrl, e);
+                       }
+               }
+
+               // body
+               URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML);
+               if (bodyUrl != null) {
+                       loadBodyHtml(bodyUrl);
+               }
+       }
+
+       public String getHtmlHeaders() {
+               StringBuilder sb = new StringBuilder();
+               if (headerCss != null) {
+                       sb.append("<style type='text/css'>\n");
+                       sb.append(headerCss);
+                       sb.append("\n</style>\n");
+               }
+               for (String link : fonts) {
+                       sb.append("<link rel='stylesheet' href='");
+                       sb.append(link);
+                       sb.append("'/>\n");
+               }
+               if (sb.length() == 0)
+                       return null;
+               else
+                       return sb.toString();
+       }
+
+       @Override
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
+
+       Set<String> addCss(Bundle themeBundle, String path) {
+               Set<String> paths = new TreeSet<>();
+
+               // common CSS
+               Enumeration<URL> commonResources = themeBundle.findEntries(styleCssPath, "*.css", true);
+               if (commonResources != null) {
+                       while (commonResources.hasMoreElements()) {
+                               String resource = commonResources.nextElement().getPath();
+                               // remove first '/' so that RWT registers it
+                               resource = resource.substring(1);
+                               if (!resource.endsWith("/")) {
+                                       paths.add(resource);
+                               }
+                       }
+               }
+
+               // specific CSS
+               Enumeration<URL> themeResources = themeBundle.findEntries(path, "*.css", true);
+               if (themeResources != null) {
+                       while (themeResources.hasMoreElements()) {
+                               String resource = themeResources.nextElement().getPath();
+                               // remove first '/' so that RWT registers it
+                               resource = resource.substring(1);
+                               if (!resource.endsWith("/")) {
+                                       paths.add(resource);
+                               }
+                       }
+               }
+               return paths;
+       }
+
+       void loadFontsUrl(URL url) {
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
+                       String line = null;
+                       while ((line = in.readLine()) != null) {
+                               line = line.trim();
+                               if (!line.equals("") && !line.startsWith("#")) {
+                                       fonts.add(line);
+                               }
+                       }
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot load URL " + url, e);
+               }
+       }
+
+       void loadBodyHtml(URL url) {
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
+                       bodyHtml = IOUtils.toString(url, StandardCharsets.UTF_8);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot load URL " + url, e);
+               }
+       }
+
+       void addImages(String pattern) {
+               Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
+               if (themeResources == null)
+                       return;
+               while (themeResources.hasMoreElements()) {
+                       String resource = themeResources.nextElement().getPath();
+                       // remove first '/' so that RWT registers it
+                       resource = resource.substring(1);
+                       if (!resource.endsWith("/")) {
+//                             if (resources.containsKey(resource))
+//                                     log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
+//                             resources.put(resource, themeBRL);
+                               imagesPaths.add(resource);
+                       }
+
+               }
+
+       }
+
+       void addFonts(String pattern) {
+               Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
+               if (themeResources == null)
+                       return;
+               while (themeResources.hasMoreElements()) {
+                       String resource = themeResources.nextElement().getPath();
+                       // remove first '/' so that RWT registers it
+                       resource = resource.substring(1);
+                       if (!resource.endsWith("/")) {
+//                             if (resources.containsKey(resource))
+//                                     log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
+//                             resources.put(resource, themeBRL);
+                               fontsPaths.add(resource);
+                       }
+
+               }
+
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               URL res = themeBundle.getEntry(resourceName);
+               if (res == null) {
+                       res = themeBundle.getResource(resourceName);
+                       if (res == null) {
+                               if (parentTheme == null)
+                                       return null;
+                               return parentTheme.getResourceAsStream(resourceName);
+                       }
+               }
+               return res.openStream();
+       }
+
+       public String getThemeId() {
+               return themeId;
+       }
+
+       @Override
+       public Set<String> getWebCssPaths() {
+               if (parentTheme != null) {
+                       Set<String> res = new HashSet<>(parentTheme.getWebCssPaths());
+                       res.addAll(webCssPaths);
+                       return res;
+               }
+               return webCssPaths;
+       }
+
+       @Override
+       public Set<String> getRapCssPaths() {
+               if (parentTheme != null) {
+                       Set<String> res = new HashSet<>(parentTheme.getRapCssPaths());
+                       res.addAll(rapCssPaths);
+                       return res;
+               }
+               return rapCssPaths;
+       }
+
+       @Override
+       public Set<String> getSwtCssPaths() {
+               if (parentTheme != null) {
+                       Set<String> res = new HashSet<>(parentTheme.getSwtCssPaths());
+                       res.addAll(swtCssPaths);
+                       return res;
+               }
+               return swtCssPaths;
+       }
+
+       @Override
+       public Set<String> getImagesPaths() {
+               if (parentTheme != null) {
+                       Set<String> res = new HashSet<>(parentTheme.getImagesPaths());
+                       res.addAll(imagesPaths);
+                       return res;
+               }
+               return imagesPaths;
+       }
+
+       @Override
+       public Set<String> getFontsPaths() {
+               return fontsPaths;
+       }
+
+       @Override
+       public Integer getDefaultIconSize() {
+               return defaultIconSize;
+       }
+
+       @Override
+       public InputStream loadPath(String path) throws IOException {
+               URL url = themeBundle.getResource(path);
+               if (url == null)
+                       throw new IllegalArgumentException(
+                                       "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
+               return url.openStream();
+       }
+
+       private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
+               if (themeId == null)
+                       return null;
+               // TODO optimize
+               // TODO deal with multiple versions
+               Bundle themeBundle = null;
+               if (themeId != null) {
+                       for (Bundle bundle : bundleContext.getBundles())
+                               if (themeId.equals(bundle.getSymbolicName())) {
+                                       themeBundle = bundle;
+                                       break;
+                               }
+               }
+               return themeBundle;
+       }
+
+       @Override
+       public int hashCode() {
+               return themeId.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "Bundle CMS Theme " + themeId;
+       }
+
+       public void setParentTheme(CmsTheme parentTheme) {
+               this.parentTheme = parentTheme;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java b/org.argeo.cms/src/org/argeo/cms/osgi/CmsOsgiUtils.java
new file mode 100644 (file)
index 0000000..424d62f
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.cms.osgi;
+
+import java.util.Collection;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsSessionId;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+public class CmsOsgiUtils {
+
+       /** @return The {@link CmsSession} for this {@link Subject} or null. */
+       public static CmsSession getCmsSession(BundleContext bc, Subject subject) {
+               if (subject.getPrivateCredentials(CmsSessionId.class).isEmpty())
+                       return null;
+               CmsSessionId cmsSessionId = subject.getPrivateCredentials(CmsSessionId.class).iterator().next();
+               String uuid = cmsSessionId.getUuid().toString();
+               Collection<ServiceReference<CmsSession>> sr;
+               try {
+                       sr = bc.getServiceReferences(CmsSession.class, "(" + CmsSession.SESSION_UUID + "=" + uuid + ")");
+               } catch (InvalidSyntaxException e) {
+                       throw new IllegalArgumentException("Cannot get CMS session for uuid " + uuid, e);
+               }
+               ServiceReference<CmsSession> cmsSessionRef;
+               if (sr.size() == 1) {
+                       cmsSessionRef = sr.iterator().next();
+                       return bc.getService(cmsSessionRef);
+               } else if (sr.size() == 0) {
+                       return null;
+               } else
+                       throw new IllegalStateException(sr.size() + " CMS sessions registered for " + uuid);
+       }
+
+       /** Singleton.*/
+       private CmsOsgiUtils() {
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/DataModelNamespace.java b/org.argeo.cms/src/org/argeo/cms/osgi/DataModelNamespace.java
new file mode 100644 (file)
index 0000000..8e07061
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.osgi;
+
+import org.osgi.resource.Namespace;
+
+/** CMS Data Model capability namespace. */
+public class DataModelNamespace extends Namespace {
+
+       public static final String CMS_DATA_MODEL_NAMESPACE = "cms.datamodel";
+       public static final String NAME = "name";
+       public static final String CND = "cnd";
+       /** If 'true', indicates that no repository should be published */
+       public static final String ABSTRACT = "abstract";
+
+       private DataModelNamespace() {
+               // empty
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/osgi/PublishNamespace.java b/org.argeo.cms/src/org/argeo/cms/osgi/PublishNamespace.java
new file mode 100644 (file)
index 0000000..606d51f
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.cms.osgi;
+
+import org.osgi.resource.Namespace;
+
+/** Namespace defining which resources can be published. Typically use to expose icon of scripts to the web. */
+public class PublishNamespace extends Namespace {
+
+       public static final String CMS_PUBLISH_NAMESPACE = "cms.publish";
+       public static final String PKG = "pkg";
+       public static final String FILE = "file";
+
+       private PublishNamespace() {
+               // empty
+       }
+
+}
index cc8294da775abaeed04f7f2195e0126a804091aa..08ac5493613758af5d4123f6441a83f3674b2e9e 100644 (file)
@@ -26,10 +26,7 @@ import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 
 import org.apache.commons.io.IOUtils;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.security.CryptoKeyring;
-import org.argeo.api.security.Keyring;
-import org.argeo.api.security.PBEKeySpecCallback;
+import org.argeo.api.cms.CmsAuth;
 import org.argeo.cms.CmsException;
 
 /** username / password based keyring. TODO internationalize */
@@ -77,7 +74,7 @@ public abstract class AbstractKeyring implements Keyring, CryptoKeyring {
                        ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
                        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
                        try {
-                               LoginContext loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_KEYRING, subject,
+                               LoginContext loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_KEYRING, subject,
                                                callbackHandler);
                                loginContext.login();
                                // FIXME will login even if password is wrong
diff --git a/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/CryptoKeyring.java
new file mode 100644 (file)
index 0000000..df26c6b
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.cms.security;
+
+/**
+ * Marker interface for an advanced keyring based on cryptography.
+ */
+public interface CryptoKeyring extends Keyring {
+       public void changePassword(char[] oldPassword, char[] newPassword);
+
+       public void unlock(char[] password);
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/JcrKeyring.java b/org.argeo.cms/src/org/argeo/cms/security/JcrKeyring.java
deleted file mode 100644 (file)
index c23db19..0000000
+++ /dev/null
@@ -1,397 +0,0 @@
-package org.argeo.cms.security;
-
-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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-import org.argeo.api.security.PBEKeySpecCallback;
-import org.argeo.cms.ArgeoNames;
-import org.argeo.cms.ArgeoTypes;
-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 Log log = LogFactory.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(NodeConstants.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 = NodeUtils.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 = NodeUtils.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 = NodeUtils.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 = NodeUtils.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/src/org/argeo/cms/security/Keyring.java b/org.argeo.cms/src/org/argeo/cms/security/Keyring.java
new file mode 100644 (file)
index 0000000..53740c6
--- /dev/null
@@ -0,0 +1,26 @@
+package org.argeo.cms.security;
+
+import java.io.InputStream;
+
+/**
+ * Access to private (typically encrypted) data. The keyring is responsible for
+ * retrieving the necessary credentials. <b>Experimental. This API may
+ * change.</b>
+ */
+public interface Keyring {
+       /**
+        * Returns the confidential information as chars. Must ask for it if it is
+        * not stored.
+        */
+       public char[] getAsChars(String path);
+
+       /**
+        * Returns the confidential information as a stream. Must ask for it if it
+        * is not stored.
+        */
+       public InputStream getAsStream(String path);
+
+       public void set(String path, char[] arr);
+
+       public void set(String path, InputStream in);
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java b/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java
new file mode 100644 (file)
index 0000000..fb53940
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.cms.security;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.api.cms.CmsConstants;
+
+public class NodeSecurityUtils {
+       public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME,
+                       ROLE_USER_ADMIN_NAME;
+       public final static List<LdapName> RESERVED_ROLES;
+       static {
+               try {
+                       ROLE_ADMIN_NAME = new LdapName(CmsConstants.ROLE_ADMIN);
+                       ROLE_DATA_ADMIN_NAME = new LdapName(CmsConstants.ROLE_DATA_ADMIN);
+                       ROLE_USER_NAME = new LdapName(CmsConstants.ROLE_USER);
+                       ROLE_USER_ADMIN_NAME = new LdapName(CmsConstants.ROLE_USER_ADMIN);
+                       ROLE_ANONYMOUS_NAME = new LdapName(CmsConstants.ROLE_ANONYMOUS);
+                       RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList(
+                                       new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME }));
+               } catch (InvalidNameException e) {
+                       throw new Error("Cannot initialize login module class", e);
+               }
+       }
+
+       public static void checkUserName(LdapName name) throws IllegalArgumentException {
+               if (RESERVED_ROLES.contains(name))
+                       throw new IllegalArgumentException(name + " is a reserved name");
+       }
+
+       public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException {
+//             if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName))
+//                     throw new IllegalArgumentException(roleName + " cannot be listed as role");
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java b/org.argeo.cms/src/org/argeo/cms/security/PBEKeySpecCallback.java
new file mode 100644 (file)
index 0000000..13e8d75
--- /dev/null
@@ -0,0 +1,63 @@
+package org.argeo.cms.security;
+
+import javax.crypto.spec.PBEKeySpec;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.PasswordCallback;
+
+/**
+ * All information required to set up a {@link PBEKeySpec} bar the password
+ * itself (use a {@link PasswordCallback})
+ */
+public class PBEKeySpecCallback implements Callback {
+       private String secretKeyFactory;
+       private byte[] salt;
+       private Integer iterationCount;
+       /** Can be null for some algorithms */
+       private Integer keyLength;
+       /** Can be null, will trigger secret key encryption if not */
+       private String secretKeyEncryption;
+
+       private String encryptedPasswordHashCipher;
+       private byte[] encryptedPasswordHash;
+
+       public void set(String secretKeyFactory, byte[] salt,
+                       Integer iterationCount, Integer keyLength,
+                       String secretKeyEncryption) {
+               this.secretKeyFactory = secretKeyFactory;
+               this.salt = salt;
+               this.iterationCount = iterationCount;
+               this.keyLength = keyLength;
+               this.secretKeyEncryption = secretKeyEncryption;
+//             this.encryptedPasswordHashCipher = encryptedPasswordHashCipher;
+//             this.encryptedPasswordHash = encryptedPasswordHash;
+       }
+
+       public String getSecretKeyFactory() {
+               return secretKeyFactory;
+       }
+
+       public byte[] getSalt() {
+               return salt;
+       }
+
+       public Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       public Integer getKeyLength() {
+               return keyLength;
+       }
+
+       public String getSecretKeyEncryption() {
+               return secretKeyEncryption;
+       }
+
+       public String getEncryptedPasswordHashCipher() {
+               return encryptedPasswordHashCipher;
+       }
+
+       public byte[] getEncryptedPasswordHash() {
+               return encryptedPasswordHash;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms/src/org/argeo/cms/servlet/CmsServletContext.java
deleted file mode 100644 (file)
index c88ee7f..0000000
+++ /dev/null
@@ -1,95 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.cms.auth.HttpRequestCallbackHandler;
-import org.argeo.cms.internal.http.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 Log log = LogFactory.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 = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request, 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 ?
-                               ServletAuthUtils.configureRequestSecurity(request);
-                               return null;
-                       }
-
-               });
-               return true;
-       }
-
-       @Override
-       public void finishSecurity(HttpServletRequest request, HttpServletResponse response) {
-               ServletAuthUtils.clearRequestSecurity(request);
-       }
-
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               // anonymous
-               try {
-                       LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_ANONYMOUS,
-                                       new HttpRequestCallbackHandler(request, 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) {
-               return bundle.getResource(name);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
deleted file mode 100644 (file)
index e454750..0000000
+++ /dev/null
@@ -1,38 +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.internal.http.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 (org.argeo.cms.internal.kernel.Activator.getAcceptorCredentials() != null && !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/src/org/argeo/cms/servlet/ServletAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/servlet/ServletAuthUtils.java
deleted file mode 100644 (file)
index 333fa1a..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.argeo.cms.servlet;
-
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.function.Supplier;
-
-import javax.security.auth.Subject;
-import javax.servlet.http.HttpServletRequest;
-
-import org.argeo.cms.auth.CmsSession;
-import org.argeo.cms.auth.CurrentUser;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.service.http.HttpContext;
-
-/** Authentications utilities when using servlets. */
-public class ServletAuthUtils {
-       private static BundleContext bundleContext = FrameworkUtil.getBundle(ServletAuthUtils.class).getBundleContext();
-
-       /**
-        * Execute this supplier, using the CMS class loader as context classloader.
-        * Useful to log in to JCR.
-        */
-       public final static <T> T doAs(Supplier<T> supplier, HttpServletRequest req) {
-               ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader();
-               Thread.currentThread().setContextClassLoader(ServletAuthUtils.class.getClassLoader());
-               try {
-                       return Subject.doAs(
-                                       Subject.getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName())),
-                                       new PrivilegedAction<T>() {
-
-                                               @Override
-                                               public T run() {
-                                                       return supplier.get();
-                                               }
-
-                                       });
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextCl);
-               }
-       }
-
-       public final static void configureRequestSecurity(HttpServletRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) != null)
-                       throw new IllegalStateException("Request already authenticated.");
-               AccessControlContext acc = AccessController.getContext();
-               req.setAttribute(HttpContext.REMOTE_USER, CurrentUser.getUsername());
-               req.setAttribute(AccessControlContext.class.getName(), acc);
-       }
-
-       public final static void clearRequestSecurity(HttpServletRequest req) {
-               if (req.getAttribute(AccessControlContext.class.getName()) == null)
-                       throw new IllegalStateException("Cannot clear non-authenticated request.");
-               req.setAttribute(HttpContext.REMOTE_USER, null);
-               req.setAttribute(AccessControlContext.class.getName(), null);
-       }
-
-       public static CmsSession getCmsSession(HttpServletRequest req) {
-               Subject subject = Subject
-                               .getSubject((AccessControlContext) req.getAttribute(AccessControlContext.class.getName()));
-               CmsSession cmsSession = CmsSession.getCmsSession(bundleContext, subject);
-               return cmsSession;
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/ArrayTabularRow.java
new file mode 100644 (file)
index 0000000..cfd4827
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.cms.tabular;
+
+import java.util.List;
+
+/** Minimal tabular row wrapping an {@link Object} array */
+public class ArrayTabularRow implements TabularRow {
+       private final Object[] arr;
+
+       public ArrayTabularRow(List<?> objs) {
+               this.arr = objs.toArray();
+       }
+
+       public Object get(Integer col) {
+               return arr[col];
+       }
+
+       public int size() {
+               return arr.length;
+       }
+
+       public Object[] toArray() {
+               return arr;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/CsvTabularWriter.java b/org.argeo.cms/src/org/argeo/cms/tabular/CsvTabularWriter.java
deleted file mode 100644 (file)
index 6f78672..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.argeo.cms.tabular;
-
-import java.io.OutputStream;
-
-import org.argeo.api.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/src/org/argeo/cms/tabular/JcrTabularRowIterator.java b/org.argeo.cms/src/org/argeo/cms/tabular/JcrTabularRowIterator.java
deleted file mode 100644 (file)
index 23bc8e8..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.argeo.cms.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.api.tabular.ArrayTabularRow;
-import org.argeo.api.tabular.TabularColumn;
-import org.argeo.api.tabular.TabularRow;
-import org.argeo.api.tabular.TabularRowIterator;
-import org.argeo.cms.ArgeoTypes;
-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/src/org/argeo/cms/tabular/JcrTabularWriter.java b/org.argeo.cms/src/org/argeo/cms/tabular/JcrTabularWriter.java
deleted file mode 100644 (file)
index 29933cd..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.cms.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.api.tabular.TabularColumn;
-import org.argeo.api.tabular.TabularWriter;
-import org.argeo.cms.ArgeoTypes;
-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/src/org/argeo/cms/tabular/TabularColumn.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularColumn.java
new file mode 100644 (file)
index 0000000..7f7ac1e
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.cms.tabular;
+
+/** The column in a tabular content */
+public class TabularColumn {
+       private String name;
+       /**
+        * JCR types, see
+        * http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/index.html
+        * ?javax/jcr/PropertyType.html
+        */
+       private Integer type;
+
+       /** column with default type */
+       public TabularColumn(String name) {
+               super();
+               this.name = name;
+       }
+
+       public TabularColumn(String name, Integer type) {
+               super();
+               this.name = name;
+               this.type = type;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public Integer getType() {
+               return type;
+       }
+
+       public void setType(Integer type) {
+               this.type = type;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularContent.java
new file mode 100644 (file)
index 0000000..c6d2ab8
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.cms.tabular;
+
+import java.util.List;
+
+/**
+ * Content organized as a table, possibly with headers. Only JCR types are
+ * supported even though there is not direct dependency on JCR.
+ */
+public interface TabularContent {
+       /** The headers of this table or <code>null</code> is none available. */
+       public List<TabularColumn> getColumns();
+
+       public TabularRowIterator read();
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRow.java
new file mode 100644 (file)
index 0000000..69b9732
--- /dev/null
@@ -0,0 +1,13 @@
+package org.argeo.cms.tabular;
+
+/** A row of tabular data */
+public interface TabularRow {
+       /** The value at this column index */
+       public Object get(Integer col);
+
+       /** The raw objects (direct references) */
+       public Object[] toArray();
+
+       /** Number of columns */
+       public int size();
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularRowIterator.java
new file mode 100644 (file)
index 0000000..7ad8719
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.cms.tabular;
+
+import java.util.Iterator;
+
+/** Navigation of rows */
+public interface TabularRowIterator extends Iterator<TabularRow> {
+       /**
+        * Current row number, has to be incremented by each call to next() ; starts at 0, will
+        * therefore be 1 for the first row returned.
+        */
+       public Long getCurrentRowNumber();
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java b/org.argeo.cms/src/org/argeo/cms/tabular/TabularWriter.java
new file mode 100644 (file)
index 0000000..34fc85b
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.cms.tabular;
+
+
+/** Write to a tabular content */
+public interface TabularWriter {
+       /** Append a new row of data */
+       public void appendRow(Object[] row);
+
+       /** Finish persisting data and release resources */
+       public void close();
+}
index a95d86604ea9499a575c71d2ac4675f02ddbabf3..6cb48d07f1d8d90b5c61a5ffd49ea5080102ef7f 100644 (file)
@@ -1,2 +1,2 @@
-/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */
+/** Tabular format API. */
 package org.argeo.cms.tabular;
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/org.argeo.cms/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/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.cms/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/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/org.argeo.cms/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/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.cms/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java
deleted file mode 100644 (file)
index 5767ecd..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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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.NodeConstants;
-import org.argeo.api.security.AnonymousPrincipal;
-import org.argeo.api.security.DataAdminPrincipal;
-import org.argeo.cms.auth.CmsSession;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-
-/** Customises Jackrabbit security. */
-public class ArgeoSecurityManager extends DefaultSecurityManager {
-       private final static Log log = LogFactory.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 = CmsSession.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 NodeConstants.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 NodeConstants.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/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/org.argeo.cms/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java
deleted file mode 100644 (file)
index 9c70e9b..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.security.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.security.AnonymousPrincipal> anonPrincipal = subject
-                               .getPrincipals(org.argeo.api.security.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/src/org/argeo/security/jackrabbit/package-info.java b/org.argeo.cms/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.core/.classpath b/org.argeo.core/.classpath
deleted file mode 100644 (file)
index 9a0a269..0000000
+++ /dev/null
@@ -1,8 +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="src" path="ext/test"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.core/.project b/org.argeo.core/.project
deleted file mode 100644 (file)
index cb3127e..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.core</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.core/.settings/org.eclipse.jdt.core.prefs b/org.argeo.core/.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.core/META-INF/.gitignore b/org.argeo.core/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.core/bnd.bnd b/org.argeo.core/bnd.bnd
deleted file mode 100644 (file)
index f096e4f..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-Import-Package:\
-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,\
-*
\ No newline at end of file
diff --git a/org.argeo.core/build.properties b/org.argeo.core/build.properties
deleted file mode 100644 (file)
index 49a93ba..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
-additional.bundles = org.junit,\
-                     org.hamcrest,\
-                     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.slf4j.log4j12,\
-                     org.apache.log4j,\
-                     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
-                     
\ No newline at end of file
diff --git a/org.argeo.core/ext/test/log4j.properties b/org.argeo.core/ext/test/log4j.properties
deleted file mode 100644 (file)
index 3d75289..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-log4j.rootLogger=WARN, console
-
-## Levels
-log4j.logger.org.argeo=DEBUG
-log4j.logger.org.apache.jackrabbit=OFF
-
-## Appenders
-# console is set to be a ConsoleAppender.
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-
-# console uses PatternLayout.
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-#log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n
-log4j.appender.console.layout.ConversionPattern=%m%n
diff --git a/org.argeo.core/ext/test/org/argeo/fs/FsUtilsTest.java b/org.argeo.core/ext/test/org/argeo/fs/FsUtilsTest.java
deleted file mode 100644 (file)
index 793216b..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.argeo.fs;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/** {@link FsUtils} tests. */
-public class FsUtilsTest {
-       final static String FILE00 = "file00";
-       final static String FILE01 = "file01";
-       final static String SUB_DIR = "subDir";
-
-       public void testDelete() throws IOException {
-               Path dir = createDir00();
-               assert Files.exists(dir);
-               FsUtils.delete(dir);
-               assert !Files.exists(dir);
-       }
-
-       public void testSync() throws IOException {
-               Path source = createDir00();
-               Path target = Files.createTempDirectory(getClass().getName());
-               FsUtils.sync(source, target);
-               assert Files.exists(target.resolve(FILE00));
-               assert Files.exists(target.resolve(SUB_DIR));
-               assert Files.exists(target.resolve(SUB_DIR + File.separator + FILE01));
-               FsUtils.delete(source.resolve(SUB_DIR));
-               FsUtils.sync(source, target, true);
-               assert Files.exists(target.resolve(FILE00));
-               assert !Files.exists(target.resolve(SUB_DIR));
-               assert !Files.exists(target.resolve(SUB_DIR + File.separator + FILE01));
-
-               // clean up
-               FsUtils.delete(source);
-               FsUtils.delete(target);
-
-       }
-
-       Path createDir00() throws IOException {
-               Path base = Files.createTempDirectory(getClass().getName());
-               base.toFile().deleteOnExit();
-               Files.createFile(base.resolve(FILE00)).toFile().deleteOnExit();
-               Path subDir = Files.createDirectories(base.resolve(SUB_DIR));
-               subDir.toFile().deleteOnExit();
-               Files.createFile(subDir.resolve(FILE01)).toFile().deleteOnExit();
-               return base;
-       }
-}
diff --git a/org.argeo.core/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java b/org.argeo.core/ext/test/org/argeo/jcr/fs/JcrFileSystemTest.java
deleted file mode 100644 (file)
index 2d03b8f..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.Arrays;
-import java.util.Map;
-
-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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.argeo.jackrabbit.fs.JackrabbitMemoryFsProvider;
-
-import junit.framework.TestCase;
-
-public class JcrFileSystemTest extends TestCase {
-       private final static Log log = LogFactory.getLog(JcrFileSystemTest.class);
-
-       public void testMounts() throws Exception {
-               JackrabbitMemoryFsProvider fsProvider = new JackrabbitMemoryFsProvider() {
-
-                       @Override
-                       protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException {
-                               // create workspace
-                               Session session = login();
-                               session.getWorkspace().createWorkspace("test");
-                       }
-
-               };
-
-               Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
-               log.debug("Got root " + rootPath);
-               Path testDir = rootPath.resolve("testDir");
-               Files.createDirectory(testDir);
-
-               Path testMount = fsProvider.getPath(new URI("jcr+memory:/test"));
-               log.debug("Test path");
-               assertEquals(rootPath, testMount.getParent());
-               assertEquals(testMount.getFileName(), rootPath.relativize(testMount));
-
-               Path testPath = testMount.resolve("test.txt");
-               log.debug("Create file " + testPath);
-               Files.createFile(testPath);
-               BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
-               FileTime ft = bfa.creationTime();
-               assertNotNull(ft);
-               assertTrue(bfa.isRegularFile());
-               log.debug("Created " + testPath + " (" + ft + ")");
-               Files.delete(testPath);
-               log.debug("Deleted " + testPath);
-
-               // Browse directories from root
-               DirectoryStream<Path> files = Files.newDirectoryStream(rootPath);
-               int directoryCount = 0;
-               for (Path file : files) {
-                       if (Files.isDirectory(file)) {
-                               directoryCount++;
-                       }
-               }
-               assertEquals(2, directoryCount);
-
-               // Browse directories from mount
-               Path mountSubDir = testMount.resolve("mountSubDir");
-               Files.createDirectory(mountSubDir);
-               Path otherSubDir = testMount.resolve("otherSubDir");
-               Files.createDirectory(otherSubDir);
-               testPath = testMount.resolve("test.txt");
-               Files.createFile(testPath);
-               files = Files.newDirectoryStream(testMount);
-               int fileCount = 0;
-               for (Path file : files) {
-                       fileCount++;
-               }
-               assertEquals(3, fileCount);
-
-       }
-
-       public void testSimple() throws Exception {
-               FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
-
-               // Simple file
-               Path rootPath = fsProvider.getPath(new URI("jcr+memory:/"));
-               log.debug("Got root " + rootPath);
-               Path testPath = fsProvider.getPath(new URI("jcr+memory:/test.txt"));
-               log.debug("Test path");
-               assertEquals("test.txt", testPath.getFileName().toString());
-               assertEquals(rootPath, testPath.getParent());
-               assertEquals(testPath.getFileName(), rootPath.relativize(testPath));
-               // relativize self should be empty path
-               Path selfRelative = testPath.relativize(testPath);
-               assertEquals("", selfRelative.toString());
-
-               log.debug("Create file " + testPath);
-               Files.createFile(testPath);
-               BasicFileAttributes bfa = Files.readAttributes(testPath, BasicFileAttributes.class);
-               FileTime ft = bfa.creationTime();
-               assertNotNull(ft);
-               assertTrue(bfa.isRegularFile());
-               log.debug("Created " + testPath + " (" + ft + ")");
-               Files.delete(testPath);
-               log.debug("Deleted " + testPath);
-               String txt = "TEST\nTEST2\n";
-               byte[] arr = txt.getBytes();
-               Files.write(testPath, arr);
-               log.debug("Wrote " + testPath);
-               byte[] read = Files.readAllBytes(testPath);
-               assertTrue(Arrays.equals(arr, read));
-               assertEquals(txt, new String(read));
-               log.debug("Read " + testPath);
-               Path testDir = rootPath.resolve("testDir");
-               log.debug("Resolved " + testDir);
-               // Copy
-               Files.createDirectory(testDir);
-               log.debug("Created directory " + testDir);
-               Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
-               log.debug("Created sub directories " + subsubdir);
-               Path copiedFile = testDir.resolve("copiedFile.txt");
-               log.debug("Resolved " + copiedFile);
-               Path relativeCopiedFile = testDir.relativize(copiedFile);
-               assertEquals(copiedFile.getFileName().toString(), relativeCopiedFile.toString());
-               log.debug("Relative copied file " + relativeCopiedFile);
-               try (OutputStream out = Files.newOutputStream(copiedFile); InputStream in = Files.newInputStream(testPath)) {
-                       IOUtils.copy(in, out);
-               }
-               log.debug("Copied " + testPath + " to " + copiedFile);
-               Files.delete(testPath);
-               log.debug("Deleted " + testPath);
-               byte[] copiedRead = Files.readAllBytes(copiedFile);
-               assertTrue(Arrays.equals(copiedRead, read));
-               log.debug("Read " + copiedFile);
-               // Browse directories
-               DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
-               int fileCount = 0;
-               Path listedFile = null;
-               for (Path file : files) {
-                       fileCount++;
-                       if (!Files.isDirectory(file))
-                               listedFile = file;
-               }
-               assertEquals(2, fileCount);
-               assertEquals(copiedFile, listedFile);
-               assertEquals(copiedFile.toString(), listedFile.toString());
-               log.debug("Listed " + testDir);
-               // Generic attributes
-               Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
-               assertEquals(3, attrs.size());
-               log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
-               // Direct node access
-               NodeFileAttributes nfa = Files.readAttributes(copiedFile, NodeFileAttributes.class);
-               nfa.getNode().addMixin(NodeType.MIX_LANGUAGE);
-               nfa.getNode().getSession().save();
-               log.debug("Add mix:language");
-               Files.setAttribute(copiedFile, Property.JCR_LANGUAGE, "fr");
-               log.debug("Set language");
-               attrs = Files.readAttributes(copiedFile, "*");
-               assertEquals(4, attrs.size());
-               log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
-       }
-
-       public void testIllegalCharacters() throws Exception {
-               FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider();
-               String fileName = "tüßçt[1].txt";
-               String pathStr = "/testDir/" + fileName;
-               Path testDir = fsProvider.getPath(new URI("jcr+memory:/testDir"));
-               Files.createDirectory(testDir);
-               Path testPath = testDir.resolve(fileName);
-               assertEquals(pathStr, testPath.toString());
-               Files.createFile(testPath);
-               DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
-               Path listedPath = files.iterator().next();
-               assertEquals(pathStr, listedPath.toString());
-
-               String dirName = "*[~WeirdDir~]*";
-               Path subDir = testDir.resolve(dirName);
-               Files.createDirectory(subDir);
-               subDir = testDir.resolve(dirName);
-               assertEquals(dirName, subDir.getFileName().toString());
-       }
-}
diff --git a/org.argeo.core/pom.xml b/org.argeo.core/pom.xml
deleted file mode 100644 (file)
index b1dfe92..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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.core</artifactId>
-       <name>Commons Third Parties Utilities</name>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.enterprise</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.jcr</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/cli/CommandArgsException.java b/org.argeo.core/src/org/argeo/cli/CommandArgsException.java
deleted file mode 100644 (file)
index d7a615a..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.cli;
-
-public class CommandArgsException extends IllegalArgumentException {
-       private static final long serialVersionUID = -7271050747105253935L;
-       private String commandName;
-       private volatile CommandsCli commandsCli;
-
-       public CommandArgsException(Exception cause) {
-               super(cause.getMessage(), cause);
-       }
-
-       public CommandArgsException(String message) {
-               super(message);
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public void setCommandName(String commandName) {
-               this.commandName = commandName;
-       }
-
-       public CommandsCli getCommandsCli() {
-               return commandsCli;
-       }
-
-       public void setCommandsCli(CommandsCli commandsCli) {
-               this.commandsCli = commandsCli;
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java b/org.argeo.core/src/org/argeo/cli/CommandRuntimeException.java
deleted file mode 100644 (file)
index 68b9a18..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.cli;
-
-import java.util.List;
-
-/** {@link RuntimeException} referring during a command run. */
-public class CommandRuntimeException extends RuntimeException {
-       private static final long serialVersionUID = 5595999301269377128L;
-
-       private final DescribedCommand<?> command;
-       private final List<String> arguments;
-
-       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               this(null, e, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
-               this(message, null, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
-                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
-               this.command = command;
-               this.arguments = arguments;
-       }
-
-       public DescribedCommand<?> getCommand() {
-               return command;
-       }
-
-       public List<String> getArguments() {
-               return arguments;
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/CommandsCli.java b/org.argeo.core/src/org/argeo/cli/CommandsCli.java
deleted file mode 100644 (file)
index b0879f0..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.argeo.cli;
-
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** Base class for a CLI managing sub commands. */
-public abstract class CommandsCli implements DescribedCommand<Object> {
-       public final static String HELP = "help";
-
-       private final String commandName;
-       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
-
-       protected final Options options = new Options();
-
-       public CommandsCli(String commandName) {
-               this.commandName = commandName;
-       }
-
-       @Override
-       public Object apply(List<String> args) {
-               String cmd = null;
-               List<String> newArgs = new ArrayList<>();
-               try {
-                       CommandLineParser clParser = new DefaultParser();
-                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
-                       List<String> leftOvers = commonCl.getArgList();
-                       for (String arg : leftOvers) {
-                               if (!arg.startsWith("-") && cmd == null) {
-                                       cmd = arg;
-                               } else {
-                                       newArgs.add(arg);
-                               }
-                       }
-               } catch (ParseException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       throw cae;
-               }
-
-               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
-               if (function == null)
-                       throw new IllegalArgumentException("Uknown command " + cmd);
-               try {
-                       return function.apply(newArgs).toString();
-               } catch (CommandArgsException e) {
-                       if (e.getCommandName() == null) {
-                               e.setCommandName(cmd);
-                               e.setCommandsCli(this);
-                       }
-                       throw e;
-               } catch (IllegalArgumentException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       cae.setCommandName(cmd);
-                       throw cae;
-               }
-       }
-
-       @Override
-       public Options getOptions() {
-               return options;
-       }
-
-       protected void addCommand(String cmd, Function<List<String>, ?> function) {
-               commands.put(cmd, function);
-
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       protected void addCommandsCli(CommandsCli commandsCli) {
-               addCommand(commandsCli.getCommandName(), commandsCli);
-               commandsCli.addCommand(HELP, new HelpCommand(this, commandsCli));
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public Set<String> getSubCommands() {
-               return commands.keySet();
-       }
-
-       public Function<List<String>, ?> getCommand(String command) {
-               return commands.get(command);
-       }
-
-       public HelpCommand getHelpCommand() {
-               return (HelpCommand) getCommand(HELP);
-       }
-
-       public Function<List<String>, String> getDefaultCommand() {
-               return getHelpCommand();
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(CommandsCli cli, String[] args) {
-               try {
-                       cli.addCommand(CommandsCli.HELP, new HelpCommand(null, cli));
-                       Object output = cli.apply(Arrays.asList(args));
-                       System.out.println(output);
-                       System.exit(0);
-               } catch (CommandArgsException e) {
-                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
-                       if (e.getCommandName() != null) {
-                               StringWriter out = new StringWriter();
-                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
-                               System.err.println(out.toString());
-                       } else {
-                               e.printStackTrace();
-                       }
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/cli/DescribedCommand.java b/org.argeo.core/src/org/argeo/cli/DescribedCommand.java
deleted file mode 100644 (file)
index 9587206..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.argeo.cli;
-
-import java.io.StringWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** A command that can be described. */
-public interface DescribedCommand<T> extends Function<List<String>, T> {
-       default Options getOptions() {
-               return new Options();
-       }
-
-       String getDescription();
-
-       default String getUsage() {
-               return null;
-       }
-
-       default String getExamples() {
-               return null;
-       }
-
-       default CommandLine toCommandLine(List<String> args) {
-               try {
-                       DefaultParser parser = new DefaultParser();
-                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
-               } catch (ParseException e) {
-                       throw new CommandArgsException(e);
-               }
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(DescribedCommand<?> command, String[] args) {
-               try {
-                       Object output = command.apply(Arrays.asList(args));
-                       System.out.println(output);
-                       System.exit(0);
-               } catch (IllegalArgumentException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(command, out);
-                       System.err.println(out.toString());
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/HelpCommand.java b/org.argeo.core/src/org/argeo/cli/HelpCommand.java
deleted file mode 100644 (file)
index 755ce59..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.argeo.cli;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Options;
-
-/** A special command that can describe {@link DescribedCommand}. */
-public class HelpCommand implements DescribedCommand<String> {
-       private CommandsCli commandsCli;
-       private CommandsCli parentCommandsCli;
-
-       // Help formatting
-       private static int helpWidth = 80;
-       private static int helpLeftPad = 4;
-       private static int helpDescPad = 20;
-
-       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
-               super();
-               this.parentCommandsCli = parentCommandsCli;
-               this.commandsCli = commandsCli;
-       }
-
-       @Override
-       public String apply(List<String> args) {
-               StringWriter out = new StringWriter();
-
-               if (args.size() == 0) {// overview
-                       printHelp(commandsCli, out);
-               } else {
-                       String cmd = args.get(0);
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       if (function == null)
-                               return "Command " + cmd + " not found.";
-                       Options options;
-                       String examples;
-                       DescribedCommand<?> command = null;
-                       if (function instanceof DescribedCommand) {
-                               command = (DescribedCommand<?>) function;
-                               options = command.getOptions();
-                               examples = command.getExamples();
-                       } else {
-                               options = new Options();
-                               examples = null;
-                       }
-                       String description = getShortDescription(function);
-                       String commandCall = getCommandUsage(cmd, command);
-                       HelpFormatter formatter = new HelpFormatter();
-                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
-                                       helpDescPad, examples, false);
-               }
-               return out.toString();
-       }
-
-       private static String getShortDescription(Function<List<String>, ?> function) {
-               if (function instanceof DescribedCommand) {
-                       return ((DescribedCommand<?>) function).getDescription();
-               } else {
-                       return function.toString();
-               }
-       }
-
-       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
-               String commandCall = getCommandCall(commandsCli) + " " + cmd;
-               assert command != null;
-               if (command != null && command.getUsage() != null) {
-                       commandCall = commandCall + " " + command.getUsage();
-               }
-               return commandCall;
-       }
-
-       @Override
-       public String getDescription() {
-               return "Shows this help or describes a command";
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       public CommandsCli getParentCommandsCli() {
-               return parentCommandsCli;
-       }
-
-       protected String getCommandCall(CommandsCli commandsCli) {
-               HelpCommand hc = commandsCli.getHelpCommand();
-               if (hc.getParentCommandsCli() != null) {
-                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
-               } else {
-                       return commandsCli.getCommandName();
-               }
-       }
-
-       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
-               String usage = "java " + command.getClass().getName()
-                               + (command.getUsage() != null ? " " + command.getUsage() : "");
-               HelpFormatter formatter = new HelpFormatter();
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
-                               helpLeftPad, helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
-               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
-               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
-               HelpFormatter formatter = new HelpFormatter();
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
-                               helpLeftPad, helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
-               out.append(commandsCli.getDescription()).append('\n');
-               String leftPad = spaces(helpLeftPad);
-               for (String cmd : commandsCli.getSubCommands()) {
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       assert function != null;
-                       out.append(leftPad);
-                       out.append(cmd);
-                       // TODO deal with long commands
-                       out.append(spaces(helpDescPad - cmd.length()));
-                       out.append(getShortDescription(function));
-                       out.append('\n');
-               }
-       }
-
-       private static String spaces(int count) {
-               // Java 11
-               // return " ".repeat(count);
-               if (count <= 0)
-                       return "";
-               else {
-                       StringBuilder sb = new StringBuilder(count);
-                       for (int i = 0; i < count; i++)
-                               sb.append(' ');
-                       return sb.toString();
-               }
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/cli/fs/FileSync.java b/org.argeo.core/src/org/argeo/cli/fs/FileSync.java
deleted file mode 100644 (file)
index ba529ea..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.argeo.cli.fs;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.argeo.cli.CommandArgsException;
-import org.argeo.cli.DescribedCommand;
-import org.argeo.sync.SyncResult;
-
-public class FileSync implements DescribedCommand<SyncResult<Path>> {
-       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
-       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
-                       .build();
-       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
-                       .build();
-
-       @Override
-       public SyncResult<Path> apply(List<String> t) {
-               try {
-                       CommandLine line = toCommandLine(t);
-                       List<String> remaining = line.getArgList();
-                       if (remaining.size() == 0) {
-                               throw new CommandArgsException("There must be at least one argument");
-                       }
-                       URI sourceUri = new URI(remaining.get(0));
-                       URI targetUri;
-                       if (remaining.size() == 1) {
-                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
-                       } else {
-                               targetUri = new URI(remaining.get(1));
-                       }
-                       boolean delete = line.hasOption(deleteOption.getLongOpt());
-                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
-                       PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
-                       return pathSync.call();
-               } catch (URISyntaxException e) {
-                       throw new CommandArgsException(e);
-               }
-       }
-
-       @Override
-       public Options getOptions() {
-               Options options = new Options();
-               options.addOption(recursiveOption);
-               options.addOption(deleteOption);
-               options.addOption(progressOption);
-               return options;
-       }
-
-       @Override
-       public String getUsage() {
-               return "[source URI] [target URI]";
-       }
-
-       public static void main(String[] args) {
-               DescribedCommand.mainImpl(new FileSync(), args);
-//             Options options = new Options();
-//             options.addOption("r", "recursive", false, "recurse into directories");
-//             options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
-//
-//             CommandLineParser parser = new DefaultParser();
-//             try {
-//                     CommandLine line = parser.parse(options, args);
-//                     List<String> remaining = line.getArgList();
-//                     if (remaining.size() == 0) {
-//                             System.err.println("There must be at least one argument");
-//                             printHelp(options);
-//                             System.exit(1);
-//                     }
-//                     URI sourceUri = new URI(remaining.get(0));
-//                     URI targetUri;
-//                     if (remaining.size() == 1) {
-//                             targetUri = Paths.get(System.getProperty("user.dir")).toUri();
-//                     } else {
-//                             targetUri = new URI(remaining.get(1));
-//                     }
-//                     PathSync pathSync = new PathSync(sourceUri, targetUri);
-//                     pathSync.run();
-//             } catch (Exception exp) {
-//                     exp.printStackTrace();
-//                     printHelp(options);
-//                     System.exit(1);
-//             }
-       }
-
-//     public static void printHelp(Options options) {
-//             HelpFormatter formatter = new HelpFormatter();
-//             formatter.printHelp("sync SRC [DEST]", options, true);
-//     }
-
-       @Override
-       public String getDescription() {
-               return "Synchronises files";
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/fs/FsCommands.java b/org.argeo.core/src/org/argeo/cli/fs/FsCommands.java
deleted file mode 100644 (file)
index c08ad00..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cli.fs;
-
-import org.argeo.cli.CommandsCli;
-
-/** File utilities. */
-public class FsCommands extends CommandsCli {
-
-       public FsCommands(String commandName) {
-               super(commandName);
-               addCommand("sync", new FileSync());
-       }
-
-       @Override
-       public String getDescription() {
-               return "Utilities around files and file systems";
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/fs/PathSync.java b/org.argeo.core/src/org/argeo/cli/fs/PathSync.java
deleted file mode 100644 (file)
index 902318c..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.argeo.cli.fs;
-
-import java.net.URI;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.concurrent.Callable;
-
-import org.argeo.jackrabbit.fs.DavexFsProvider;
-import org.argeo.sync.SyncResult;
-
-/** Synchronises two paths. */
-public class PathSync implements Callable<SyncResult<Path>> {
-       private final URI sourceUri, targetUri;
-       private final boolean delete;
-       private final boolean recursive;
-
-       public PathSync(URI sourceUri, URI targetUri) {
-               this(sourceUri, targetUri, false, false);
-       }
-
-       public PathSync(URI sourceUri, URI targetUri, boolean delete, boolean recursive) {
-               this.sourceUri = sourceUri;
-               this.targetUri = targetUri;
-               this.delete = delete;
-               this.recursive = recursive;
-       }
-
-       @Override
-       public SyncResult<Path> call() {
-               try {
-                       Path sourceBasePath = createPath(sourceUri);
-                       Path targetBasePath = createPath(targetUri);
-                       SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete, recursive);
-                       Files.walkFileTree(sourceBasePath, syncFileVisitor);
-                       return syncFileVisitor.getSyncResult();
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot sync " + sourceUri + " to " + targetUri, e);
-               }
-       }
-
-       private Path createPath(URI uri) {
-               Path path;
-               if (uri.getScheme() == null) {
-                       path = Paths.get(uri.getPath());
-               } else if (uri.getScheme().equals("file")) {
-                       FileSystemProvider fsProvider = FileSystems.getDefault().provider();
-                       path = fsProvider.getPath(uri);
-               } else if (uri.getScheme().equals("davex")) {
-                       FileSystemProvider fsProvider = new DavexFsProvider();
-                       path = fsProvider.getPath(uri);
-//             } else if (uri.getScheme().equals("sftp")) {
-//                     Sftp sftp = new Sftp(uri);
-//                     path = sftp.getBasePath();
-               } else
-                       throw new IllegalArgumentException("URI scheme not supported for " + uri);
-               return path;
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/cli/fs/SyncFileVisitor.java b/org.argeo.core/src/org/argeo/cli/fs/SyncFileVisitor.java
deleted file mode 100644 (file)
index 892df50..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.cli.fs;
-
-import java.nio.file.Path;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.fs.BasicSyncFileVisitor;
-
-/** Synchronises two directory structures. */
-public class SyncFileVisitor extends BasicSyncFileVisitor {
-       private final static Log log = LogFactory.getLog(SyncFileVisitor.class);
-
-       public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
-               super(sourceBasePath, targetBasePath, delete, recursive);
-       }
-
-       @Override
-       protected void error(Object obj, Throwable e) {
-               log.error(obj, e);
-       }
-
-       @Override
-       protected boolean isTraceEnabled() {
-               return log.isTraceEnabled();
-       }
-
-       @Override
-       protected void trace(Object obj) {
-               log.trace(obj);
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/cli/fs/package-info.java b/org.argeo.core/src/org/argeo/cli/fs/package-info.java
deleted file mode 100644 (file)
index 8ad42b2..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** File system CLI commands. */
-package org.argeo.cli.fs;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java b/org.argeo.core/src/org/argeo/cli/jcr/JcrCommands.java
deleted file mode 100644 (file)
index ea74674..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.cli.jcr;
-
-import org.argeo.cli.CommandsCli;
-
-/** File utilities. */
-public class JcrCommands extends CommandsCli {
-
-       public JcrCommands(String commandName) {
-               super(commandName);
-               addCommand("sync", new JcrSync());
-       }
-
-       @Override
-       public String getDescription() {
-               return "Utilities around remote and local JCR repositories";
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java b/org.argeo.core/src/org/argeo/cli/jcr/JcrSync.java
deleted file mode 100644 (file)
index 401f447..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.cli.jcr;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Credentials;
-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.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.cli.CommandArgsException;
-import org.argeo.cli.CommandRuntimeException;
-import org.argeo.cli.DescribedCommand;
-import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.sync.SyncResult;
-
-public class JcrSync implements DescribedCommand<SyncResult<Node>> {
-       public final static String DEFAULT_LOCALFS_CONFIG = "repository-localfs.xml";
-
-       final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
-       final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
-                       .build();
-       final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
-                       .build();
-
-       @Override
-       public SyncResult<Node> apply(List<String> t) {
-               try {
-                       CommandLine line = toCommandLine(t);
-                       List<String> remaining = line.getArgList();
-                       if (remaining.size() == 0) {
-                               throw new CommandArgsException("There must be at least one argument");
-                       }
-                       URI sourceUri = new URI(remaining.get(0));
-                       URI targetUri;
-                       if (remaining.size() == 1) {
-                               targetUri = Paths.get(System.getProperty("user.dir")).toUri();
-                       } else {
-                               targetUri = new URI(remaining.get(1));
-                       }
-                       boolean delete = line.hasOption(deleteOption.getLongOpt());
-                       boolean recursive = line.hasOption(recursiveOption.getLongOpt());
-
-                       // TODO make it configurable
-                       String sourceWorkspace = "home";
-                       String targetWorkspace = sourceWorkspace;
-
-                       final Repository sourceRepository;
-                       final Session sourceSession;
-                       Credentials sourceCredentials = null;
-                       final Repository targetRepository;
-                       final Session targetSession;
-                       Credentials targetCredentials = null;
-
-                       if ("http".equals(sourceUri.getScheme()) || "https".equals(sourceUri.getScheme())) {
-                               sourceRepository = createRemoteRepository(sourceUri);
-                       } else if (null == sourceUri.getScheme() || "file".equals(sourceUri.getScheme())) {
-                               RepositoryConfig repositoryConfig = RepositoryConfig.create(
-                                               JcrSync.class.getResourceAsStream(DEFAULT_LOCALFS_CONFIG), sourceUri.getPath().toString());
-                               sourceRepository = RepositoryImpl.create(repositoryConfig);
-                               sourceCredentials = new SimpleCredentials("admin", "admin".toCharArray());
-                       } else {
-                               throw new IllegalArgumentException("Unsupported scheme " + sourceUri.getScheme());
-                       }
-                       sourceSession = JcrUtils.loginOrCreateWorkspace(sourceRepository, sourceWorkspace, sourceCredentials);
-
-                       if ("http".equals(targetUri.getScheme()) || "https".equals(targetUri.getScheme())) {
-                               targetRepository = createRemoteRepository(targetUri);
-                       } else if (null == targetUri.getScheme() || "file".equals(targetUri.getScheme())) {
-                               RepositoryConfig repositoryConfig = RepositoryConfig.create(
-                                               JcrSync.class.getResourceAsStream(DEFAULT_LOCALFS_CONFIG), targetUri.getPath().toString());
-                               targetRepository = RepositoryImpl.create(repositoryConfig);
-                               targetCredentials = new SimpleCredentials("admin", "admin".toCharArray());
-                       } else {
-                               throw new IllegalArgumentException("Unsupported scheme " + targetUri.getScheme());
-                       }
-                       targetSession = JcrUtils.loginOrCreateWorkspace(targetRepository, targetWorkspace, targetCredentials);
-
-                       JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode());
-                       return new SyncResult<Node>();
-               } catch (URISyntaxException e) {
-                       throw new CommandArgsException(e);
-               } catch (Exception e) {
-                       throw new CommandRuntimeException(e, this, t);
-               }
-       }
-
-       protected 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());
-               // FIXME make it configurable
-               params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys");
-               return repositoryFactory.getRepository(params);
-       }
-
-       @Override
-       public Options getOptions() {
-               Options options = new Options();
-               options.addOption(recursiveOption);
-               options.addOption(deleteOption);
-               options.addOption(progressOption);
-               return options;
-       }
-
-       @Override
-       public String getUsage() {
-               return "[source URI] [target URI]";
-       }
-
-       public static void main(String[] args) {
-               DescribedCommand.mainImpl(new JcrSync(), args);
-       }
-
-       @Override
-       public String getDescription() {
-               return "Synchronises JCR repositories";
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/package-info.java b/org.argeo.core/src/org/argeo/cli/jcr/package-info.java
deleted file mode 100644 (file)
index 6f3f01f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** JCR CLI commands. */
-package org.argeo.cli.jcr;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml b/org.argeo.core/src/org/argeo/cli/jcr/repository-localfs.xml
deleted file mode 100644 (file)
index 5e7759c..0000000
+++ /dev/null
@@ -1,76 +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.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="main" configRootPath="/workspaces" />
-       <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="blobFSBlockSize" value="1" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${rep.home}/repository/index" />
-               </SearchIndex>
-       </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="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="tikaConfigPath" value="tika-config.xml"/>
-       </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.core/src/org/argeo/cli/package-info.java b/org.argeo.core/src/org/argeo/cli/package-info.java
deleted file mode 100644 (file)
index 2389593..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Command line API. */
-package org.argeo.cli;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/cli/posix/Echo.java b/org.argeo.core/src/org/argeo/cli/posix/Echo.java
deleted file mode 100644 (file)
index 5746ebd..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.cli.posix;
-
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.argeo.cli.DescribedCommand;
-
-public class Echo implements DescribedCommand<String> {
-
-       @Override
-       public Options getOptions() {
-               Options options = new Options();
-               options.addOption(Option.builder("n").desc("do not output the trailing newline").build());
-               return options;
-       }
-
-       @Override
-       public String getDescription() {
-               return "Display a line of text";
-       }
-
-       @Override
-       public String getUsage() {
-               return "[STRING]...";
-       }
-
-       @Override
-       public String apply(List<String> args) {
-               CommandLine cl = toCommandLine(args);
-
-               StringBuffer sb = new StringBuffer();
-               for (String s : cl.getArgList()) {
-                       sb.append(s).append(' ');
-               }
-
-               if (cl.hasOption('n')) {
-                       sb.deleteCharAt(sb.length() - 1);
-               } else {
-                       sb.setCharAt(sb.length() - 1, '\n');
-               }
-               return sb.toString();
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/cli/posix/PosixCommands.java b/org.argeo.core/src/org/argeo/cli/posix/PosixCommands.java
deleted file mode 100644 (file)
index bb6af67..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.cli.posix;
-
-import org.argeo.cli.CommandsCli;
-
-/** POSIX commands. */
-public class PosixCommands extends CommandsCli {
-
-       public PosixCommands(String commandName) {
-               super(commandName);
-               addCommand("echo", new Echo());
-       }
-
-       @Override
-       public String getDescription() {
-               return "Reimplementation of some POSIX commands in plain Java";
-       }
-
-       public static void main(String[] args) {
-               mainImpl(new PosixCommands("argeo-posix"), args);
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/cli/posix/package-info.java b/org.argeo.core/src/org/argeo/cli/posix/package-info.java
deleted file mode 100644 (file)
index b0d1a46..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Posix CLI commands. */
-package org.argeo.cli.posix;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/fs/BasicSyncFileVisitor.java b/org.argeo.core/src/org/argeo/fs/BasicSyncFileVisitor.java
deleted file mode 100644 (file)
index 03bac59..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.fs;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-
-import org.argeo.sync.SyncResult;
-
-/** Synchronises two directory structures. */
-public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
-       // TODO make it configurable
-       private boolean trace = false;
-
-       private final Path sourceBasePath;
-       private final Path targetBasePath;
-       private final boolean delete;
-       private final boolean recursive;
-
-       private SyncResult<Path> syncResult = new SyncResult<>();
-
-       public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
-               this.sourceBasePath = sourceBasePath;
-               this.targetBasePath = targetBasePath;
-               this.delete = delete;
-               this.recursive = recursive;
-       }
-
-       @Override
-       public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
-               if (!recursive && !sourceDir.equals(sourceBasePath))
-                       return FileVisitResult.SKIP_SUBTREE;
-               Path targetDir = toTargetPath(sourceDir);
-               Files.createDirectories(targetDir);
-               return FileVisitResult.CONTINUE;
-       }
-
-       @Override
-       public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
-               if (delete) {
-                       Path targetDir = toTargetPath(sourceDir);
-                       for (Path targetPath : Files.newDirectoryStream(targetDir)) {
-                               Path sourcePath = sourceDir.resolve(targetPath.getFileName());
-                               if (!Files.exists(sourcePath)) {
-                                       try {
-                                               FsUtils.delete(targetPath);
-                                               deleted(targetPath);
-                                       } catch (Exception e) {
-                                               deleteFailed(targetPath, exc);
-                                       }
-                               }
-                       }
-               }
-               return FileVisitResult.CONTINUE;
-       }
-
-       @Override
-       public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
-               Path targetFile = toTargetPath(sourceFile);
-               try {
-                       if (!Files.exists(targetFile)) {
-                               Files.copy(sourceFile, targetFile);
-                               added(sourceFile, targetFile);
-                       } else {
-                               if (shouldOverwrite(sourceFile, targetFile)) {
-                                       Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
-                               }
-                       }
-               } catch (Exception e) {
-                       copyFailed(sourceFile, targetFile, e);
-               }
-               return FileVisitResult.CONTINUE;
-       }
-
-       protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
-               long sourceSize = Files.size(sourceFile);
-               long targetSize = Files.size(targetFile);
-               if (sourceSize != targetSize) {
-                       return true;
-               }
-               FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
-               FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
-               if (sourceLastModif.compareTo(targetLastModif) > 0)
-                       return true;
-               return shouldOverwriteLaterSameSize(sourceFile, targetFile);
-       }
-
-       protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
-               return false;
-       }
-
-//     @Override
-//     public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
-//             error("Cannot sync " + sourceFile, exc);
-//             return FileVisitResult.CONTINUE;
-//     }
-
-       private Path toTargetPath(Path sourcePath) {
-               Path relativePath = sourceBasePath.relativize(sourcePath);
-               Path targetPath = targetBasePath.resolve(relativePath.toString());
-               return targetPath;
-       }
-
-       public Path getSourceBasePath() {
-               return sourceBasePath;
-       }
-
-       public Path getTargetBasePath() {
-               return targetBasePath;
-       }
-
-       protected void added(Path sourcePath, Path targetPath) {
-               syncResult.getAdded().add(targetPath);
-               if (isTraceEnabled())
-                       trace("Added " + sourcePath + " as " + targetPath);
-       }
-
-       protected void modified(Path sourcePath, Path targetPath) {
-               syncResult.getModified().add(targetPath);
-               if (isTraceEnabled())
-                       trace("Overwritten from " + sourcePath + " to " + targetPath);
-       }
-
-       protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
-               syncResult.addError(sourcePath, targetPath, e);
-               if (isTraceEnabled())
-                       error("Cannot copy " + sourcePath + " to " + targetPath, e);
-       }
-
-       protected void deleted(Path targetPath) {
-               syncResult.getDeleted().add(targetPath);
-               if (isTraceEnabled())
-                       trace("Deleted " + targetPath);
-       }
-
-       protected void deleteFailed(Path targetPath, Exception e) {
-               syncResult.addError(null, targetPath, e);
-               if (isTraceEnabled())
-                       error("Cannot delete " + targetPath, e);
-       }
-
-       /** Log error. */
-       protected void error(Object obj, Throwable e) {
-               System.err.println(obj);
-               e.printStackTrace();
-       }
-
-       protected boolean isTraceEnabled() {
-               return trace;
-       }
-
-       protected void trace(Object obj) {
-               System.out.println(obj);
-       }
-
-       public SyncResult<Path> getSyncResult() {
-               return syncResult;
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/fs/FsUtils.java b/org.argeo.core/src/org/argeo/fs/FsUtils.java
deleted file mode 100644 (file)
index c96f56e..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.fs;
-
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-
-/** Utilities around the standard Java file abstractions. */
-public class FsUtils {
-       /** Sync a source path with a target path. */
-       public static void sync(Path sourceBasePath, Path targetBasePath) {
-               sync(sourceBasePath, targetBasePath, false);
-       }
-
-       /** Sync a source path with a target path. */
-       public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
-               sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
-       }
-
-       public static void sync(BasicSyncFileVisitor syncFileVisitor) {
-               try {
-                       Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
-                                       + syncFileVisitor.getTargetBasePath(), e);
-               }
-       }
-
-       /** Deletes this path, recursively if needed. */
-       public static void delete(Path path) {
-               try {
-                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
-                               @Override
-                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
-                                       if (e != null)
-                                               throw e;
-                                       Files.delete(directory);
-                                       return FileVisitResult.CONTINUE;
-                               }
-
-                               @Override
-                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       Files.delete(file);
-                                       return FileVisitResult.CONTINUE;
-                               }
-                       });
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot delete " + path, e);
-               }
-       }
-
-       /** Singleton. */
-       private FsUtils() {
-       }
-
-}
diff --git a/org.argeo.core/src/org/argeo/fs/package-info.java b/org.argeo.core/src/org/argeo/fs/package-info.java
deleted file mode 100644 (file)
index ea2de9e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic file system utilities. */
-package org.argeo.fs;
\ No newline at end of file
diff --git a/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java
deleted file mode 100644 (file)
index 9a49a06..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-package org.argeo.jackrabbit;
-
-import java.awt.geom.CubicCurve2D;
-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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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.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 Log log = LogFactory.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.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/fs/fs-memory.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/fs/package-info.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/package-info.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/repository-h2.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/repository-localfs.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/repository-memory.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/repository-postgresql.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/org.argeo.core/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java
deleted file mode 100644 (file)
index a75c795..0000000
+++ /dev/null
@@ -1,80 +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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.argeo.jcr.JcrUtils;
-
-/** Utilities around Jackrabbit security extensions. */
-public class JackrabbitSecurityUtils {
-       private final static Log log = LogFactory.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.core/src/org/argeo/jackrabbit/security/package-info.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/unit/jaas.config b/org.argeo.core/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.core/src/org/argeo/jackrabbit/unit/package-info.java b/org.argeo.core/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.core/src/org/argeo/jackrabbit/unit/repository-h2.xml b/org.argeo.core/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.core/src/org/argeo/jackrabbit/unit/repository-memory.xml b/org.argeo.core/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.core/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.core/src/org/argeo/jcr/proxy/AbstractUrlProxy.java
deleted file mode 100644 (file)
index 0984276..0000000
+++ /dev/null
@@ -1,154 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.core/src/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.core/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.core/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/org.argeo.core/src/org/argeo/jcr/proxy/ResourceProxyServlet.java
deleted file mode 100644 (file)
index d77bd49..0000000
+++ /dev/null
@@ -1,116 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.jcr.JcrException;
-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 Log log = LogFactory
-                       .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.core/src/org/argeo/jcr/proxy/package-info.java b/org.argeo.core/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.core/src/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.core/src/org/argeo/jcr/unit/AbstractJcrTestCase.java
deleted file mode 100644 (file)
index dc2963a..0000000
+++ /dev/null
@@ -1,116 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.core/src/org/argeo/jcr/unit/package-info.java b/org.argeo.core/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.core/src/org/argeo/sync/SyncException.java b/org.argeo.core/src/org/argeo/sync/SyncException.java
deleted file mode 100644 (file)
index 89bf869..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.sync;
-
-/** Commons exception for sync */
-public class SyncException extends RuntimeException {
-       private static final long serialVersionUID = -3371314343580218538L;
-
-       public SyncException(String message) {
-               super(message);
-       }
-
-       public SyncException(String message, Throwable cause) {
-               super(message, cause);
-       }
-
-       public SyncException(Object source, Object target, Throwable cause) {
-               super("Cannot sync from " + source + " to " + target, cause);
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/sync/SyncResult.java b/org.argeo.core/src/org/argeo/sync/SyncResult.java
deleted file mode 100644 (file)
index 6d12ada..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.sync;
-
-import java.time.Instant;
-import java.util.Set;
-import java.util.TreeSet;
-
-/** Describes what happendend during a sync operation. */
-public class SyncResult<T> {
-       private final Set<T> added = new TreeSet<>();
-       private final Set<T> modified = new TreeSet<>();
-       private final Set<T> deleted = new TreeSet<>();
-       private final Set<Error> errors = new TreeSet<>();
-
-       public Set<T> getAdded() {
-               return added;
-       }
-
-       public Set<T> getModified() {
-               return modified;
-       }
-
-       public Set<T> getDeleted() {
-               return deleted;
-       }
-
-       public Set<Error> getErrors() {
-               return errors;
-       }
-
-       public void addError(T sourcePath, T targetPath, Exception e) {
-               Error error = new Error(sourcePath, targetPath, e);
-               errors.add(error);
-       }
-
-       public boolean noModification() {
-               return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
-       }
-
-       @Override
-       public String toString() {
-               if (noModification())
-                       return "No modification.";
-               StringBuffer sb = new StringBuffer();
-               for (T p : modified)
-                       sb.append("MOD ").append(p).append('\n');
-               for (T p : deleted)
-                       sb.append("DEL ").append(p).append('\n');
-               for (T p : added)
-                       sb.append("ADD ").append(p).append('\n');
-               for (Error error : errors)
-                       sb.append(error).append('\n');
-               return sb.toString();
-       }
-
-       public class Error implements Comparable<Error> {
-               private final T sourcePath;// if null this is a failed delete
-               private final T targetPath;
-               private final Exception exception;
-               private final Instant timestamp = Instant.now();
-
-               public Error(T sourcePath, T targetPath, Exception e) {
-                       super();
-                       this.sourcePath = sourcePath;
-                       this.targetPath = targetPath;
-                       this.exception = e;
-               }
-
-               public T getSourcePath() {
-                       return sourcePath;
-               }
-
-               public T getTargetPath() {
-                       return targetPath;
-               }
-
-               public Exception getException() {
-                       return exception;
-               }
-
-               public Instant getTimestamp() {
-                       return timestamp;
-               }
-
-               @Override
-               public int compareTo(Error o) {
-                       return timestamp.compareTo(o.timestamp);
-               }
-
-               @Override
-               public int hashCode() {
-                       return timestamp.hashCode();
-               }
-
-               @Override
-               public String toString() {
-                       return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
-                                       + targetPath + " " + exception.getMessage();
-               }
-
-       }
-}
diff --git a/org.argeo.core/src/org/argeo/sync/package-info.java b/org.argeo.core/src/org/argeo/sync/package-info.java
deleted file mode 100644 (file)
index c5e9da0..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Synchrnoisation related utilities. */
-package org.argeo.sync;
\ No newline at end of file
diff --git a/org.argeo.eclipse.ui.rap/.classpath b/org.argeo.eclipse.ui.rap/.classpath
deleted file mode 100644 (file)
index e03d341..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="con"
-               path="org.eclipse.pde.core.requiredPlugins" />
-       <classpathentry kind="con"
-               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11" />
-       <classpathentry kind="output" path="bin" />
-</classpath>
diff --git a/org.argeo.eclipse.ui.rap/.project b/org.argeo.eclipse.ui.rap/.project
deleted file mode 100644 (file)
index df496c2..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.eclipse.ui.rap</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.eclipse.ui.rap/META-INF/.gitignore b/org.argeo.eclipse.ui.rap/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.eclipse.ui.rap/bnd.bnd b/org.argeo.eclipse.ui.rap/bnd.bnd
deleted file mode 100644 (file)
index f73a8d0..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-Import-Package: org.eclipse.swt,\
-org.eclipse.jface.dialogs,\
-org.argeo.eclipse.ui.util,\
-org.eclipse.swt.events,\
-org.eclipse.jetty.util.component;resolution:=optional,\
-org.eclipse.jetty.http;resolution:=optional,\
-org.eclipse.jetty.io;resolution:=optional,\
-org.eclipse.jetty.security;resolution:=optional,\
-org.eclipse.jetty.server.handler;resolution:=optional,\
-org.eclipse.jetty.*;resolution:=optional,\
-*
diff --git a/org.argeo.eclipse.ui.rap/build.properties b/org.argeo.eclipse.ui.rap/build.properties
deleted file mode 100644 (file)
index fd806ca..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-source.. = src/
-output.. = bin/
diff --git a/org.argeo.eclipse.ui.rap/pom.xml b/org.argeo.eclipse.ui.rap/pom.xml
deleted file mode 100644 (file)
index e56d439..0000000
+++ /dev/null
@@ -1,49 +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.1-SNAPSHOT</version>
-               <artifactId>argeo-commons</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.eclipse.ui.rap</artifactId>
-       <name>Commons Eclipse UI RAP</name>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.eclipse.ui</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </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>
-
-               <!-- File upload -->
-               <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.eclipse.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java
deleted file mode 100644 (file)
index 29165a4..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.argeo.eclipse.ui.jetty;
-
-import java.io.IOException;
-import java.lang.management.ManagementFactory;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.resource.Resource;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.rap.rwt.application.AbstractEntryPoint;
-import org.eclipse.rap.rwt.application.ApplicationRunner;
-import org.eclipse.rap.rwt.engine.RWTServlet;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Label;
-
-/** A minimal RWT runner based on embedded Jetty. */
-public class RwtRunner {
-
-       private final Server server;
-       private final ServerConnector serverConnector;
-       private Path tempDir;
-
-       public RwtRunner() {
-               server = new Server(new QueuedThreadPool(10, 1));
-               serverConnector = new ServerConnector(server);
-               serverConnector.setPort(0);
-               server.setConnectors(new Connector[] { serverConnector });
-       }
-
-       protected Control createUi(Composite parent, Object context) {
-               return new Label(parent, SWT.NONE);
-       }
-
-       public void init() {
-               ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-               context.setContextPath("/");
-               server.setHandler(context);
-
-               String entryPoint = "app";
-
-               // rwt-resources requires a file system
-               try {
-                       tempDir = Files.createTempDirectory("argeo-rwtRunner");
-                       context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString()));
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot create temporary directory", e);
-               }
-               context.addEventListener(new ServletContextListener() {
-                       ApplicationRunner applicationRunner;
-
-                       @Override
-                       public void contextInitialized(ServletContextEvent sce) {
-                               applicationRunner = new ApplicationRunner(
-                                               (application) -> application.addEntryPoint("/" + entryPoint, () -> new AbstractEntryPoint() {
-                                                       private static final long serialVersionUID = 5678385921969090733L;
-
-                                                       @Override
-                                                       protected void createContents(Composite parent) {
-                                                               createUi(parent, null);
-                                                       }
-                                               }, null), sce.getServletContext());
-                               applicationRunner.start();
-                       }
-
-                       @Override
-                       public void contextDestroyed(ServletContextEvent sce) {
-                               applicationRunner.stop();
-                       }
-               });
-
-               context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
-
-               // Required to serve rwt-resources. It is important that this is last.
-               ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
-               context.addServlet(holderPwd, "/");
-
-               try {
-                       server.start();
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot start Jetty server", e);
-               }
-       }
-
-       public void destroy() {
-               try {
-                       serverConnector.close();
-                       server.stop();
-                       // TODO delete temp dir
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public Integer getEffectivePort() {
-               return serverConnector.getLocalPort();
-       }
-
-       public void waitFor() throws InterruptedException {
-               server.join();
-       }
-
-       public static void main(String[] args) throws Exception {
-               RwtRunner rwtRunner = new RwtRunner() {
-
-                       @Override
-                       protected Control createUi(Composite parent, Object context) {
-                               Label label = new Label(parent, SWT.NONE);
-                               label.setText("Hello world!");
-                               return label;
-                       }
-               };
-               rwtRunner.init();
-               Runtime.getRuntime().addShutdownHook(new Thread(() -> rwtRunner.destroy(), "Jetty shutdown"));
-
-               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-               System.out.println("App available in " + jvmUptime + " ms, on port " + rwtRunner.getEffectivePort());
-
-               // open browser in app mode
-               Thread.sleep(2000);// wait for RWT to be ready
-               Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app");
-
-               rwtRunner.waitFor();
-       }
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
deleted file mode 100644 (file)
index 6100c1a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.swt.widgets.FileDialog;
-import org.eclipse.swt.widgets.Shell;
-
-public class CmsFileDialog extends FileDialog {
-       private static final long serialVersionUID = -7540791204102318801L;
-
-       public CmsFileDialog(Shell parent, int style) {
-               super(parent, style);
-       }
-
-       public CmsFileDialog(Shell parent) {
-               super(parent);
-       }
-
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
deleted file mode 100644 (file)
index 3f30bde..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.rap.rwt.widgets.FileUpload;
-import org.eclipse.swt.events.SelectionListener;
-import org.eclipse.swt.widgets.Composite;
-
-public class CmsFileUpload extends FileUpload {
-       private static final long serialVersionUID = 8027963992680980549L;
-
-       public CmsFileUpload(Composite parent, int style) {
-               super(parent, style);
-       }
-
-       @Override
-       public void setText(String text) {
-               super.setText(text);
-       }
-
-       @Override
-       public String getFileName() {
-               return super.getFileName();
-       }
-
-       @Override
-       public String[] getFileNames() {
-               return super.getFileNames();
-       }
-
-       @Override
-       public void addSelectionListener(SelectionListener listener) {
-               super.addSelectionListener(listener);
-       }
-
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
deleted file mode 100644 (file)
index a89b921..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.eclipse.jface.viewers.AbstractTableViewer;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Widget;
-
-/** Static utilities to bridge differences between RCP and RAP */
-public class EclipseUiSpecificUtils {
-
-       public static void setStyleData(Widget widget, Object data) {
-               widget.setData(RWT.CUSTOM_VARIANT, data);
-       }
-
-       public static Object getStyleData(Widget widget) {
-               return widget.getData(RWT.CUSTOM_VARIANT);
-       }
-
-       public static void setMarkupData(Widget widget) {
-               widget.setData(RWT.MARKUP_ENABLED, true);
-       }
-
-       public static void setMarkupValidationDisabledData(Widget widget) {
-               widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
-       }
-
-       /**
-        * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
-        */
-       public static void enableToolTipSupport(Viewer viewer) {
-               if (viewer instanceof ColumnViewer)
-                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
-       }
-
-       private EclipseUiSpecificUtils() {
-       }
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
deleted file mode 100644 (file)
index f9ca816..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.eclipse.rap.fileupload.FileDetails;
-import org.eclipse.rap.fileupload.FileUploadHandler;
-import org.eclipse.rap.fileupload.FileUploadReceiver;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.ClientFile;
-import org.eclipse.rap.rwt.client.service.ClientFileUploader;
-import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
-import org.eclipse.swt.dnd.DND;
-import org.eclipse.swt.dnd.DropTarget;
-import org.eclipse.swt.dnd.DropTargetAdapter;
-import org.eclipse.swt.dnd.DropTargetEvent;
-import org.eclipse.swt.dnd.Transfer;
-import org.eclipse.swt.widgets.Control;
-
-/** Configures a {@link Control} to receive files drop from the client OS. */
-public class FileDropAdapter {
-
-       public void prepareDropTarget(Control control, DropTarget dropTarget) {
-               dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
-               dropTarget.addDropListener(new DropTargetAdapter() {
-                       private static final long serialVersionUID = 5361645765549463168L;
-
-                       @Override
-                       public void dropAccept(DropTargetEvent event) {
-                               if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
-                                       event.detail = DND.DROP_NONE;
-                               }
-                       }
-
-                       @Override
-                       public void drop(DropTargetEvent event) {
-                               handleFileDrop(control, event);
-                       }
-               });
-       }
-
-       public void handleFileDrop(Control control, DropTargetEvent event) {
-               ClientFile[] clientFiles = (ClientFile[]) event.data;
-               ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
-//             DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
-               FileUploadReceiver receiver = new FileUploadReceiver() {
-
-                       @Override
-                       public void receive(InputStream stream, FileDetails details) throws IOException {
-                               control.getDisplay().syncExec(() -> {
-                                       try {
-                                               processUpload(stream, details.getFileName(), details.getContentType());
-                                       } catch (IOException e) {
-                                               throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
-                                       }
-                               });
-                       }
-               };
-               FileUploadHandler handler = new FileUploadHandler(receiver);
-//                 handler.setMaxFileSize( sizeLimit );
-//                 handler.setUploadTimeLimit( timeLimit );
-               service.submit(handler.getUploadUrl(), clientFiles);
-//             for (File file : receiver.getTargetFiles()) {
-//                     paths.add(file.toPath());
-//             }
-       }
-
-       /** Executed in UI thread */
-       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
-
-       }
-
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFile.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFile.java
deleted file mode 100644 (file)
index b2d3518..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.util.SingleSourcingConstants;
-import org.eclipse.core.commands.AbstractHandler;
-import org.eclipse.core.commands.ExecutionEvent;
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.rap.rwt.client.service.UrlLauncher;
-
-/**
- * RWT specific object to open a file retrieved from the server. It forwards the
- * request to the correct service after encoding file name and path in the
- * request URI.
- * 
- * <p>
- * The parameter "URI" is used to determine the correct file service, the path
- * and the file name. An optional file name can be added to present the end user
- * with a different file name as the one used to retrieve it.
- * </p>
- * 
- * 
- * <p>
- * The instance specific service is called by its ID and must have been
- * externally created
- * </p>
- */
-public class OpenFile extends AbstractHandler {
-       private final static Log log = LogFactory.getLog(OpenFile.class);
-
-       public final static String ID = SingleSourcingConstants.OPEN_FILE_CMD_ID;
-       public final static String PARAM_FILE_NAME = SingleSourcingConstants.PARAM_FILE_NAME;
-       public final static String PARAM_FILE_URI = SingleSourcingConstants.PARAM_FILE_URI;;
-       
-       /* DEPENDENCY INJECTION */
-       private String openFileServiceId;
-
-       public Object execute(ExecutionEvent event) {
-               String fileName = event.getParameter(PARAM_FILE_NAME);
-               String fileUri = event.getParameter(PARAM_FILE_URI);
-               // Sanity check
-               if (fileUri == null || "".equals(fileUri.trim()) || openFileServiceId == null
-                               || "".equals(openFileServiceId.trim()))
-                       return null;
-
-               org.argeo.eclipse.ui.specific.OpenFile openFileClient = new org.argeo.eclipse.ui.specific.OpenFile();
-               openFileClient.execute(openFileServiceId, fileUri, fileName);
-               return null;
-       }
-
-       public Object execute(String openFileServiceId, String fileUri, String fileName) {
-               StringBuilder url = new StringBuilder();
-               url.append(RWT.getServiceManager().getServiceHandlerUrl(openFileServiceId));
-
-               if (EclipseUiUtils.notEmpty(fileName))
-                       url.append("&").append(SingleSourcingConstants.PARAM_FILE_NAME).append("=").append(fileName);
-               url.append("&").append(SingleSourcingConstants.PARAM_FILE_URI).append("=").append(fileUri);
-
-               String downloadUrl = url.toString();
-               if (log.isTraceEnabled())
-                       log.trace("Calling OpenFileService with ID: " + openFileServiceId + " , with download URL: " + downloadUrl);
-
-               UrlLauncher launcher = RWT.getClient().getService(UrlLauncher.class);
-               launcher.openURL(downloadUrl);
-               return null;
-       }
-
-       /* DEPENDENCY INJECTION */
-       public void setOpenFileServiceId(String openFileServiceId) {
-               this.openFileServiceId = openFileServiceId;
-       }
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFileService.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/OpenFileService.java
deleted file mode 100644 (file)
index 4630d63..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import static org.argeo.eclipse.ui.util.SingleSourcingConstants.FILE_SCHEME;
-import static org.argeo.eclipse.ui.util.SingleSourcingConstants.SCHEME_HOST_SEPARATOR;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.eclipse.ui.EclipseUiUtils;
-import org.argeo.eclipse.ui.util.SingleSourcingConstants;
-import org.eclipse.rap.rwt.service.ServiceHandler;
-
-/**
- * RWT specific Basic Default service handler that retrieves a file on the
- * server file system using its absolute path and forwards it to the end user
- * browser.
- * 
- * Clients might extend to provide context specific services
- */
-public class OpenFileService implements ServiceHandler {
-       public OpenFileService() {
-       }
-
-       public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
-               String fileName = request.getParameter(SingleSourcingConstants.PARAM_FILE_NAME);
-               String uri = request.getParameter(SingleSourcingConstants.PARAM_FILE_URI);
-
-               // Use buffered array to directly write the stream?
-               if (!uri.startsWith(SingleSourcingConstants.FILE_SCHEME))
-                       throw new IllegalArgumentException(
-                                       "Open file service can only handle files that are on the server file system");
-
-               // Set the Metadata
-               response.setContentLength((int) getFileSize(uri));
-               if (EclipseUiUtils.isEmpty(fileName))
-                       fileName = getFileName(uri);
-               response.setContentType(getMimeType(uri, fileName));
-               String contentDisposition = "attachment; filename=\"" + fileName + "\"";
-               response.setHeader("Content-Disposition", contentDisposition);
-
-               // Useless for current use
-               // response.setHeader("Content-Transfer-Encoding", "binary");
-               // response.setHeader("Pragma", "no-cache");
-               // response.setHeader("Cache-Control", "no-cache, must-revalidate");
-
-               Path path = Paths.get(getAbsPathFromUri(uri));
-               Files.copy(path, response.getOutputStream());
-
-               // FIXME we always use temporary files for the time being.
-               // the deleteOnClose file only works when the JVM is closed so we
-               // explicitly delete to avoid overloading the server
-               if (path.startsWith("/tmp"))
-                       path.toFile().delete();
-       }
-
-       protected long getFileSize(String uri) throws IOException {
-               if (uri.startsWith(SingleSourcingConstants.FILE_SCHEME)) {
-                       Path path = Paths.get(getAbsPathFromUri(uri));
-                       return Files.size(path);
-               }
-               return -1l;
-       }
-
-       protected String getFileName(String uri) {
-               if (uri.startsWith(SingleSourcingConstants.FILE_SCHEME)) {
-                       Path path = Paths.get(getAbsPathFromUri(uri));
-                       return path.getFileName().toString();
-               }
-               return null;
-       }
-
-       private String getAbsPathFromUri(String uri) {
-               if (uri.startsWith(FILE_SCHEME))
-                       return uri.substring((FILE_SCHEME + SCHEME_HOST_SEPARATOR).length());
-               // else if (uri.startsWith(JCR_SCHEME))
-               // return uri.substring((JCR_SCHEME + SCHEME_HOST_SEPARATOR).length());
-               else
-                       throw new IllegalArgumentException("Unknown URI prefix for" + uri);
-       }
-
-       protected String getMimeType(String uri, String fileName) throws IOException {
-               if (uri.startsWith(FILE_SCHEME)) {
-                       Path path = Paths.get(getAbsPathFromUri(uri));
-                       String mimeType = Files.probeContentType(path);
-                       if (EclipseUiUtils.notEmpty(mimeType))
-                               return mimeType;
-               }
-               return "application/octet-stream";
-       }
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/SingleSourcingException.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/SingleSourcingException.java
deleted file mode 100644 (file)
index 9b75690..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-/** Exception related to SWT/RWT single sourcing. */
-public class SingleSourcingException extends RuntimeException {
-       private static final long serialVersionUID = -727700418055348468L;
-
-       public SingleSourcingException(String message, Throwable cause) {
-               super(message, cause);
-       }
-
-       public SingleSourcingException(String message) {
-               super(message);
-       }
-
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/UiContext.java
deleted file mode 100644 (file)
index dac2700..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.argeo.eclipse.ui.specific;
-
-import java.util.Locale;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.rap.rwt.RWT;
-import org.eclipse.swt.widgets.Display;
-
-/** Singleton class providing single sources infos about the UI context. */
-public class UiContext {
-       /** Can be null, thus indicating that we are not in a web context. */
-       public static HttpServletRequest getHttpRequest() {
-               return RWT.getRequest();
-       }
-
-       public static HttpServletResponse getHttpResponse() {
-               return RWT.getResponse();
-       }
-
-       public static Locale getLocale() {
-               if (Display.getCurrent() != null)
-                       return RWT.getUISession().getLocale();
-               else
-                       return Locale.getDefault();
-       }
-
-       public static void setLocale(Locale locale) {
-               if (Display.getCurrent() != null)
-                       RWT.getUISession().setLocale(locale);
-               else
-                       Locale.setDefault(locale);
-       }
-
-       /** Can always be null */
-       @SuppressWarnings("unchecked")
-       public static <T> T getData(String key) {
-               Display display = getDisplay();
-               if (display == null)
-                       return null;
-               return (T) display.getData(key);
-       }
-
-       public static void setData(String key, Object value) {
-               Display display = getDisplay();
-               if (display == null)
-                       throw new SingleSourcingException("Not display available in RAP context");
-               display.setData(key, value);
-       }
-
-       private static Display getDisplay() {
-               return Display.getCurrent();
-       }
-
-       private UiContext() {
-       }
-
-}
diff --git a/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/org.argeo.eclipse.ui.rap/src/org/argeo/eclipse/ui/specific/package-info.java
deleted file mode 100644 (file)
index 4ec451f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
-package org.argeo.eclipse.ui.specific;
\ No newline at end of file
diff --git a/org.argeo.eclipse.ui/.classpath b/org.argeo.eclipse.ui/.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.eclipse.ui/.project b/org.argeo.eclipse.ui/.project
deleted file mode 100644 (file)
index 3140a5c..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.eclipse.ui</name>
-       <comment></comment>
-       <projects></projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments />
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments />
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments />
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
\ No newline at end of file
diff --git a/org.argeo.eclipse.ui/META-INF/.gitignore b/org.argeo.eclipse.ui/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.eclipse.ui/bnd.bnd b/org.argeo.eclipse.ui/bnd.bnd
deleted file mode 100644 (file)
index b836fc0..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Import-Package: javax.jcr.nodetype,\
-                               org.eclipse.swt,\
-                               org.eclipse.jface.window,\
-                               org.eclipse.core.commands.common,\
-                               *
diff --git a/org.argeo.eclipse.ui/build.properties b/org.argeo.eclipse.ui/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.eclipse.ui/pom.xml b/org.argeo.eclipse.ui/pom.xml
deleted file mode 100644 (file)
index e3069dd..0000000
+++ /dev/null
@@ -1,41 +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.1-SNAPSHOT</version>
-               <artifactId>argeo-commons</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.eclipse.ui</artifactId>
-       <name>Commons Eclipse UI</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.jcr</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </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.eclipse.ui/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/ColumnDefinition.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/EclipseJcrMonitor.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/EclipseJcrMonitor.java
deleted file mode 100644 (file)
index ffc50ad..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.eclipse.ui;
-
-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.eclipse.ui/src/org/argeo/eclipse/ui/EclipseUiException.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/FileProvider.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/GenericTableComparator.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/IListProvider.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDoubleClick.java
deleted file mode 100644 (file)
index 49d11e5..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.eclipse.ui;
-
-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.eclipse.ui/src/org/argeo/eclipse/ui/MouseDown.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/MouseDown.java
deleted file mode 100644 (file)
index 7abed80..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.eclipse.ui;
-
-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.eclipse.ui/src/org/argeo/eclipse/ui/Selected.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/Selected.java
deleted file mode 100644 (file)
index 77bdd79..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.eclipse.ui;
-
-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.eclipse.ui/src/org/argeo/eclipse/ui/TreeParent.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java
deleted file mode 100644 (file)
index f3d0531..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java
deleted file mode 100644 (file)
index 825879c..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java
deleted file mode 100644 (file)
index d00365b..0000000
+++ /dev/null
@@ -1,257 +0,0 @@
-package org.argeo.eclipse.ui.dialogs;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/dialogs/package-info.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java
deleted file mode 100644 (file)
index 136eb50..0000000
+++ /dev/null
@@ -1,451 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiException.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/ParentDir.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java
deleted file mode 100644 (file)
index ff93d82..0000000
+++ /dev/null
@@ -1,212 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java
deleted file mode 100644 (file)
index f8128d9..0000000
+++ /dev/null
@@ -1,129 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.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.eclipse.ui/src/org/argeo/eclipse/ui/fs/file.png b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/file.png
deleted file mode 100644 (file)
index ce2f2a8..0000000
Binary files a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/file.png and /dev/null differ
diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/folder.png b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/folder.png
deleted file mode 100644 (file)
index c31f37e..0000000
Binary files a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/folder.png and /dev/null differ
diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/fs/package-info.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java
deleted file mode 100644 (file)
index 8f7c2f9..0000000
+++ /dev/null
@@ -1,139 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory
-                       .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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java
deleted file mode 100644 (file)
index ae5c293..0000000
+++ /dev/null
@@ -1,84 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log logThis = LogFactory.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 Log 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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/org.argeo.eclipse.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.eclipse.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/org.argeo.eclipse.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/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/package-info.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/parts/package-info.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/util/SingleSourcingConstants.java b/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/util/SingleSourcingConstants.java
deleted file mode 100644 (file)
index b99f37a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.eclipse.ui.util;
-
-/**
- * Centralise constants that are used in both RAP and RCP specific code to avoid
- * duplicated declaration
- */
-public interface SingleSourcingConstants {
-
-       // Single sourced open file command
-       String OPEN_FILE_CMD_ID = "org.argeo.cms.ui.workbench.openFile";
-       String PARAM_FILE_NAME = "param.fileName";
-       String PARAM_FILE_URI = "param.fileURI";
-
-       String SCHEME_HOST_SEPARATOR = "://";
-       String FILE_SCHEME = "file";
-       String JCR_SCHEME = "jcr";
-}
diff --git a/org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/org.argeo.eclipse.ui/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.eclipse.ui/src/org/argeo/eclipse/ui/util/package-info.java b/org.argeo.eclipse.ui/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.enterprise/.classpath b/org.argeo.enterprise/.classpath
deleted file mode 100644 (file)
index b21df7d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src" />
-       <classpathentry kind="src" path="ext/test" />
-       <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.enterprise/.project b/org.argeo.enterprise/.project
deleted file mode 100644 (file)
index 5de2f0a..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.enterprise</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-               <nature>org.eclipse.pde.PluginNature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.enterprise/META-INF/.gitignore b/org.argeo.enterprise/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.enterprise/bnd.bnd b/org.argeo.enterprise/bnd.bnd
deleted file mode 100644 (file)
index 5f42f77..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-Bundle-Activator: org.argeo.osgi.internal.EnterpriseActivator
-Bundle-ActivationPolicy: lazy
-
-Import-Package:        org.osgi.*;version=0.0.0,\
-!org.apache.commons.logging,\
-*                              
diff --git a/org.argeo.enterprise/build.properties b/org.argeo.enterprise/build.properties
deleted file mode 100644 (file)
index b82dd50..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/,\
-           ext/test/
-additional.bundles = org.junit
-
diff --git a/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/BasicTestConstants.java b/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/BasicTestConstants.java
deleted file mode 100644 (file)
index 98b8bc9..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-interface BasicTestConstants {
-       String BASE_DN = "dc=example,dc=com";
-       String ROOT_USER_DN = "uid=root,ou=users," + BASE_DN;
-       String DEMO_USER_DN = "uid=demo,ou=users," + BASE_DN;
-       String ADMIN_GROUP_DN = "cn=admin,ou=groups," + BASE_DN;
-       String EDITORS_GROUP_DN = "cn=editors,ou=groups," + BASE_DN;
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java b/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifParserTest.java
deleted file mode 100644 (file)
index 012a50e..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.LdapAttrs;
-import org.argeo.naming.LdifParser;
-
-/** {@link LdifParser} tests. */
-public class LdifParserTest implements BasicTestConstants {
-       public void testBasicLdif() throws Exception {
-               LdifParser ldifParser = new LdifParser();
-               SortedMap<LdapName, Attributes> res = ldifParser.read(getClass().getResourceAsStream("basic.ldif"));
-               LdapName rootDn = new LdapName(ROOT_USER_DN);
-               Attributes rootAttributes = res.get(rootDn);
-               assert rootAttributes != null;
-               assert "Superuser".equals(rootAttributes.get(LdapAttrs.description.name()).get());
-               byte[] rawPwEntry = (byte[]) rootAttributes.get(LdapAttrs.userPassword.name()).get();
-               assert "{SHA}ieSV55Qc+eQOaYDRSha/AjzNTJE=".contentEquals(new String(rawPwEntry));
-               byte[] hashedPassword = DigestUtils.sha1("demo".getBytes());
-               assert ("{SHA}" + Base64.getEncoder().encodeToString(hashedPassword)).equals(new String(rawPwEntry));
-
-               LdapName adminDn = new LdapName(ADMIN_GROUP_DN);
-               Attributes adminAttributes = res.get(adminDn);
-               assert adminAttributes != null;
-               Attribute memberAttribute = adminAttributes.get(LdapAttrs.member.name());
-               assert memberAttribute != null;
-               NamingEnumeration<?> members = memberAttribute.getAll();
-               List<String> users = new ArrayList<String>();
-               while (members.hasMore()) {
-                       Object value = members.next();
-                       users.add(value.toString());
-               }
-               assert 1 == users.size();
-               assert rootDn.equals(new LdapName(users.get(0)));
-       }
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifUserAdminTest.java b/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/LdifUserAdminTest.java
deleted file mode 100644 (file)
index 126125b..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.transaction.TransactionManager;
-
-import org.argeo.naming.LdapAttrs;
-import org.argeo.transaction.simple.SimpleTransactionManager;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-import junit.framework.TestCase;
-
-/** {@link LdifUserAdmin} tests. */
-public class LdifUserAdminTest extends TestCase implements BasicTestConstants {
-       // We have to keep using JUnit because of
-       // https://issues.apache.org/jira/browse/SUREFIRE-1669
-
-       final static int TM_SIMPLE = 0;
-       final static int TM_BITRONIX = 1;
-
-       private int tmType = TM_SIMPLE;
-       private TransactionManager tm;
-       private URI uri;
-       private AbstractUserDirectory userAdmin;
-       private Path tempDir;
-
-       public void setUp() {
-               System.out.println("Enter setUp()");
-               try {
-                       tempDir = Files.createTempDirectory(getClass().getName());
-                       tempDir.toFile().deleteOnExit();
-                       String uriProp = System.getProperty("argeo.userdirectory.uri");
-                       if (uriProp != null)
-                               uri = new URI(uriProp);
-                       else {
-                               tempDir.toFile().deleteOnExit();
-                               Path ldifPath = tempDir.resolve(BASE_DN + ".ldif");
-                               try (InputStream in = getClass().getResource("basic.ldif").openStream()) {
-                                       Files.copy(in, ldifPath);
-                               }
-                               uri = ldifPath.toUri();
-                       }
-
-                       // Init transaction manager
-                       if (TM_SIMPLE == tmType) {
-                               tm = new SimpleTransactionManager();
-                       }
-//             else if (TM_BITRONIX == tmType) {
-//                     bitronix.tm.Configuration tmConf = TransactionManagerServices.getConfiguration();
-//                     tmConf.setServerId(UUID.randomUUID().toString());
-//                     tmConf.setLogPart1Filename(new File(tempDir.toFile(), "btm1.tlog").getAbsolutePath());
-//                     tmConf.setLogPart2Filename(new File(tempDir.toFile(), "btm2.tlog").getAbsolutePath());
-//                     tm = TransactionManagerServices.getTransactionManager();
-//             }
-
-                       userAdmin = initUserAdmin(uri, tm);
-               } catch (Exception e) {
-                       throw new RuntimeException(e);
-               }
-       }
-
-       public void testEdition() throws Exception {
-               User demoUser = (User) userAdmin.getRole(DEMO_USER_DN);
-               assert demoUser != null;
-
-               tm.begin();
-               String newName = "demo";
-               demoUser.getProperties().put("cn", newName);
-               assert newName.equals(demoUser.getProperties().get("cn"));
-               tm.commit();
-               persistAndRestart();
-               assert newName.equals(demoUser.getProperties().get("cn"));
-
-               tm.begin();
-               userAdmin.removeRole(DEMO_USER_DN);
-               tm.commit();
-               persistAndRestart();
-
-               // check data
-               Role[] search = userAdmin.getRoles("(objectclass=inetOrgPerson)");
-               assert 1 == search.length;
-               Group editorGroup = (Group) userAdmin.getRole(EDITORS_GROUP_DN);
-               assert editorGroup != null;
-               Role[] members = editorGroup.getMembers();
-               assert 1 == members.length;
-       }
-
-       public void testRetrieve() throws Exception {
-               // users
-               User rootUser = (User) userAdmin.getRole(ROOT_USER_DN);
-               assert rootUser != null;
-               User demoUser = (User) userAdmin.getRole(DEMO_USER_DN);
-               assert demoUser != null;
-
-               // groups
-               Group adminGroup = (Group) userAdmin.getRole(ADMIN_GROUP_DN);
-               assert adminGroup != null;
-               Role[] members = adminGroup.getMembers();
-               assert 1 == members.length;
-               assert rootUser.equals(members[0]);
-
-               Group editorGroup = (Group) userAdmin.getRole(EDITORS_GROUP_DN);
-               assert editorGroup != null;
-               members = editorGroup.getMembers();
-               assert 2 == members.length;
-               assert adminGroup.equals(members[0]);
-               assert demoUser.equals(members[1]);
-
-               Authorization rootAuth = userAdmin.getAuthorization(rootUser);
-               List<String> rootRoles = Arrays.asList(rootAuth.getRoles());
-               assert 3 == rootRoles.size();
-               assert rootRoles.contains(ROOT_USER_DN);
-               assert rootRoles.contains(ADMIN_GROUP_DN);
-               assert rootRoles.contains(EDITORS_GROUP_DN);
-
-               // properties
-               assert "root@localhost".equals(rootUser.getProperties().get("mail"));
-
-               // credentials
-               // {SHA}
-               assert rootUser.hasCredential(LdapAttrs.userPassword.name(), "demo".getBytes(StandardCharsets.UTF_8));
-               // {PBKDF2_SHA256}
-               assert demoUser.hasCredential(LdapAttrs.userPassword.name(), "demo".getBytes(StandardCharsets.UTF_8));
-
-               // search
-               Role[] search = userAdmin.getRoles(null);
-               assert 4 == search.length;
-               search = userAdmin.getRoles("(objectClass=groupOfNames)");
-               assert 2 == search.length;
-               search = userAdmin.getRoles("(objectclass=inetOrgPerson)");
-               assert 2 == search.length;
-               search = userAdmin.getRoles("(&(objectclass=inetOrgPerson)(uid=demo))");
-               assert 1 == search.length;
-       }
-
-       public void testReadWriteRead() throws Exception {
-               if (userAdmin instanceof LdifUserAdmin) {
-                       Dictionary<String, Object> props = userAdmin.getProperties();
-                       ByteArrayOutputStream out = new ByteArrayOutputStream();
-                       ((LdifUserAdmin) userAdmin).save(out);
-                       byte[] arr = out.toByteArray();
-                       out.close();
-                       userAdmin.destroy();
-                       // String written = new String(arr);
-                       // System.out.print(written);
-                       try (ByteArrayInputStream in = new ByteArrayInputStream(arr)) {
-                               userAdmin = new LdifUserAdmin(props);
-                               ((LdifUserAdmin) userAdmin).load(in);
-                       }
-                       Role[] search = userAdmin.getRoles(null);
-                       assert 4 == search.length;
-               } else {
-                       // test not relevant for LDAP
-               }
-       }
-
-       private AbstractUserDirectory initUserAdmin(URI uri, TransactionManager tm) {
-               Dictionary<String, Object> props = new Hashtable<>();
-               props.put(UserAdminConf.uri.name(), uri.toString());
-               props.put(UserAdminConf.baseDn.name(), BASE_DN);
-               props.put(UserAdminConf.userBase.name(), "ou=users");
-               props.put(UserAdminConf.groupBase.name(), "ou=groups");
-               AbstractUserDirectory userAdmin;
-               if (uri.getScheme().startsWith("ldap"))
-                       userAdmin = new LdapUserAdmin(props);
-               else
-                       userAdmin = new LdifUserAdmin(props);
-               userAdmin.init();
-               // JTA
-//             if (TM_BITRONIX == tmType)
-//                     EhCacheXAResourceProducer.registerXAResource(UserDirectory.class.getName(), userAdmin.getXaResource());
-               userAdmin.setTransactionManager(tm);
-               return userAdmin;
-       }
-
-       private void persistAndRestart() {
-//             if (TM_BITRONIX == tmType)
-//                     EhCacheXAResourceProducer.unregisterXAResource(UserDirectory.class.getName(), userAdmin.getXaResource());
-               if (userAdmin instanceof LdifUserAdmin)
-                       ((LdifUserAdmin) userAdmin).save();
-               userAdmin.destroy();
-               userAdmin = initUserAdmin(uri, tm);
-       }
-
-       public void tearDown() throws Exception {
-//             if (TM_BITRONIX == tmType) {
-//                     EhCacheXAResourceProducer.unregisterXAResource(UserDirectory.class.getName(), userAdmin.getXaResource());
-//                     ((BitronixTransactionManager) tm).shutdown();
-//             }
-               if (userAdmin != null)
-                       userAdmin.destroy();
-               if (tempDir != null)
-                       Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
-                               @Override
-                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                       Files.delete(file);
-                                       return FileVisitResult.CONTINUE;
-                               }
-
-                               @Override
-                               public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                                       Files.delete(dir);
-                                       return FileVisitResult.CONTINUE;
-                               }
-
-                       });
-       }
-
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/UserAdminConfTest.java b/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/UserAdminConfTest.java
deleted file mode 100644 (file)
index 77a35f4..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.osgi.useradmin.UserAdminConf.propertiesAsUri;
-import static org.argeo.osgi.useradmin.UserAdminConf.uriAsProperties;
-
-import java.net.URI;
-import java.util.Dictionary;
-
-/** {@link UserAdminConf} tests. */
-public class UserAdminConfTest {
-       public void testUriFormat() throws Exception {
-               // LDAP
-               URI uriIn = new URI("ldap://" + "uid=admin,ou=system:secret@localhost:10389" + "/dc=example,dc=com"
-                               + "?readOnly=false&userObjectClass=person");
-               Dictionary<String, ?> props = uriAsProperties(uriIn.toString());
-               System.out.println(props);
-               assert "dc=example,dc=com".equals(props.get(UserAdminConf.baseDn.name()));
-               assert "false".equals(props.get(UserAdminConf.readOnly.name()));
-               assert "person".equals(props.get(UserAdminConf.userObjectClass.name()));
-               URI uriOut = propertiesAsUri(props);
-               System.out.println(uriOut);
-               assert "/dc=example,dc=com?userObjectClass=person&readOnly=false".equals(uriOut.toString());
-
-               // File
-               uriIn = new URI("file://some/dir/dc=example,dc=com.ldif");
-               props = uriAsProperties(uriIn.toString());
-               System.out.println(props);
-               assert "dc=example,dc=com".equals(props.get(UserAdminConf.baseDn.name()));
-
-               // Base configuration
-               uriIn = new URI("/dc=example,dc=com.ldif?readOnly=true&userBase=ou=CoWorkers,ou=People&groupBase=ou=Roles");
-               props = uriAsProperties(uriIn.toString());
-               System.out.println(props);
-               assert "dc=example,dc=com".equals(props.get(UserAdminConf.baseDn.name()));
-               assert "true".equals(props.get(UserAdminConf.readOnly.name()));
-               assert "ou=CoWorkers,ou=People".equals(props.get(UserAdminConf.userBase.name()));
-               assert "ou=Roles".equals(props.get(UserAdminConf.groupBase.name()));
-               uriOut = propertiesAsUri(props);
-               System.out.println(uriOut);
-               assert "/dc=example,dc=com?userBase=ou=CoWorkers,ou=People&groupBase=ou=Roles&readOnly=true"
-                               .equals(uriOut.toString());
-
-               // OS
-               uriIn = new URI("os:///dc=example,dc=com");
-               props = uriAsProperties(uriIn.toString());
-               System.out.println(props);
-               assert "dc=example,dc=com".equals(props.get(UserAdminConf.baseDn.name()));
-               assert "true".equals(props.get(UserAdminConf.readOnly.name()));
-               uriOut = propertiesAsUri(props);
-               System.out.println(uriOut);
-               assert "/dc=example,dc=com?readOnly=true".equals(uriOut.toString());
-       }
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/basic.ldif b/org.argeo.enterprise/ext/test/org/argeo/osgi/useradmin/basic.ldif
deleted file mode 100644 (file)
index e5b5615..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-dn: dc=example,dc=com
-objectClass: domain
-objectClass: extensibleObject
-objectClass: top
-dc: example
-
-dn: ou=groups,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: groups
-
-dn: ou=users,dc=example,dc=com
-objectClass: organizationalUnit
-objectClass: top
-ou: users
-
-dn: uid=demo,ou=users,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: organizationalPerson
-objectClass: person
-objectClass: top
-cn: Demo User
-description: Demo user
-givenName: Demo
-mail: demo@localhost
-sn: User
-uid: demo
-userPassword:: e1BCS0RGMl9TSEEyNTZ9QUFBSUFOMEtpaTA5Z0h5SHA4Q1Y2bHZhbE5DOWJPcjZTVGVpSFU3UDB
- 5UGVxVUVIdnR2c2pIVmVadW5YV3FNNG5MV090U1gvWS9Jc1FsdXdjR3lFclBJVTRBVWlRVytNb1
- Y0TTYzaWlPNnlkcXRFZ2dzSGlNK1lPamFZZGl2YUMrRERqRkNBeEN5VFdsdEFYNXZKaWZMMlBwa
- S93OXFkTWI4YjgyRFFJMUIxZG9IMEdPZ2ZISFQwT2luYm95QlNjUmhvaDN6WGVPd1ZabWlqNHlH
- Y1JPazhta1lRVm5SQXlyR2pvSHVsSXIwR3ovMnlhR3VFdWJSL2NLOUtsYTQyWWo5RTNRdmJJbkE
- 3Y0Rjc2xYTlJHTENMZVBhYTdsSWUxc3pUR2JGRVZ4aVQ2M2xQck9RcHNwamRubEFlSjkvWUx5Z3
- VFTHIrZDJoNmN1SzNmdGFLbmpiRWxTRFJBMy9OanIwRVVzUHBxZDFibWIxbmxMRHR3Mlo5Y3h0Y
- WljQTdSOHE3eXVhZzFQc0xac2dxdk9HR1hsZ1RVSk4rVitkWkVYdk1BSEgra0YvY1hhU05Q
-
-dn: uid=root,ou=users,dc=example,dc=com
-objectClass: inetOrgPerson
-objectClass: person
-objectClass: organizationalPerson
-objectClass: top
-cn: Super User
-description: Superuser
-givenName: Super
-mail: root@localhost
-sn: User
-uid: root
-userPassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
-
-dn: cn=admin,ou=groups,dc=example,dc=com
-objectClass: groupOfNames
-objectClass: top
-cn: admin
-member: uid=root,ou=users,dc=example,dc=com
-
-dn: cn=editors,ou=groups,dc=example,dc=com
-objectClass: groupOfNames
-objectClass: top
-cn: editors
-member: cn=admin,ou=groups,dc=example,dc=com
-member: uid=demo,ou=users,dc=example,dc=com
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserEncodingTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserEncodingTest.java
deleted file mode 100644 (file)
index 09443c2..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.List;
-
-/** Tests that {@link CsvParser} can deal properly with encodings. */
-public class CsvParserEncodingTest {
-
-       private String iso = "ISO-8859-1";
-       private String utf8 = "UTF-8";
-
-       public void testParse() throws Exception {
-
-               String xml = new String("áéíóúñ,éééé");
-               byte[] utfBytes = xml.getBytes(utf8);
-               byte[] isoBytes = xml.getBytes(iso);
-
-               InputStream inUtf = new ByteArrayInputStream(utfBytes);
-               InputStream inIso = new ByteArrayInputStream(isoBytes);
-
-               CsvParser csvParser = new CsvParser() {
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               assert header.size() == tokens.size();
-                               assert 2 == tokens.size();
-                               assert "áéíóúñ".equals(tokens.get(0));
-                               assert "éééé".equals(tokens.get(1));
-                       }
-               };
-
-               csvParser.parse(inUtf, utf8);
-               inUtf.close();
-               csvParser.parse(inIso, iso);
-               inIso.close();
-       }
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserParseFileTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserParseFileTest.java
deleted file mode 100644 (file)
index 5a92c68..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.util;
-
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-/** Test that {@link CsvParser} can properly parse a CSV file. */
-public class CsvParserParseFileTest {
-       public void testParse() throws Exception {
-
-               final Map<Integer, Map<String, String>> lines = new HashMap<Integer, Map<String, String>>();
-               InputStream in = getClass().getResourceAsStream("/org/argeo/util/ReferenceFile.csv");
-               CsvParserWithLinesAsMap parser = new CsvParserWithLinesAsMap() {
-                       protected void processLine(Integer lineNumber, Map<String, String> line) {
-                               lines.put(lineNumber, line);
-                       }
-               };
-
-               parser.parse(in);
-               in.close();
-
-               assert 5 == lines.size();
-       }
-
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserTest.java
deleted file mode 100644 (file)
index e59dbd1..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.List;
-
-/** {@link CsvParser} tests. */
-public class CsvParserTest {
-       public void testParse() throws Exception {
-               String toParse = "Header1,\"Header\n2\",Header3,\"Header4\"\n" + "Col1,\"Col\n2\",Col3,\"\"\"Col4\"\"\"\n"
-                               + "Col1,\"Col\n2\",Col3,\"\"\"Col4\"\"\"\n" + "Col1,\"Col\n2\",Col3,\"\"\"Col4\"\"\"\n";
-
-               InputStream in = new ByteArrayInputStream(toParse.getBytes());
-
-               CsvParser csvParser = new CsvParser() {
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               assert header.size() == tokens.size();
-                               assert 4 == tokens.size();
-                               assert "Col1".equals(tokens.get(0));
-                               assert "Col\n2".equals(tokens.get(1));
-                               assert "Col3".equals(tokens.get(2));
-                               assert "\"Col4\"".equals(tokens.get(3));
-                       }
-               };
-
-               csvParser.parse(in);
-               in.close();
-       }
-
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserWithQuotedSeparatorTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/CsvParserWithQuotedSeparatorTest.java
deleted file mode 100644 (file)
index 67ba346..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/** Test that {@link CsvParser} deals properly with "" quotes. */
-public class CsvParserWithQuotedSeparatorTest {
-       public void testSimpleParse() throws Exception {
-               String toParse = "Header1,\"Header2\",Header3,\"Header4\"\n"
-                               + "\"Col1, Col2\",\"Col\n2\",Col3,\"\"\"Col4\"\"\"\n";
-
-               InputStream in = new ByteArrayInputStream(toParse.getBytes());
-
-               CsvParser csvParser = new CsvParser() {
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               assert header.size() == tokens.size();
-                               assert 4 == tokens.size();
-                               assert "Col1, Col2".equals(tokens.get(0));
-                       }
-               };
-               // System.out.println(toParse);
-               csvParser.parse(in);
-               in.close();
-
-       }
-
-       public void testParseFile() throws Exception {
-
-               final Map<Integer, Map<String, String>> lines = new HashMap<Integer, Map<String, String>>();
-               InputStream in = getClass().getResourceAsStream("/org/argeo/util/ReferenceFile.csv");
-
-               CsvParserWithLinesAsMap parser = new CsvParserWithLinesAsMap() {
-                       protected void processLine(Integer lineNumber, Map<String, String> line) {
-                               // System.out.println("processing line #" + lineNumber);
-                               lines.put(lineNumber, line);
-                       }
-               };
-
-               parser.parse(in);
-               in.close();
-
-               Map<String, String> line = lines.get(2);
-               assert ",,,,".equals(line.get("Coma testing"));
-               line = lines.get(3);
-               assert ",, ,,".equals(line.get("Coma testing"));
-               line = lines.get(4);
-               assert "module1, module2".equals(line.get("Coma testing"));
-               line = lines.get(5);
-               assert "module1,module2".equals(line.get("Coma testing"));
-               line = lines.get(6);
-               assert ",module1,module2, \nmodule3, module4".equals(line.get("Coma testing"));
-               assert 5 == lines.size();
-
-       }
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/CsvWriterTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/CsvWriterTest.java
deleted file mode 100644 (file)
index ff5dcc5..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** {@link CsvWriter} tests. */
-public class CsvWriterTest {
-       public void testWrite() throws Exception {
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               final CsvWriter csvWriter = new CsvWriter(out);
-
-               String[] header = { "Header1", "Header 2", "Header,3", "Header\n4", "Header\"5\"" };
-               String[] line1 = { "Value1", "Value 2", "Value,3", "Value\n4", "Value\"5\"" };
-               csvWriter.writeLine(Arrays.asList(header));
-               csvWriter.writeLine(Arrays.asList(line1));
-
-               String reference = "Header1,Header 2,\"Header,3\",\"Header\n4\",\"Header\"\"5\"\"\"\n"
-                               + "Value1,Value 2,\"Value,3\",\"Value\n4\",\"Value\"\"5\"\"\"\n";
-               String written = new String(out.toByteArray());
-               assert reference.equals(written);
-               out.close();
-               System.out.println(written);
-
-               final List<String> allTokens = new ArrayList<String>();
-               CsvParser csvParser = new CsvParser() {
-                       protected void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-                               if (lineNumber == 2)
-                                       allTokens.addAll(header);
-                               allTokens.addAll(tokens);
-                       }
-               };
-               ByteArrayInputStream in = new ByteArrayInputStream(written.getBytes());
-               csvParser.parse(in);
-               in.close();
-               List<String> allTokensRef = new ArrayList<String>();
-               allTokensRef.addAll(Arrays.asList(header));
-               allTokensRef.addAll(Arrays.asList(line1));
-
-               assert allTokensRef.size() == allTokens.size();
-               for (int i = 0; i < allTokensRef.size(); i++)
-                       assert allTokensRef.get(i).equals(allTokens.get(i));
-       }
-
-}
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/ReferenceFile.csv b/org.argeo.enterprise/ext/test/org/argeo/util/ReferenceFile.csv
deleted file mode 100644 (file)
index 351453d..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-"ID","A long Text","Name","Other","Number","Reference","Target","Date","Update","Language","ID Ref","Weird chars","line feeds","after line feed","Empty column","Status comment","Comments","Empty","Coma testing"
-"AK251","Everything & with some line feed 
- more “some” quote","Marge S.",,78.6,"A1155222221111268515131",,12/12/12,03/12/08,,9821308500721,"%%%ùù","ao","Nothing special",,,"Some very usefull comment",,",,,,"
-"AG254","same","Roger “wallace” Big","15 – JI",78.5,"A1155222221111268515131","next milestone",12/12/12,03/12/08,"_fr (French - France)",9812309500953,"***µ”","a
-
-
-
-
-o","after line feed",,"Do the job",,,",, ,,"
-"FG211","Very long text with some bullets.
-1 first
-2 second
-3. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long","Father & Son","15 – JI",15.4,"A1155222221111268515131","next milestone",12/12/12,03/12/08,"_fr (French - France)",9812309500952,"///","a
-
-
-
-
-
-
-o","module1,module2",,"Be fast",,,"module1, module2"
-"RRT152","Very long text with some bullets.
-1 first
-2 second
-3. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long. some more very very very long","Another $$","15 – JI",12.3,"A1155222221111268515131","next milestone",12/12/12,03/12/08,"_fr (French - France)",9812309500950,"---","a
-
-o
-
-
-","module1,module2",,,,,"module1,module2"
-"YU121","Another use case : “blank line”
-
-After the blank.","nothing with brackets( )","15 – JI",15.2,"A1155222221111268515131",,12/12/12,03/12/08,"_fr (French - France)",9812309500925,",;:?./","ao","
-
-
-
-After line feed again",,,,,",module1,module2, 
-module3, module4"
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/TestParse-ISO.csv b/org.argeo.enterprise/ext/test/org/argeo/util/TestParse-ISO.csv
deleted file mode 100644 (file)
index 0bec611..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-"Date d'imputation","N° de compte","Code journal","Pièce interne","Pièce externe","Libellé d'écriture","Débit","Crédit","Lettrage","Quantité","Code analytique","Date d'échéance","Date d'imputation origine","Code journal origine","Mode de règlement","Date début de période","Date fin de période"
-26.01.2010,"101300","BQ","BQ01.10",,"Depot société en formation",,"3.000,00",,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"101300","BQ","BQ01.10",,"Depot société en formation",,"7.000,00",,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"411OPEN","BQ","BQ01.10",,"Vir Client ",,"2.508,00","A",,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"455100","BQ","BQ01.10",,"Bankomat Raiffeise","250,00",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"512101","BQ","BQ01.10",,"Extrait bancaire 01.10","12.250,55",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"627800","BQ","BQ01.10",,"Envoi de chequier","2,30",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"627800","BQ","BQ01.10",,"Frais d'expedition","5,15",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/TestParse-UTF-8.csv b/org.argeo.enterprise/ext/test/org/argeo/util/TestParse-UTF-8.csv
deleted file mode 100644 (file)
index 0bec611..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-"Date d'imputation","N° de compte","Code journal","Pièce interne","Pièce externe","Libellé d'écriture","Débit","Crédit","Lettrage","Quantité","Code analytique","Date d'échéance","Date d'imputation origine","Code journal origine","Mode de règlement","Date début de période","Date fin de période"
-26.01.2010,"101300","BQ","BQ01.10",,"Depot société en formation",,"3.000,00",,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"101300","BQ","BQ01.10",,"Depot société en formation",,"7.000,00",,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"411OPEN","BQ","BQ01.10",,"Vir Client ",,"2.508,00","A",,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"455100","BQ","BQ01.10",,"Bankomat Raiffeise","250,00",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"512101","BQ","BQ01.10",,"Extrait bancaire 01.10","12.250,55",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"627800","BQ","BQ01.10",,"Envoi de chequier","2,30",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
-26.01.2010,"627800","BQ","BQ01.10",,"Frais d'expedition","5,15",,,,,"          ",26.01.2010,"BQ","    ","          ","          "
diff --git a/org.argeo.enterprise/ext/test/org/argeo/util/ThroughputTest.java b/org.argeo.enterprise/ext/test/org/argeo/util/ThroughputTest.java
deleted file mode 100644 (file)
index d62f55c..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.argeo.util;
-
-public class ThroughputTest {
-       public void testParse() throws Exception {
-//             assert 0 == 1;
-
-               Throughput t;
-               t = new Throughput("3.54/s");
-               assert 3.54d == t.getValue();
-               assert Throughput.Unit.s.equals(t.getUnit());
-               assert 282l == (long) t.asMsPeriod();
-
-               t = new Throughput("35698.2569/h");
-               assert Throughput.Unit.h.equals(t.getUnit());
-               assert 101l == (long) t.asMsPeriod();
-       }
-}
diff --git a/org.argeo.enterprise/pom.xml b/org.argeo.enterprise/pom.xml
deleted file mode 100644 (file)
index fef4084..0000000
+++ /dev/null
@@ -1,12 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.enterprise</artifactId>
-       <name>Commons Enterprise</name>
-</project>
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/ident/IdentClient.java b/org.argeo.enterprise/src/org/argeo/ident/IdentClient.java
deleted file mode 100644 (file)
index c42da97..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.argeo.ident;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.ConnectException;
-import java.net.Socket;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/**
- * A simple ident client, supporting authd OpenSSL encrypted username.
- * 
- * @see RFC 1413 https://tools.ietf.org/html/rfc1413
- */
-public class IdentClient {
-       public final static int DEFAULT_IDENT_PORT = 113;
-       public final static String AUTHD_PASSPHRASE_PATH = "/etc/ident.key";
-       final static String NO_USER = "NO-USER";
-
-       private final String host;
-       private final int port;
-
-       private OpenSslDecryptor openSslDecryptor = new OpenSslDecryptor();
-       private String identPassphrase = null;
-
-       public IdentClient(String host) {
-               this(host, readPassphrase(AUTHD_PASSPHRASE_PATH), DEFAULT_IDENT_PORT);
-       }
-
-       public IdentClient(String host, Path passPhrasePath) {
-               this(host, readPassphrase(passPhrasePath), DEFAULT_IDENT_PORT);
-       }
-
-       public IdentClient(String host, String identPassphrase) {
-               this(host, identPassphrase, DEFAULT_IDENT_PORT);
-       }
-
-       public IdentClient(String host, String identPassphrase, int port) {
-               this.host = host;
-               this.identPassphrase = identPassphrase;
-               this.port = port;
-       }
-
-       /** @return the username or null if none */
-       public String getUsername(int serverPort, int clientPort) {
-               String answer;
-               try (Socket socket = new Socket(host, port)) {
-                       String msg = clientPort + "," + serverPort + "\n";
-                       OutputStream out = socket.getOutputStream();
-                       out.write(msg.getBytes(StandardCharsets.US_ASCII));
-                       out.flush();
-                       BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-                       answer = reader.readLine();
-               } catch (ConnectException e) {
-                       System.err.println(
-                                       "Ident client is configured but no ident server available on " + host + " (port " + port + ")");
-                       return null;
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot read from ident server on " + host + " (port " + port + ")", e);
-               }
-               StringTokenizer st = new StringTokenizer(answer, " :\n");
-               String username = null;
-               while (st.hasMoreTokens())
-                       username = st.nextToken();
-
-               if (username.equals(NO_USER))
-                       return null;
-
-               if (identPassphrase != null && username.startsWith("[")) {
-                       String encrypted = username.substring(1, username.length() - 1);
-                       username = openSslDecryptor.decryptAuthd(encrypted, identPassphrase).trim();
-               }
-//             System.out.println(username);
-               return username;
-       }
-
-       public void setOpenSslDecryptor(OpenSslDecryptor openSslDecryptor) {
-               this.openSslDecryptor = openSslDecryptor;
-       }
-
-       public static String readPassphrase(String filePath) {
-               return readPassphrase(Paths.get(filePath));
-       }
-
-       /** @return the first line of the file. */
-       public static String readPassphrase(Path path) {
-               if (!isPathAvailable(path))
-                       return null;
-               List<String> lines;
-               try {
-                       lines = Files.readAllLines(path);
-               } catch (IOException e) {
-                       throw new IllegalStateException("Cannot read " + path, e);
-               }
-               if (lines.size() == 0)
-                       return null;
-               String passphrase = lines.get(0);
-               return passphrase;
-       }
-
-       public static boolean isDefaultAuthdPassphraseFileAvailable() {
-               return isPathAvailable(Paths.get(AUTHD_PASSPHRASE_PATH));
-       }
-
-       public static boolean isPathAvailable(Path path) {
-               if (!Files.exists(path))
-                       return false;
-               if (!Files.isReadable(path))
-                       return false;
-               return true;
-       }
-
-       public static void main(String[] args) {
-               IdentClient identClient = new IdentClient("127.0.0.1", "changeit");
-               String username = identClient.getUsername(7070, 55958);
-               System.out.println(username);
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/ident/OpenSslDecryptor.java b/org.argeo.enterprise/src/org/argeo/ident/OpenSslDecryptor.java
deleted file mode 100644 (file)
index 702b09b..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-package org.argeo.ident;
-
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import java.util.Base64;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * Decrypts OpenSSL encrypted data.
- * 
- * From
- * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
- * 
- * See also
- * https://stackoverflow.com/questions/54171959/badpadding-exception-when-trying-to-decrypt-aes-based-encrypted-text/54173509#54173509
- * for new default message digest (not yet in CentOS 7 as of July 2019)
- */
-public class OpenSslDecryptor {
-       private static final int INDEX_KEY = 0;
-       private static final int INDEX_IV = 1;
-       private static final int ITERATIONS = 1;
-
-       private static final int SALT_OFFSET = 8;
-       private static final int SALT_SIZE = 8;
-       private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
-
-       /** In bits. */
-       private final int keySize;
-
-       private Cipher cipher;
-       private MessageDigest messageDigest;
-
-       public OpenSslDecryptor() {
-               /*
-                * Changed to SHA-256 from OpenSSL v1.1.0 (see
-                * https://stackoverflow.com/questions/39637388/encryption-decryption-doesnt-
-                * work-well-between-two-different-openssl-versions)
-                */
-               this(128, "MD5");
-       }
-
-       public OpenSslDecryptor(int keySize, String messageDigest) {
-               this.keySize = keySize;
-               try {
-                       this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-                       this.messageDigest = MessageDigest.getInstance(messageDigest);
-               } catch (GeneralSecurityException e) {
-                       throw new IllegalArgumentException("Cannot initialise decryptor", e);
-               }
-       }
-
-       public String decryptAuthd(String dataBase64, String passphrase) {
-               try {
-                       byte[] headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64);
-
-                       boolean withSalt = true;
-                       byte[] salt = withSalt ? Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
-                                       : null;
-                       byte[] encrypted = withSalt
-                                       ? Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length)
-                                       : headerSaltAndCipherText;
-
-                       final byte[][] keyAndIV = EVP_BytesToKey(keySize / Byte.SIZE, cipher.getBlockSize(), messageDigest, salt,
-                                       passphrase.getBytes(StandardCharsets.US_ASCII), ITERATIONS);
-                       SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
-                       IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
-
-                       cipher.init(Cipher.DECRYPT_MODE, key, iv);
-                       byte[] decrypted = cipher.doFinal(encrypted);
-
-                       String answer = new String(decrypted, StandardCharsets.US_ASCII);
-                       return answer;
-               } catch (BadPaddingException e) {
-                       throw new IllegalStateException("Bad password, algorithm, mode or padding;"
-                                       + " no salt, wrong number of iterations or corrupted ciphertext.", e);
-               } catch (IllegalBlockSizeException e) {
-                       throw new IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.", e);
-               } catch (GeneralSecurityException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       private static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data,
-                       int count) {
-               byte[][] both = new byte[2][];
-               byte[] key = new byte[key_len];
-               int key_ix = 0;
-               byte[] iv = new byte[iv_len];
-               int iv_ix = 0;
-               both[0] = key;
-               both[1] = iv;
-               byte[] md_buf = null;
-               int nkey = key_len;
-               int niv = iv_len;
-               int i = 0;
-               if (data == null) {
-                       return both;
-               }
-               int addmd = 0;
-               for (;;) {
-                       md.reset();
-                       if (addmd++ > 0) {
-                               md.update(md_buf);
-                       }
-                       md.update(data);
-                       if (null != salt) {
-                               md.update(salt, 0, 8);
-                       }
-                       md_buf = md.digest();
-                       for (i = 1; i < count; i++) {
-                               md.reset();
-                               md.update(md_buf);
-                               md_buf = md.digest();
-                       }
-                       i = 0;
-                       if (nkey > 0) {
-                               for (;;) {
-                                       if (nkey == 0)
-                                               break;
-                                       if (i == md_buf.length)
-                                               break;
-                                       key[key_ix++] = md_buf[i];
-                                       nkey--;
-                                       i++;
-                               }
-                       }
-                       if (niv > 0 && i != md_buf.length) {
-                               for (;;) {
-                                       if (niv == 0)
-                                               break;
-                                       if (i == md_buf.length)
-                                               break;
-                                       iv[iv_ix++] = md_buf[i];
-                                       niv--;
-                                       i++;
-                               }
-                       }
-                       if (nkey == 0 && niv == 0) {
-                               break;
-                       }
-               }
-               for (i = 0; i < md_buf.length; i++) {
-                       md_buf[i] = 0;
-               }
-               return both;
-       }
-
-       public static void main(String[] args) {
-               String dataBase64 = args[0];
-               String passphrase = args[1];
-               OpenSslDecryptor decryptor = new OpenSslDecryptor();
-               System.out.println(decryptor.decryptAuthd(dataBase64, passphrase));
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/ident/package-info.java b/org.argeo.enterprise/src/org/argeo/ident/package-info.java
deleted file mode 100644 (file)
index 35dd1a2..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Ident authentication protocol support. */
-package org.argeo.ident;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/naming/AttributesDictionary.java b/org.argeo.enterprise/src/org/argeo/naming/AttributesDictionary.java
deleted file mode 100644 (file)
index e047216..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-package org.argeo.naming;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-
-public class AttributesDictionary extends Dictionary<String, Object> {
-       private final Attributes attributes;
-
-       /** The provided attributes is wrapped, not copied. */
-       public AttributesDictionary(Attributes attributes) {
-               if (attributes == null)
-                       throw new IllegalArgumentException("Attributes cannot be null");
-               this.attributes = attributes;
-       }
-
-       @Override
-       public int size() {
-               return attributes.size();
-       }
-
-       @Override
-       public boolean isEmpty() {
-               return attributes.size() == 0;
-       }
-
-       @Override
-       public Enumeration<String> keys() {
-               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
-               return new Enumeration<String>() {
-
-                       @Override
-                       public boolean hasMoreElements() {
-                               return namingEnumeration.hasMoreElements();
-                       }
-
-                       @Override
-                       public String nextElement() {
-                               return namingEnumeration.nextElement();
-                       }
-
-               };
-       }
-
-       @Override
-       public Enumeration<Object> elements() {
-               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
-               return new Enumeration<Object>() {
-
-                       @Override
-                       public boolean hasMoreElements() {
-                               return namingEnumeration.hasMoreElements();
-                       }
-
-                       @Override
-                       public Object nextElement() {
-                               String key = namingEnumeration.nextElement();
-                               return get(key);
-                       }
-
-               };
-       }
-
-       @Override
-       /** @returns a <code>String</code> or <code>String[]</code> */
-       public Object get(Object key) {
-               try {
-                       if (key == null)
-                               throw new IllegalArgumentException("Key cannot be null");
-                       Attribute attr = attributes.get(key.toString());
-                       if (attr == null)
-                               return null;
-                       if (attr.size() == 0)
-                               throw new IllegalStateException("There must be at least one value");
-                       else if (attr.size() == 1) {
-                               return attr.get().toString();
-                       } else {// multiple
-                               String[] res = new String[attr.size()];
-                               for (int i = 0; i < attr.size(); i++) {
-                                       Object value = attr.get();
-                                       if (value == null)
-                                               throw new RuntimeException("Values cannot be null");
-                                       res[i] = attr.get(i).toString();
-                               }
-                               return res;
-                       }
-               } catch (NamingException e) {
-                       throw new RuntimeException("Cannot get value for " + key, e);
-               }
-       }
-
-       @Override
-       public Object put(String key, Object value) {
-               if (key == null)
-                       throw new IllegalArgumentException("Key cannot be null");
-               if (value == null)
-                       throw new IllegalArgumentException("Value cannot be null");
-
-               Object oldValue = get(key);
-               Attribute attr = attributes.get(key);
-               if (attr == null) {
-                       attr = new BasicAttribute(key);
-                       attributes.put(attr);
-               }
-
-               if (value instanceof String[]) {
-                       String[] values = (String[]) value;
-                       // clean additional values
-                       for (int i = values.length; i < attr.size(); i++)
-                               attr.remove(i);
-                       // set values
-                       for (int i = 0; i < values.length; i++) {
-                               attr.set(i, values[i]);
-                       }
-               } else {
-                       if (attr.size() > 1)
-                               throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
-                       if (attr.size() == 1) {
-                               try {
-                                       if (!attr.get(0).equals(value))
-                                               attr.set(0, value.toString());
-                               } catch (NamingException e) {
-                                       throw new RuntimeException("Cannot check existing value", e);
-                               }
-                       } else {
-                               attr.add(value.toString());
-                       }
-               }
-               return oldValue;
-       }
-
-       @Override
-       public Object remove(Object key) {
-               if (key == null)
-                       throw new IllegalArgumentException("Key cannot be null");
-               Object oldValue = get(key);
-               if (oldValue == null)
-                       return null;
-               return attributes.remove(key.toString());
-       }
-
-       /**
-        * Copy the <b>content</b> of an {@link Attributes} to the provided
-        * {@link Dictionary}.
-        */
-       public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
-               AttributesDictionary ad = new AttributesDictionary(attributes);
-               Enumeration<String> keys = ad.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       dictionary.put(key, ad.get(key));
-               }
-       }
-
-       /**
-        * Copy a {@link Dictionary} into an {@link Attributes}.
-        */
-       public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
-               AttributesDictionary ad = new AttributesDictionary(attributes);
-               Enumeration<String> keys = dictionary.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       ad.put(key, dictionary.get(key));
-               }
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/AuthPassword.java b/org.argeo.enterprise/src/org/argeo/naming/AuthPassword.java
deleted file mode 100644 (file)
index 6d4c62b..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-package org.argeo.naming;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.StringTokenizer;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** LDAP authPassword field according to RFC 3112 */
-public class AuthPassword implements CallbackHandler {
-       private final String authScheme;
-       private final String authInfo;
-       private final String authValue;
-
-       public AuthPassword(String value) {
-               StringTokenizer st = new StringTokenizer(value, "$");
-               // TODO make it more robust, deal with bad formatting
-               this.authScheme = st.nextToken().trim();
-               this.authInfo = st.nextToken().trim();
-               this.authValue = st.nextToken().trim();
-
-               String expectedAuthScheme = getExpectedAuthScheme();
-               if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
-                       throw new IllegalArgumentException(
-                                       "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
-       }
-
-       protected AuthPassword(String authInfo, String authValue) {
-               this.authScheme = getExpectedAuthScheme();
-               if (authScheme == null)
-                       throw new IllegalArgumentException("Expected auth scheme cannot be null");
-               this.authInfo = authInfo;
-               this.authValue = authValue;
-       }
-
-       protected AuthPassword(AuthPassword authPassword) {
-               this.authScheme = authPassword.getAuthScheme();
-               this.authInfo = authPassword.getAuthInfo();
-               this.authValue = authPassword.getAuthValue();
-       }
-
-       protected String getExpectedAuthScheme() {
-               return null;
-       }
-
-       protected boolean matchAuthValue(Object object) {
-               return authValue.equals(object.toString());
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof AuthPassword))
-                       return false;
-               AuthPassword authPassword = (AuthPassword) obj;
-               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
-                               && authValue.equals(authValue);
-       }
-
-       public boolean keyEquals(AuthPassword authPassword) {
-               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
-       }
-
-       @Override
-       public int hashCode() {
-               return authValue.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return toAuthPassword();
-       }
-
-       public final String toAuthPassword() {
-               return getAuthScheme() + '$' + authInfo + '$' + authValue;
-       }
-
-       public String getAuthScheme() {
-               return authScheme;
-       }
-
-       public String getAuthInfo() {
-               return authInfo;
-       }
-
-       public String getAuthValue() {
-               return authValue;
-       }
-
-       public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
-               try {
-                       Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
-                       if (authPassword != null) {
-                               NamingEnumeration<?> values = authPassword.getAll();
-                               while (values.hasMore()) {
-                                       Object val = values.next();
-                                       AuthPassword token = new AuthPassword(val.toString());
-                                       String auth;
-                                       if (Arrays.binarySearch(value, '$') >= 0) {
-                                               auth = token.authInfo + '$' + token.authValue;
-                                       } else {
-                                               auth = token.authValue;
-                                       }
-                                       if (Arrays.equals(auth.toCharArray(), value))
-                                               return token;
-                                       // if (token.matchAuthValue(value))
-                                       // return token;
-                               }
-                       }
-                       return null;
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot check attribute", e);
-               }
-       }
-
-       public static boolean remove(Attributes attributes, AuthPassword value) {
-               Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
-               return authPassword.remove(value.toAuthPassword());
-       }
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               for (Callback callback : callbacks) {
-                       if (callback instanceof NameCallback)
-                               ((NameCallback) callback).setName(toAuthPassword());
-                       else if (callback instanceof PasswordCallback)
-                               ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
-               }
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/Distinguished.java b/org.argeo.enterprise/src/org/argeo/naming/Distinguished.java
deleted file mode 100644 (file)
index 8b9c4b9..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.naming;
-
-import java.util.EnumSet;
-import java.util.Set;
-import java.util.TreeSet;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/**
- * An object that can be identified with an X.500 distinguished name.
- * 
- * @see https://tools.ietf.org/html/rfc1779
- */
-public interface Distinguished {
-       /** The related distinguished name. */
-       String dn();
-
-       /** The related distinguished name as an {@link LdapName}. */
-       default LdapName distinguishedName() {
-               try {
-                       return new LdapName(dn());
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
-               }
-       }
-
-       /** List all DNs of an enumeration as strings. */
-       static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
-               Set<String> res = new TreeSet<>();
-               for (Enum<? extends Distinguished> enm : enumSet) {
-                       res.add(((Distinguished) enm).dn());
-               }
-               return res;
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java
deleted file mode 100644 (file)
index d9358c0..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-package org.argeo.naming;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import javax.naming.Binding;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-
-public class DnsBrowser implements Closeable {
-       private final DirContext initialCtx;
-
-       public DnsBrowser() throws NamingException {
-               this(null);
-       }
-
-       public DnsBrowser(String dnsServerUrls) throws NamingException {
-               Hashtable<String, Object> env = new Hashtable<>();
-               env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
-               if (dnsServerUrls != null)
-                       env.put("java.naming.provider.url", dnsServerUrls);
-               initialCtx = new InitialDirContext(env);
-       }
-
-       public Map<String, List<String>> getAllRecords(String name) throws NamingException {
-               Map<String, List<String>> res = new TreeMap<>();
-               Attributes attrs = initialCtx.getAttributes(name);
-               NamingEnumeration<String> ids = attrs.getIDs();
-               while (ids.hasMore()) {
-                       String recordType = ids.next();
-                       List<String> lst = new ArrayList<String>();
-                       res.put(recordType, lst);
-                       Attribute attr = attrs.get(recordType);
-                       addValues(attr, lst);
-               }
-               return Collections.unmodifiableMap(res);
-       }
-
-       /**
-        * Return a single record (typically A, AAAA, etc. or null if not available.
-        * Will fail if multiple records.
-        */
-       public String getRecord(String name, String recordType) throws NamingException {
-               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
-               if (attrs.size() == 0)
-                       return null;
-               Attribute attr = attrs.get(recordType);
-               if (attr.size() > 1)
-                       throw new IllegalArgumentException("Multiple record type " + recordType);
-               assert attr.size() != 0;
-               Object value = attr.get();
-               assert value != null;
-               return value.toString();
-       }
-
-       /**
-        * Return records of a given type.
-        */
-       public List<String> getRecords(String name, String recordType) throws NamingException {
-               List<String> res = new ArrayList<String>();
-               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
-               Attribute attr = attrs.get(recordType);
-               addValues(attr, res);
-               return res;
-       }
-
-       /** Ordered, with preferred first. */
-       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
-               List<String> raw = getRecords(name, "SRV");
-               if (raw.size() == 0)
-                       return null;
-               SortedSet<SrvRecord> res = new TreeSet<>();
-               for (int i = 0; i < raw.size(); i++) {
-                       String record = raw.get(i);
-                       String[] arr = record.split(" ");
-                       Integer priority = Integer.parseInt(arr[0]);
-                       Integer weight = Integer.parseInt(arr[1]);
-                       Integer port = Integer.parseInt(arr[2]);
-                       String hostname = arr[3];
-                       SrvRecord order = new SrvRecord(priority, weight, port, hostname);
-                       res.add(order);
-               }
-               List<String> lst = new ArrayList<>();
-               for (SrvRecord order : res) {
-                       lst.add(order.toHost(withPort));
-               }
-               return Collections.unmodifiableList(lst);
-       }
-
-       private void addValues(Attribute attr, List<String> lst) throws NamingException {
-               NamingEnumeration<?> values = attr.getAll();
-               while (values.hasMore()) {
-                       Object value = values.next();
-                       if (value != null) {
-                               if (value instanceof byte[]) {
-                                       String str = Base64.getEncoder().encodeToString((byte[]) value);
-                                       lst.add(str);
-                               } else
-                                       lst.add(value.toString());
-                       }
-               }
-
-       }
-
-       public List<String> listEntries(String name) throws NamingException {
-               List<String> res = new ArrayList<String>();
-               NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
-               while (ne.hasMore()) {
-                       Binding b = ne.next();
-                       res.add(b.getName());
-               }
-               return Collections.unmodifiableList(res);
-       }
-
-       @Override
-       public void close() throws IOException {
-               destroy();
-       }
-
-       public void destroy() {
-               try {
-                       initialCtx.close();
-               } catch (NamingException e) {
-                       // silent
-               }
-       }
-
-       public static void main(String[] args) {
-               if (args.length == 0) {
-                       printUsage(System.err);
-                       System.exit(1);
-               }
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       String hostname = args[0];
-                       String recordType = args.length > 1 ? args[1] : "A";
-                       if (recordType.equals("*")) {
-                               Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
-                               for (String type : records.keySet()) {
-                                       for (String record : records.get(type)) {
-                                               String typeLabel;
-                                               if ("44".equals(type))
-                                                       typeLabel = "SSHFP";
-                                               else if ("46".equals(type))
-                                                       typeLabel = "RRSIG";
-                                               else if ("48".equals(type))
-                                                       typeLabel = "DNSKEY";
-                                               else
-                                                       typeLabel = type;
-                                               System.out.println(typeLabel + "\t" + record);
-                                       }
-                               }
-                       } else {
-                               System.out.println(dnsBrowser.getRecord(hostname, recordType));
-                       }
-
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-       public static void printUsage(PrintStream out) {
-               out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
-       }
-
-}
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.csv b/org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.csv
deleted file mode 100644 (file)
index 676d727..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-uid,,,0.9.2342.19200300.100.1.1,,RFC 4519
-mail,,,0.9.2342.19200300.100.1.3,,RFC 4524
-info,,,0.9.2342.19200300.100.1.4,,RFC 4524
-drink,,,0.9.2342.19200300.100.1.5,,RFC 4524
-roomNumber,,,0.9.2342.19200300.100.1.6,,RFC 4524
-photo,,,0.9.2342.19200300.100.1.7,,RFC 2798
-userClass,,,0.9.2342.19200300.100.1.8,,RFC 4524
-host,,,0.9.2342.19200300.100.1.9,,RFC 4524
-manager,,,0.9.2342.19200300.100.1.10,,RFC 4524
-documentIdentifier,,,0.9.2342.19200300.100.1.11,,RFC 4524
-documentTitle,,,0.9.2342.19200300.100.1.12,,RFC 4524
-documentVersion,,,0.9.2342.19200300.100.1.13,,RFC 4524
-documentAuthor,,,0.9.2342.19200300.100.1.14,,RFC 4524
-documentLocation,,,0.9.2342.19200300.100.1.15,,RFC 4524
-homePhone,,,0.9.2342.19200300.100.1.20,,RFC 4524
-secretary,,,0.9.2342.19200300.100.1.21,,RFC 4524
-dc,,,0.9.2342.19200300.100.1.25,,RFC 4519
-associatedDomain,,,0.9.2342.19200300.100.1.37,,RFC 4524
-associatedName,,,0.9.2342.19200300.100.1.38,,RFC 4524
-homePostalAddress,,,0.9.2342.19200300.100.1.39,,RFC 4524
-personalTitle,,,0.9.2342.19200300.100.1.40,,RFC 4524
-mobile,,,0.9.2342.19200300.100.1.41,,RFC 4524
-pager,,,0.9.2342.19200300.100.1.42,,RFC 4524
-co,,,0.9.2342.19200300.100.1.43,,RFC 4524
-uniqueIdentifier,,,0.9.2342.19200300.100.1.44,,RFC 4524
-organizationalStatus,,,0.9.2342.19200300.100.1.45,,RFC 4524
-buildingName,,,0.9.2342.19200300.100.1.48,,RFC 4524
-audio,,,0.9.2342.19200300.100.1.55,,RFC 2798
-documentPublisher,,,0.9.2342.19200300.100.1.56,,RFC 4524
-jpegPhoto,,,0.9.2342.19200300.100.1.60,,RFC 2798
-vendorName,,,1.3.6.1.1.4,,RFC 3045
-vendorVersion,,,1.3.6.1.1.5,,RFC 3045
-entryUUID,,,1.3.6.1.1.16.4,,RFC 4530
-entryDN,,,1.3.6.1.1.20,,RFC 5020
-labeledURI,,,1.3.6.1.4.1.250.1.57,,RFC 2798
-numSubordinates,,,1.3.6.1.4.1.453.16.2.103,,draft-ietf-boreham-numsubordinates
-namingContexts,,,1.3.6.1.4.1.1466.101.120.5,,RFC 4512
-altServer,,,1.3.6.1.4.1.1466.101.120.6,,RFC 4512
-supportedExtension,,,1.3.6.1.4.1.1466.101.120.7,,RFC 4512
-supportedControl,,,1.3.6.1.4.1.1466.101.120.13,,RFC 4512
-supportedSASLMechanisms,,,1.3.6.1.4.1.1466.101.120.14,,RFC 4512
-supportedLDAPVersion,,,1.3.6.1.4.1.1466.101.120.15,,RFC 4512
-ldapSyntaxes,,,1.3.6.1.4.1.1466.101.120.16,,RFC 4512
-supportedAuthPasswordSchemes,,,1.3.6.1.4.1.4203.1.3.3,,RFC 3112
-authPassword,,,1.3.6.1.4.1.4203.1.3.4,,RFC 3112
-supportedFeatures,,,1.3.6.1.4.1.4203.1.3.5,,RFC 4512
-inheritable,,,1.3.6.1.4.1.7628.5.4.1,,draft-ietf-ldup-subentry
-blockInheritance,,,1.3.6.1.4.1.7628.5.4.2,,draft-ietf-ldup-subentry
-objectClass,,,2.5.4.0,,RFC 4512
-aliasedObjectName,,,2.5.4.1,,RFC 4512
-cn,,,2.5.4.3,,RFC 4519
-sn,,,2.5.4.4,,RFC 4519
-serialNumber,,,2.5.4.5,,RFC 4519
-c,,,2.5.4.6,,RFC 4519
-l,,,2.5.4.7,,RFC 4519
-st,,,2.5.4.8,,RFC 4519
-street,,,2.5.4.9,,RFC 4519
-o,,,2.5.4.10,,RFC 4519
-ou,,,2.5.4.11,,RFC 4519
-title,,,2.5.4.12,,RFC 4519
-description,,,2.5.4.13,,RFC 4519
-searchGuide,,,2.5.4.14,,RFC 4519
-businessCategory,,,2.5.4.15,,RFC 4519
-postalAddress,,,2.5.4.16,,RFC 4519
-postalCode,,,2.5.4.17,,RFC 4519
-postOfficeBox,,,2.5.4.18,,RFC 4519
-physicalDeliveryOfficeName,,,2.5.4.19,,RFC 4519
-telephoneNumber,,,2.5.4.20,,RFC 4519
-telexNumber,,,2.5.4.21,,RFC 4519
-teletexTerminalIdentifier,,,2.5.4.22,,RFC 4519
-facsimileTelephoneNumber,,,2.5.4.23,,RFC 4519
-x121Address,,,2.5.4.24,,RFC 4519
-internationalISDNNumber,,,2.5.4.25,,RFC 4519
-registeredAddress,,,2.5.4.26,,RFC 4519
-destinationIndicator,,,2.5.4.27,,RFC 4519
-preferredDeliveryMethod,,,2.5.4.28,,RFC 4519
-member,,,2.5.4.31,,RFC 4519
-owner,,,2.5.4.32,,RFC 4519
-roleOccupant,,,2.5.4.33,,RFC 4519
-seeAlso,,,2.5.4.34,,RFC 4519
-userPassword,,,2.5.4.35,,RFC 4519
-userCertificate,,,2.5.4.36,,RFC 4523
-cACertificate,,,2.5.4.37,,RFC 4523
-authorityRevocationList,,,2.5.4.38,,RFC 4523
-certificateRevocationList,,,2.5.4.39,,RFC 4523
-crossCertificatePair,,,2.5.4.40,,RFC 4523
-name,,,2.5.4.41,,RFC 4519
-givenName,,,2.5.4.42,,RFC 4519
-initials,,,2.5.4.43,,RFC 4519
-generationQualifier,,,2.5.4.44,,RFC 4519
-x500UniqueIdentifier,,,2.5.4.45,,RFC 4519
-dnQualifier,,,2.5.4.46,,RFC 4519
-enhancedSearchGuide,,,2.5.4.47,,RFC 4519
-distinguishedName,,,2.5.4.49,,RFC 4519
-uniqueMember,,,2.5.4.50,,RFC 4519
-houseIdentifier,,,2.5.4.51,,RFC 4519
-supportedAlgorithms,,,2.5.4.52,,RFC 4523
-deltaRevocationList,,,2.5.4.53,,RFC 4523
-createTimestamp,,,2.5.18.1,,RFC 4512
-modifyTimestamp,,,2.5.18.2,,RFC 4512
-creatorsName,,,2.5.18.3,,RFC 4512
-modifiersName,,,2.5.18.4,,RFC 4512
-subschemaSubentry,,,2.5.18.10,,RFC 4512
-dITStructureRules,,,2.5.21.1,,RFC 4512
-dITContentRules,,,2.5.21.2,,RFC 4512
-matchingRules,,,2.5.21.4,,RFC 4512
-attributeTypes,,,2.5.21.5,,RFC 4512
-objectClasses,,,2.5.21.6,,RFC 4512
-nameForms,,,2.5.21.7,,RFC 4512
-matchingRuleUse,,,2.5.21.8,,RFC 4512
-structuralObjectClass,,,2.5.21.9,,RFC 4512
-governingStructureRule,,,2.5.21.10,,RFC 4512
-carLicense,,,2.16.840.1.113730.3.1.1,,RFC 2798
-departmentNumber,,,2.16.840.1.113730.3.1.2,,RFC 2798
-employeeNumber,,,2.16.840.1.113730.3.1.3,,RFC 2798
-employeeType,,,2.16.840.1.113730.3.1.4,,RFC 2798
-changeNumber,,,2.16.840.1.113730.3.1.5,,draft-good-ldap-changelog
-targetDN,,,2.16.840.1.113730.3.1.6,,draft-good-ldap-changelog
-changeType,,,2.16.840.1.113730.3.1.7,,draft-good-ldap-changelog
-changes,,,2.16.840.1.113730.3.1.8,,draft-good-ldap-changelog
-newRDN,,,2.16.840.1.113730.3.1.9,,draft-good-ldap-changelog
-deleteOldRDN,,,2.16.840.1.113730.3.1.10,,draft-good-ldap-changelog
-newSuperior,,,2.16.840.1.113730.3.1.11,,draft-good-ldap-changelog
-ref,,,2.16.840.1.113730.3.1.34,,RFC 3296
-changelog,,,2.16.840.1.113730.3.1.35,,draft-good-ldap-changelog
-preferredLanguage,,,2.16.840.1.113730.3.1.39,,RFC 2798
-userSMIMECertificate,,,2.16.840.1.113730.3.1.40,,RFC 2798
-userPKCS12,,,2.16.840.1.113730.3.1.216,,RFC 2798
-displayName,,,2.16.840.1.113730.3.1.241,,RFC 2798
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.java b/org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.java
deleted file mode 100644 (file)
index cfabeb7..0000000
+++ /dev/null
@@ -1,341 +0,0 @@
-package org.argeo.naming;
-
-/**
- * Standard LDAP attributes as per:<br>
- * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
- * - <a href=
- * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
- * LDAP (partial)</a>
- */
-public enum LdapAttrs implements SpecifiedName {
-       /** */
-       uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
-       /** */
-       mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
-       /** */
-       info("0.9.2342.19200300.100.1.4", "RFC 4524"),
-       /** */
-       drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
-       /** */
-       roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
-       /** */
-       photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
-       /** */
-       userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
-       /** */
-       host("0.9.2342.19200300.100.1.9", "RFC 4524"),
-       /** */
-       manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
-       /** */
-       documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
-       /** */
-       documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
-       /** */
-       documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
-       /** */
-       documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
-       /** */
-       documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
-       /** */
-       homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
-       /** */
-       secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
-       /** */
-       dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
-       /** */
-       associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
-       /** */
-       associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
-       /** */
-       homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
-       /** */
-       personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
-       /** */
-       mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
-       /** */
-       pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
-       /** */
-       co("0.9.2342.19200300.100.1.43", "RFC 4524"),
-       /** */
-       uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
-       /** */
-       organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
-       /** */
-       buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
-       /** */
-       audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
-       /** */
-       documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
-       /** */
-       jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
-       /** */
-       vendorName("1.3.6.1.1.4", "RFC 3045"),
-       /** */
-       vendorVersion("1.3.6.1.1.5", "RFC 3045"),
-       /** */
-       entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
-       /** */
-       entryDN("1.3.6.1.1.20", "RFC 5020"),
-       /** */
-       labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
-       /** */
-       numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
-       /** */
-       namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
-       /** */
-       altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
-       /** */
-       supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
-       /** */
-       supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
-       /** */
-       supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
-       /** */
-       supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
-       /** */
-       ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
-       /** */
-       supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
-       /** */
-       authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
-       /** */
-       supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
-       /** */
-       inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
-       /** */
-       blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
-       /** */
-       objectClass("2.5.4.0", "RFC 4512"),
-       /** */
-       aliasedObjectName("2.5.4.1", "RFC 4512"),
-       /** */
-       cn("2.5.4.3", "RFC 4519"),
-       /** */
-       sn("2.5.4.4", "RFC 4519"),
-       /** */
-       serialNumber("2.5.4.5", "RFC 4519"),
-       /** */
-       c("2.5.4.6", "RFC 4519"),
-       /** */
-       l("2.5.4.7", "RFC 4519"),
-       /** */
-       st("2.5.4.8", "RFC 4519"),
-       /** */
-       street("2.5.4.9", "RFC 4519"),
-       /** */
-       o("2.5.4.10", "RFC 4519"),
-       /** */
-       ou("2.5.4.11", "RFC 4519"),
-       /** */
-       title("2.5.4.12", "RFC 4519"),
-       /** */
-       description("2.5.4.13", "RFC 4519"),
-       /** */
-       searchGuide("2.5.4.14", "RFC 4519"),
-       /** */
-       businessCategory("2.5.4.15", "RFC 4519"),
-       /** */
-       postalAddress("2.5.4.16", "RFC 4519"),
-       /** */
-       postalCode("2.5.4.17", "RFC 4519"),
-       /** */
-       postOfficeBox("2.5.4.18", "RFC 4519"),
-       /** */
-       physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
-       /** */
-       telephoneNumber("2.5.4.20", "RFC 4519"),
-       /** */
-       telexNumber("2.5.4.21", "RFC 4519"),
-       /** */
-       teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
-       /** */
-       facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
-       /** */
-       x121Address("2.5.4.24", "RFC 4519"),
-       /** */
-       internationalISDNNumber("2.5.4.25", "RFC 4519"),
-       /** */
-       registeredAddress("2.5.4.26", "RFC 4519"),
-       /** */
-       destinationIndicator("2.5.4.27", "RFC 4519"),
-       /** */
-       preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
-       /** */
-       member("2.5.4.31", "RFC 4519"),
-       /** */
-       owner("2.5.4.32", "RFC 4519"),
-       /** */
-       roleOccupant("2.5.4.33", "RFC 4519"),
-       /** */
-       seeAlso("2.5.4.34", "RFC 4519"),
-       /** */
-       userPassword("2.5.4.35", "RFC 4519"),
-       /** */
-       userCertificate("2.5.4.36", "RFC 4523"),
-       /** */
-       cACertificate("2.5.4.37", "RFC 4523"),
-       /** */
-       authorityRevocationList("2.5.4.38", "RFC 4523"),
-       /** */
-       certificateRevocationList("2.5.4.39", "RFC 4523"),
-       /** */
-       crossCertificatePair("2.5.4.40", "RFC 4523"),
-       /** */
-       name("2.5.4.41", "RFC 4519"),
-       /** */
-       givenName("2.5.4.42", "RFC 4519"),
-       /** */
-       initials("2.5.4.43", "RFC 4519"),
-       /** */
-       generationQualifier("2.5.4.44", "RFC 4519"),
-       /** */
-       x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
-       /** */
-       dnQualifier("2.5.4.46", "RFC 4519"),
-       /** */
-       enhancedSearchGuide("2.5.4.47", "RFC 4519"),
-       /** */
-       distinguishedName("2.5.4.49", "RFC 4519"),
-       /** */
-       uniqueMember("2.5.4.50", "RFC 4519"),
-       /** */
-       houseIdentifier("2.5.4.51", "RFC 4519"),
-       /** */
-       supportedAlgorithms("2.5.4.52", "RFC 4523"),
-       /** */
-       deltaRevocationList("2.5.4.53", "RFC 4523"),
-       /** */
-       createTimestamp("2.5.18.1", "RFC 4512"),
-       /** */
-       modifyTimestamp("2.5.18.2", "RFC 4512"),
-       /** */
-       creatorsName("2.5.18.3", "RFC 4512"),
-       /** */
-       modifiersName("2.5.18.4", "RFC 4512"),
-       /** */
-       subschemaSubentry("2.5.18.10", "RFC 4512"),
-       /** */
-       dITStructureRules("2.5.21.1", "RFC 4512"),
-       /** */
-       dITContentRules("2.5.21.2", "RFC 4512"),
-       /** */
-       matchingRules("2.5.21.4", "RFC 4512"),
-       /** */
-       attributeTypes("2.5.21.5", "RFC 4512"),
-       /** */
-       objectClasses("2.5.21.6", "RFC 4512"),
-       /** */
-       nameForms("2.5.21.7", "RFC 4512"),
-       /** */
-       matchingRuleUse("2.5.21.8", "RFC 4512"),
-       /** */
-       structuralObjectClass("2.5.21.9", "RFC 4512"),
-       /** */
-       governingStructureRule("2.5.21.10", "RFC 4512"),
-       /** */
-       carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
-       /** */
-       departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
-       /** */
-       employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
-       /** */
-       employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
-       /** */
-       changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
-       /** */
-       targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
-       /** */
-       changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
-       /** */
-       changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
-       /** */
-       newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
-       /** */
-       deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
-       /** */
-       newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
-       /** */
-       ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
-       /** */
-       changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
-       /** */
-       preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
-       /** */
-       userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
-       /** */
-       userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
-       /** */
-       displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
-
-       // Sun memberOf
-       memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
-
-       // KERBEROS (partial)
-       krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
-
-       // RFC 2985 and RFC 3039 (partial)
-       dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
-       /** */
-       placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
-       /** */
-       gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
-       /** */
-       countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
-       /** */
-       countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
-       //
-       ;
-
-       public final static String DN = "dn";
-
-//     private final static String LDAP_ = "ldap:";
-
-       private final String oid, spec;
-
-       LdapAttrs(String oid, String spec) {
-               this.oid = oid;
-               this.spec = spec;
-       }
-
-       @Override
-       public String getID() {
-               return oid;
-       }
-
-       @Override
-       public String getSpec() {
-               return spec;
-       }
-
-       public String getPrefix() {
-               return prefix();
-       }
-
-       public static String prefix() {
-               return "ldap";
-       }
-
-       public String property() {
-               return qualified();
-       }
-
-       public String qualified() {
-               String prefix = getPrefix();
-               return prefix != null ? prefix + ":" + name() : name();
-       }
-
-       public String getNamespace() {
-               return namespace();
-       }
-
-       public static String namespace() {
-               return "http://www.argeo.org/ns/ldap";
-       }
-
-       @Override
-       public final String toString() {
-               // must return the name
-               return name();
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdapObjs.csv b/org.argeo.enterprise/src/org/argeo/naming/LdapObjs.csv
deleted file mode 100644 (file)
index 3d907cb..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-account,,,0.9.2342.19200300.100.4.5,,RFC 4524
-document,,,0.9.2342.19200300.100.4.6,,RFC 4524
-room,,,0.9.2342.19200300.100.4.7,,RFC 4524
-documentSeries,,,0.9.2342.19200300.100.4.9,,RFC 4524
-domain,,,0.9.2342.19200300.100.4.13,,RFC 4524
-rFC822localPart,,,0.9.2342.19200300.100.4.14,,RFC 4524
-domainRelatedObject,,,0.9.2342.19200300.100.4.17,,RFC 4524
-friendlyCountry,,,0.9.2342.19200300.100.4.18,,RFC 4524
-simpleSecurityObject,,,0.9.2342.19200300.100.4.19,,RFC 4524
-uidObject,,,1.3.6.1.1.3.1,,RFC 4519
-extensibleObject,,,1.3.6.1.4.1.1466.101.120.111,,RFC 4512
-dcObject,,,1.3.6.1.4.1.1466.344,,RFC 4519
-authPasswordObject,,,1.3.6.1.4.1.4203.1.4.7,,RFC 3112
-namedObject,,,1.3.6.1.4.1.5322.13.1.1,,draft-howard-namedobject
-inheritableLDAPSubEntry,,,1.3.6.1.4.1.7628.5.6.1.1,,draft-ietf-ldup-subentry
-top,,,2.5.6.0,,RFC 4512
-alias,,,2.5.6.1,,RFC 4512
-country,,,2.5.6.2,,RFC 4519
-locality,,,2.5.6.3,,RFC 4519
-organization,,,2.5.6.4,,RFC 4519
-organizationalUnit,,,2.5.6.5,,RFC 4519
-person,,,2.5.6.6,,RFC 4519
-organizationalPerson,,,2.5.6.7,,RFC 4519
-organizationalRole,,,2.5.6.8,,RFC 4519
-groupOfNames,,,2.5.6.9,,RFC 4519
-residentialPerson,,,2.5.6.10,,RFC 4519
-applicationProcess,,,2.5.6.11,,RFC 4519
-device,,,2.5.6.14,,RFC 4519
-strongAuthenticationUser,,,2.5.6.15,,RFC 4523
-certificationAuthority,,,2.5.6.16,,RFC 4523
-certificationAuthority-V2,,,2.5.6.16.2,,RFC 4523
-groupOfUniqueNames,,,2.5.6.17,,RFC 4519
-userSecurityInformation,,,2.5.6.18,,RFC 4523
-cRLDistributionPoint,,,2.5.6.19,,RFC 4523
-pkiUser,,,2.5.6.21,,RFC 4523
-pkiCA,,,2.5.6.22,,RFC 4523
-deltaCRL,,,2.5.6.23,,RFC 4523
-subschema,,,2.5.20.1,,RFC 4512
-ldapSubEntry,,,2.16.840.1.113719.2.142.6.1.1,,draft-ietf-ldup-subentry
-changeLogEntry,,,2.16.840.1.113730.3.2.1,,draft-good-ldap-changelog
-inetOrgPerson,,,2.16.840.1.113730.3.2.2,,RFC 2798
-referral,,,2.16.840.1.113730.3.2.6,,RFC 3296
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdapObjs.java b/org.argeo.enterprise/src/org/argeo/naming/LdapObjs.java
deleted file mode 100644 (file)
index 0611675..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-package org.argeo.naming;
-
-/**
- * Standard LDAP object classes as per
- * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
- * oid-reference</a>
- */
-public enum LdapObjs implements SpecifiedName {
-       account("0.9.2342.19200300.100.4.5", "RFC 4524"),
-       /** */
-       document("0.9.2342.19200300.100.4.6", "RFC 4524"),
-       /** */
-       room("0.9.2342.19200300.100.4.7", "RFC 4524"),
-       /** */
-       documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
-       /** */
-       domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
-       /** */
-       rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
-       /** */
-       domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
-       /** */
-       friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
-       /** */
-       simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
-       /** */
-       uidObject("1.3.6.1.1.3.1", "RFC 4519"),
-       /** */
-       extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
-       /** */
-       dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
-       /** */
-       authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
-       /** */
-       namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
-       /** */
-       inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
-       /** */
-       top("2.5.6.0", "RFC 4512"),
-       /** */
-       alias("2.5.6.1", "RFC 4512"),
-       /** */
-       country("2.5.6.2", "RFC 4519"),
-       /** */
-       locality("2.5.6.3", "RFC 4519"),
-       /** */
-       organization("2.5.6.4", "RFC 4519"),
-       /** */
-       organizationalUnit("2.5.6.5", "RFC 4519"),
-       /** */
-       person("2.5.6.6", "RFC 4519"),
-       /** */
-       organizationalPerson("2.5.6.7", "RFC 4519"),
-       /** */
-       organizationalRole("2.5.6.8", "RFC 4519"),
-       /** */
-       groupOfNames("2.5.6.9", "RFC 4519"),
-       /** */
-       residentialPerson("2.5.6.10", "RFC 4519"),
-       /** */
-       applicationProcess("2.5.6.11", "RFC 4519"),
-       /** */
-       device("2.5.6.14", "RFC 4519"),
-       /** */
-       strongAuthenticationUser("2.5.6.15", "RFC 4523"),
-       /** */
-       certificationAuthority("2.5.6.16", "RFC 4523"),
-       // /** Should be certificationAuthority-V2 */
-       // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
-       // },
-       /** */
-       groupOfUniqueNames("2.5.6.17", "RFC 4519"),
-       /** */
-       userSecurityInformation("2.5.6.18", "RFC 4523"),
-       /** */
-       cRLDistributionPoint("2.5.6.19", "RFC 4523"),
-       /** */
-       pkiUser("2.5.6.21", "RFC 4523"),
-       /** */
-       pkiCA("2.5.6.22", "RFC 4523"),
-       /** */
-       deltaCRL("2.5.6.23", "RFC 4523"),
-       /** */
-       subschema("2.5.20.1", "RFC 4512"),
-       /** */
-       ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
-       /** */
-       changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
-       /** */
-       inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
-       /** */
-       referral("2.16.840.1.113730.3.2.6", "RFC 3296");
-
-       private final static String LDAP_ = "ldap:";
-       private final String oid, spec;
-
-       private LdapObjs(String oid, String spec) {
-               this.oid = oid;
-               this.spec = spec;
-       }
-
-       public String getOid() {
-               return oid;
-       }
-
-       public String getSpec() {
-               return spec;
-       }
-
-       public String property() {
-               return new StringBuilder(LDAP_).append(name()).toString();
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdifParser.java b/org.argeo.enterprise/src/org/argeo/naming/LdifParser.java
deleted file mode 100644 (file)
index cc19570..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-package org.argeo.naming;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF parser. */
-public class LdifParser {
-       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
-       protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
-                       Attributes currentAttributes) {
-               try {
-                       Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
-                       Attribute nameAttr = currentAttributes.get(nameRdn.getType());
-                       if (nameAttr == null)
-                               currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
-                       else if (!nameAttr.get().equals(nameRdn.getValue()))
-                               throw new UserDirectoryException(
-                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
-                                                               + " (shortly before line " + lineNumber + " in LDIF file)");
-                       Attributes previous = res.put(currentDn, currentAttributes);
-                       return previous;
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot add " + currentDn, e);
-               }
-       }
-
-       /** With UTF-8 charset */
-       public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
-               try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
-                       return read(reader);
-               } finally {
-                       try {
-                               in.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-       }
-
-       /** Will close the reader. */
-       public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
-               SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
-               try {
-                       List<String> lines = new ArrayList<>();
-                       try (BufferedReader br = new BufferedReader(reader)) {
-                               String line;
-                               while ((line = br.readLine()) != null) {
-                                       lines.add(line);
-                               }
-                       }
-                       if (lines.size() == 0)
-                               return res;
-                       // add an empty new line since the last line is not checked
-                       if (!lines.get(lines.size() - 1).equals(""))
-                               lines.add("");
-
-                       LdapName currentDn = null;
-                       Attributes currentAttributes = null;
-                       StringBuilder currentEntry = new StringBuilder();
-
-                       readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
-                               String line = lines.get(lineNumber);
-                               boolean isLastLine = false;
-                               if (lineNumber == lines.size() - 1)
-                                       isLastLine = true;
-                               if (line.startsWith(" ")) {
-                                       currentEntry.append(line.substring(1));
-                                       if (!isLastLine)
-                                               continue readLines;
-                               }
-
-                               if (currentEntry.length() != 0 || isLastLine) {
-                                       // read previous attribute
-                                       StringBuilder attrId = new StringBuilder(8);
-                                       boolean isBase64 = false;
-                                       readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
-                                               char c = currentEntry.charAt(i);
-                                               if (c == ':') {
-                                                       if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
-                                                               isBase64 = true;
-                                                       currentEntry.delete(0, i + (isBase64 ? 2 : 1));
-                                                       break readAttrId;
-                                               } else {
-                                                       attrId.append(c);
-                                               }
-                                       }
-
-                                       String attributeId = attrId.toString();
-                                       // TODO should we really trim the end of the string as well?
-                                       String cleanValueStr = currentEntry.toString().trim();
-                                       Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
-
-                                       // manage DN attributes
-                                       if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
-                                               if (currentDn != null) {
-                                                       //
-                                                       // ADD
-                                                       //
-                                                       Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
-                                                       if (previous != null) {
-//                                                             log.warn("There was already an entry with DN " + currentDn
-//                                                                             + ", which has been discarded by a subsequent one.");
-                                                       }
-                                               }
-
-                                               if (attributeId.equals(LdapAttrs.DN))
-                                                       try {
-                                                               currentDn = new LdapName(attributeValue.toString());
-                                                               currentAttributes = new BasicAttributes(true);
-                                                       } catch (InvalidNameException e) {
-//                                                             log.error(attributeValue + " not a valid DN, skipping the entry.");
-                                                               currentDn = null;
-                                                               currentAttributes = null;
-                                                       }
-                                       }
-
-                                       // store attribute
-                                       if (currentAttributes != null) {
-                                               Attribute attribute = currentAttributes.get(attributeId);
-                                               if (attribute == null) {
-                                                       attribute = new BasicAttribute(attributeId);
-                                                       currentAttributes.put(attribute);
-                                               }
-                                               attribute.add(attributeValue);
-                                       }
-                                       currentEntry = new StringBuilder();
-                               }
-                               currentEntry.append(line);
-                       }
-               } finally {
-                       try {
-                               reader.close();
-                       } catch (IOException e) {
-                               // silent
-                       }
-               }
-               return res;
-       }
-}
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/naming/LdifWriter.java b/org.argeo.enterprise/src/org/argeo/naming/LdifWriter.java
deleted file mode 100644 (file)
index 98d2df0..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.naming;
-
-import static org.argeo.naming.LdapAttrs.DN;
-import static org.argeo.naming.LdapAttrs.member;
-import static org.argeo.naming.LdapAttrs.objectClass;
-import static org.argeo.naming.LdapAttrs.uniqueMember;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.argeo.osgi.useradmin.UserDirectoryException;
-
-/** Basic LDIF writer */
-public class LdifWriter {
-       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-       private final Writer writer;
-
-       /** Writer must be closed by caller */
-       public LdifWriter(Writer writer) {
-               this.writer = writer;
-       }
-
-       /** Stream must be closed by caller */
-       public LdifWriter(OutputStream out) {
-               this(new OutputStreamWriter(out, DEFAULT_CHARSET));
-       }
-
-       public void writeEntry(LdapName name, Attributes attributes) throws IOException {
-               try {
-                       // check consistency
-                       Rdn nameRdn = name.getRdn(name.size() - 1);
-                       Attribute nameAttr = attributes.get(nameRdn.getType());
-                       if (!nameAttr.get().equals(nameRdn.getValue()))
-                               throw new UserDirectoryException(
-                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
-
-                       writer.append(DN + ": ").append(name.toString()).append('\n');
-                       Attribute objectClassAttr = attributes.get(objectClass.name());
-                       if (objectClassAttr != null)
-                               writeAttribute(objectClassAttr);
-                       attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
-                               Attribute attribute = attrs.next();
-                               if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
-                                       continue attributes;// skip DN attribute
-                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
-                                       continue attributes;// skip member and uniqueMember attributes, so that they are always written last
-                               writeAttribute(attribute);
-                       }
-                       // write member and uniqueMember attributes last
-                       for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
-                               Attribute attribute = attrs.next();
-                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
-                                       writeMemberAttribute(attribute);
-                       }
-                       writer.append('\n');
-                       writer.flush();
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot write LDIF", e);
-               }
-       }
-
-       public void write(Map<LdapName, Attributes> entries) throws IOException {
-               for (LdapName dn : entries.keySet())
-                       writeEntry(dn, entries.get(dn));
-       }
-
-       protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
-               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
-                       Object value = attrValues.next();
-                       if (value instanceof byte[]) {
-                               String encoded = Base64.getEncoder().encodeToString((byte[]) value);
-                               writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
-                       } else {
-                               writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
-                       }
-               }
-       }
-
-       protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
-               // Note: duplicate entries will be swallowed
-               SortedSet<String> values = new TreeSet<>();
-               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
-                       String value = attrValues.next().toString();
-                       values.add(value);
-               }
-
-               for (String value : values) {
-                       writer.append(attribute.getID()).append(": ").append(value).append('\n');
-               }
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java b/org.argeo.enterprise/src/org/argeo/naming/NamingUtils.java
deleted file mode 100644 (file)
index 5a868dd..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.argeo.naming;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.ChronoField;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-public class NamingUtils {
-       /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
-       private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
-                       .withZone(ZoneOffset.UTC);
-
-       /** @return null if not parseable */
-       public static Instant ldapDateToInstant(String ldapDate) {
-               try {
-                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
-               } catch (DateTimeParseException e) {
-                       return null;
-               }
-       }
-
-       /** @return null if not parseable */
-       public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
-               try {
-                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
-               } catch (DateTimeParseException e) {
-                       return null;
-               }
-       }
-
-       public static Calendar ldapDateToCalendar(String ldapDate) {
-               OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
-               GregorianCalendar calendar = new GregorianCalendar();
-               calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
-               calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
-               calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
-               return calendar;
-       }
-
-       public static String instantToLdapDate(ZonedDateTime instant) {
-               return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
-       }
-
-       public static String getQueryValue(Map<String, List<String>> query, String key) {
-               if (!query.containsKey(key))
-                       return null;
-               List<String> val = query.get(key);
-               if (val.size() == 1)
-                       return val.get(0);
-               else
-                       throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
-       }
-
-       public static Map<String, List<String>> queryToMap(URI uri) {
-               return queryToMap(uri.getQuery());
-       }
-
-       private static Map<String, List<String>> queryToMap(String queryPart) {
-               try {
-                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
-                       if (queryPart == null)
-                               return query_pairs;
-                       final String[] pairs = queryPart.split("&");
-                       for (String pair : pairs) {
-                               final int idx = pair.indexOf("=");
-                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
-                                               : pair;
-                               if (!query_pairs.containsKey(key)) {
-                                       query_pairs.put(key, new LinkedList<String>());
-                               }
-                               final String value = idx > 0 && pair.length() > idx + 1
-                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
-                                               : null;
-                               query_pairs.get(key).add(value);
-                       }
-                       return query_pairs;
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
-               }
-       }
-
-       private NamingUtils() {
-
-       }
-
-       public static void main(String args[]) {
-               ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
-               String str = utcLdapDate.format(now);
-               System.out.println(str);
-               utcLdapDate.parse(str);
-               utcLdapDate.parse("19520512000000Z");
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/SharedSecret.java b/org.argeo.enterprise/src/org/argeo/naming/SharedSecret.java
deleted file mode 100644 (file)
index 369b411..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.argeo.naming;
-
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-
-public class SharedSecret extends AuthPassword {
-       public final static String X_SHARED_SECRET = "X-SharedSecret";
-       private final Instant expiry;
-
-       public SharedSecret(String authInfo, String authValue) {
-               super(authInfo, authValue);
-               expiry = null;
-       }
-
-       public SharedSecret(AuthPassword authPassword) {
-               super(authPassword);
-               String authInfo = getAuthInfo();
-               if (authInfo.length() == 16) {
-                       expiry = NamingUtils.ldapDateToInstant(authInfo);
-               } else {
-                       expiry = null;
-               }
-       }
-
-       public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
-               super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
-               expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
-       }
-
-       public SharedSecret(int hours, String value) {
-               this(ZonedDateTime.now().plusHours(hours), value);
-       }
-
-       @Override
-       protected String getExpectedAuthScheme() {
-               return X_SHARED_SECRET;
-       }
-
-       public boolean isExpired() {
-               if (expiry == null)
-                       return false;
-               return expiry.isBefore(Instant.now());
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/SpecifiedName.java b/org.argeo.enterprise/src/org/argeo/naming/SpecifiedName.java
deleted file mode 100644 (file)
index 28cc2f9..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.argeo.naming;
-
-/**
- * A name which has been specified and for which an id has been defined
- * (typically an OID).
- */
-public interface SpecifiedName {
-       /** The name */
-       String name();
-
-       /** An RFC or the URLof some specification */
-       default String getSpec() {
-               return null;
-       }
-
-       /** Typically an OID */
-       default String getID() {
-               return getClass().getName() + "." + name();
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java
deleted file mode 100644 (file)
index 8ecc944..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.argeo.naming;
-
-class SrvRecord implements Comparable<SrvRecord> {
-       private final Integer priority;
-       private final Integer weight;
-       private final Integer port;
-       private final String hostname;
-
-       public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
-               this.priority = priority;
-               this.weight = weight;
-               this.port = port;
-               this.hostname = hostname;
-       }
-
-       @Override
-       public int compareTo(SrvRecord other) {
-               // https: // en.wikipedia.org/wiki/SRV_record
-               if (priority != other.priority)
-                       return priority - other.priority;
-               if (weight != other.weight)
-                       return other.weight - other.weight;
-               String host = toHost(false);
-               String otherHost = other.toHost(false);
-               if (host.length() == otherHost.length())
-                       return host.compareTo(otherHost);
-               else
-                       return host.length() - otherHost.length();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof SrvRecord) {
-                       SrvRecord other = (SrvRecord) obj;
-                       return priority == other.priority && weight == other.weight && port == other.port
-                                       && hostname.equals(other.hostname);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return priority + " " + weight;
-       }
-
-       public String toHost(boolean withPort) {
-               String hostStr = hostname;
-               if (hostname.charAt(hostname.length() - 1) == '.')
-                       hostStr = hostname.substring(0, hostname.length() - 1);
-               return hostStr + (withPort ? ":" + port : "");
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/naming/package-info.java b/org.argeo.enterprise/src/org/argeo/naming/package-info.java
deleted file mode 100644 (file)
index 95e7de3..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic naming and LDAP support. */
-package org.argeo.naming;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/internal/EnterpriseActivator.java b/org.argeo.enterprise/src/org/argeo/osgi/internal/EnterpriseActivator.java
deleted file mode 100644 (file)
index 0305482..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.osgi.internal;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-/**
- * Called to gather information about the OSGi runtime. Should not activate
- * anything else that canonical monitoring services (not creating implicit
- * APIs), which is the responsibility of higher levels..
- */
-public class EnterpriseActivator implements BundleActivator {
-
-       @Override
-       public void start(BundleContext context) throws Exception {
-       }
-
-       @Override
-       public void stop(BundleContext context) throws Exception {
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumAD.java b/org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumAD.java
deleted file mode 100644 (file)
index 44b4299..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.osgi.metatype;
-
-import org.argeo.naming.SpecifiedName;
-import org.osgi.service.metatype.AttributeDefinition;
-
-public interface EnumAD extends SpecifiedName, AttributeDefinition {
-       String name();
-
-       default Object getDefault() {
-               return null;
-       }
-
-       @Override
-       default String getName() {
-               return name();
-       }
-
-       @Override
-       default String getID() {
-               return getClass().getName() + "." + name();
-       }
-
-       @Override
-       default String getDescription() {
-               return null;
-       }
-
-       @Override
-       default int getCardinality() {
-               return 0;
-       }
-
-       @Override
-       default int getType() {
-               return STRING;
-       }
-
-       @Override
-       default String[] getOptionValues() {
-               return null;
-       }
-
-       @Override
-       default String[] getOptionLabels() {
-               return null;
-       }
-
-       @Override
-       default String validate(String value) {
-               return null;
-       }
-
-       @Override
-       default String[] getDefaultValue() {
-               Object value = getDefault();
-               if (value == null)
-                       return null;
-               return new String[] { value.toString() };
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumOCD.java b/org.argeo.enterprise/src/org/argeo/osgi/metatype/EnumOCD.java
deleted file mode 100644 (file)
index 97c7d56..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.argeo.osgi.metatype;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.osgi.service.metatype.AttributeDefinition;
-import org.osgi.service.metatype.ObjectClassDefinition;
-
-public class EnumOCD<T extends Enum<T>> implements ObjectClassDefinition {
-       private final Class<T> enumClass;
-       private String locale;
-
-       public EnumOCD(Class<T> clazz, String locale) {
-               this.enumClass = clazz;
-               this.locale = locale;
-       }
-
-       @Override
-       public String getName() {
-               return null;
-       }
-
-       public String getLocale() {
-               return locale;
-       }
-
-       @Override
-       public String getID() {
-               return enumClass.getName();
-       }
-
-       @Override
-       public String getDescription() {
-               return null;
-       }
-
-       @Override
-       public AttributeDefinition[] getAttributeDefinitions(int filter) {
-               EnumSet<T> set = EnumSet.allOf(enumClass);
-               List<AttributeDefinition> attrs = new ArrayList<>();
-               for (T key : set)
-                       attrs.add((AttributeDefinition) key);
-               return attrs.toArray(new AttributeDefinition[attrs.size()]);
-       }
-
-       @Override
-       public InputStream getIcon(int size) throws IOException {
-               return null;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/metatype/package-info.java b/org.argeo.enterprise/src/org/argeo/osgi/metatype/package-info.java
deleted file mode 100644 (file)
index bca5d1f..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** OSGi metatype support. */
-package org.argeo.osgi.metatype;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java b/org.argeo.enterprise/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java
deleted file mode 100644 (file)
index c0ec290..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-package org.argeo.osgi.provisioning;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.zip.ZipInputStream;
-
-import org.osgi.service.provisioning.ProvisioningService;
-
-public class SimpleProvisioningService implements ProvisioningService {
-       private Map<String, Object> map = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public SimpleProvisioningService() {
-               // update count
-               map.put(PROVISIONING_UPDATE_COUNT, 0);
-       }
-
-       @Override
-       public Dictionary<String, Object> getInformation() {
-               return new Information();
-       }
-
-       @Override
-       public synchronized void setInformation(Dictionary<String, ?> info) {
-               map.clear();
-               addInformation(info);
-       }
-
-       @Override
-       public synchronized void addInformation(Dictionary<String, ?> info) {
-               Enumeration<String> e = info.keys();
-               while (e.hasMoreElements()) {
-                       String key = e.nextElement();
-                       map.put(key, info.get(key));
-               }
-               incrementProvisioningUpdateCount();
-       }
-
-       protected synchronized void incrementProvisioningUpdateCount() {
-               Integer current = (Integer) map.get(PROVISIONING_UPDATE_COUNT);
-               Integer newValue = current + 1;
-               map.put(PROVISIONING_UPDATE_COUNT, newValue);
-       }
-
-       @Override
-       public synchronized void addInformation(ZipInputStream zis) throws IOException {
-               throw new UnsupportedOperationException();
-       }
-
-       class Information extends Dictionary<String, Object> {
-
-               @Override
-               public int size() {
-                       return map.size();
-               }
-
-               @Override
-               public boolean isEmpty() {
-                       return map.isEmpty();
-               }
-
-               @Override
-               public Enumeration<String> keys() {
-                       Iterator<String> it = map.keySet().iterator();
-                       return new Enumeration<String>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public String nextElement() {
-                                       return it.next();
-                               }
-
-                       };
-               }
-
-               @Override
-               public Enumeration<Object> elements() {
-                       Iterator<Object> it = map.values().iterator();
-                       return new Enumeration<Object>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public Object nextElement() {
-                                       return it.next();
-                               }
-
-                       };
-               }
-
-               @Override
-               public Object get(Object key) {
-                       return map.get(key);
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public Object remove(Object key) {
-                       throw new UnsupportedOperationException();
-               }
-
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/provisioning/package-info.java b/org.argeo.enterprise/src/org/argeo/osgi/provisioning/package-info.java
deleted file mode 100644 (file)
index 1859887..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** OSGi provisioning support. */
-package org.argeo.osgi.provisioning;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
deleted file mode 100644 (file)
index f2d7c88..0000000
+++ /dev/null
@@ -1,505 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.naming.LdapAttrs.objectClass;
-import static org.argeo.naming.LdapObjs.extensibleObject;
-import static org.argeo.naming.LdapObjs.inetOrgPerson;
-import static org.argeo.naming.LdapObjs.organizationalPerson;
-import static org.argeo.naming.LdapObjs.person;
-import static org.argeo.naming.LdapObjs.top;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-
-import org.argeo.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/** Base class for a {@link UserDirectory}. */
-public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
-       static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
-       static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
-
-       private final Hashtable<String, Object> properties;
-       private final LdapName baseDn, userBaseDn, groupBaseDn;
-       private final String userObjectClass, userBase, groupObjectClass, groupBase;
-
-       private final boolean readOnly;
-       private final boolean disabled;
-       private final String uri;
-
-       private UserAdmin externalRoles;
-       // private List<String> indexedUserProperties = Arrays
-       // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
-       // LdapAttrs.cn.name() });
-
-       private final boolean scoped;
-
-       private String memberAttributeId = "member";
-       private List<String> credentialAttributeIds = Arrays
-                       .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
-
-       // JTA
-       private TransactionManager transactionManager;
-       private WcXaResource xaResource = new WcXaResource(this);
-
-       AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
-               this.scoped = scoped;
-               properties = new Hashtable<String, Object>();
-               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                       String key = keys.nextElement();
-                       properties.put(key, props.get(key));
-               }
-
-               if (uriArg != null) {
-                       uri = uriArg.toString();
-                       // uri from properties is ignored
-               } else {
-                       String uriStr = UserAdminConf.uri.getValue(properties);
-                       if (uriStr == null)
-                               uri = null;
-                       else
-                               uri = uriStr;
-               }
-
-               userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
-               userBase = UserAdminConf.userBase.getValue(properties);
-               groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
-               groupBase = UserAdminConf.groupBase.getValue(properties);
-               try {
-                       baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
-                       userBaseDn = new LdapName(userBase + "," + baseDn);
-                       groupBaseDn = new LdapName(groupBase + "," + baseDn);
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
-               }
-               String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
-               if (readOnlyStr == null) {
-                       readOnly = readOnlyDefault(uri);
-                       properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
-               } else
-                       readOnly = Boolean.parseBoolean(readOnlyStr);
-               String disabledStr = UserAdminConf.disabled.getValue(properties);
-               if (disabledStr != null)
-                       disabled = Boolean.parseBoolean(disabledStr);
-               else
-                       disabled = false;
-       }
-
-       /** Returns the groups this user is a direct member of. */
-       protected abstract List<LdapName> getDirectGroups(LdapName dn);
-
-       protected abstract Boolean daoHasRole(LdapName dn);
-
-       protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
-
-       protected abstract List<DirectoryUser> doGetRoles(Filter f);
-
-       protected abstract AbstractUserDirectory scope(User user);
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-
-       }
-
-       protected boolean isEditing() {
-               return xaResource.wc() != null;
-       }
-
-       protected UserDirectoryWorkingCopy getWorkingCopy() {
-               UserDirectoryWorkingCopy wc = xaResource.wc();
-               if (wc == null)
-                       return null;
-               return wc;
-       }
-
-       protected void checkEdit() {
-               Transaction transaction;
-               try {
-                       transaction = transactionManager.getTransaction();
-               } catch (SystemException e) {
-                       throw new UserDirectoryException("Cannot get transaction", e);
-               }
-               if (transaction == null)
-                       throw new UserDirectoryException("A transaction needs to be active in order to edit");
-               if (xaResource.wc() == null) {
-                       try {
-                               transaction.enlistResource(xaResource);
-                       } catch (Exception e) {
-                               throw new UserDirectoryException("Cannot enlist " + xaResource, e);
-                       }
-               } else {
-               }
-       }
-
-       protected List<Role> getAllRoles(DirectoryUser user) {
-               List<Role> allRoles = new ArrayList<Role>();
-               if (user != null) {
-                       collectRoles(user, allRoles);
-                       allRoles.add(user);
-               } else
-                       collectAnonymousRoles(allRoles);
-               return allRoles;
-       }
-
-       private void collectRoles(DirectoryUser user, List<Role> allRoles) {
-               Attributes attrs = user.getAttributes();
-               // TODO centralize attribute name
-               Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
-               // if user belongs to this directory, we only check meberOf
-               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
-                       try {
-                               NamingEnumeration<?> values = memberOf.getAll();
-                               while (values.hasMore()) {
-                                       Object value = values.next();
-                                       LdapName groupDn = new LdapName(value.toString());
-                                       DirectoryUser group = doGetRole(groupDn);
-                                       if (group != null)
-                                               allRoles.add(group);
-                               }
-                       } catch (Exception e) {
-                               throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
-                       }
-               } else {
-                       for (LdapName groupDn : getDirectGroups(user.getDn())) {
-                               // TODO check for loops
-                               DirectoryUser group = doGetRole(groupDn);
-                               if (group != null) {
-                                       allRoles.add(group);
-                                       collectRoles(group, allRoles);
-                               }
-                       }
-               }
-       }
-
-       private void collectAnonymousRoles(List<Role> allRoles) {
-               // TODO gather anonymous roles
-       }
-
-       // USER ADMIN
-       @Override
-       public Role getRole(String name) {
-               return doGetRole(toDn(name));
-       }
-
-       protected DirectoryUser doGetRole(LdapName dn) {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               DirectoryUser user;
-               try {
-                       user = daoGetRole(dn);
-               } catch (NameNotFoundException e) {
-                       user = null;
-               }
-               if (wc != null) {
-                       if (user == null && wc.getNewUsers().containsKey(dn))
-                               user = wc.getNewUsers().get(dn);
-                       else if (wc.getDeletedUsers().containsKey(dn))
-                               user = null;
-               }
-               return user;
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
-               List<DirectoryUser> res = doGetRoles(f);
-               if (wc != null) {
-                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
-                               DirectoryUser user = it.next();
-                               LdapName dn = user.getDn();
-                               if (wc.getDeletedUsers().containsKey(dn))
-                                       it.remove();
-                       }
-                       for (DirectoryUser user : wc.getNewUsers().values()) {
-                               if (f == null || f.match(user.getProperties()))
-                                       res.add(user);
-                       }
-                       // no need to check modified users,
-                       // since doGetRoles was already based on the modified attributes
-               }
-               return res.toArray(new Role[res.size()]);
-       }
-
-       @Override
-       public User getUser(String key, String value) {
-               // TODO check value null or empty
-               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
-               if (key != null) {
-                       doGetUser(key, value, collectedUsers);
-               } else {
-                       throw new UserDirectoryException("Key cannot be null");
-               }
-
-               if (collectedUsers.size() == 1) {
-                       return collectedUsers.get(0);
-               } else if (collectedUsers.size() > 1) {
-                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
-                       // "") + value);
-               }
-               return null;
-       }
-
-       protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
-               try {
-                       Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
-                       List<DirectoryUser> users = doGetRoles(f);
-                       collectedUsers.addAll(users);
-               } catch (InvalidSyntaxException e) {
-                       throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
-               }
-       }
-
-       @Override
-       public Authorization getAuthorization(User user) {
-               if (user == null || user instanceof DirectoryUser) {
-                       return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
-               } else {
-                       // bind
-                       AbstractUserDirectory scopedUserAdmin = scope(user);
-                       try {
-                               DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
-                               if (directoryUser == null)
-                                       throw new UserDirectoryException("No scoped user found for " + user);
-                               LdifAuthorization authorization = new LdifAuthorization(directoryUser,
-                                               scopedUserAdmin.getAllRoles(directoryUser));
-                               return authorization;
-                       } finally {
-                               scopedUserAdmin.destroy();
-                       }
-               }
-       }
-
-       @Override
-       public Role createRole(String name, int type) {
-               checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toDn(name);
-               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
-                       throw new UserDirectoryException("Already a role " + name);
-               BasicAttributes attrs = new BasicAttributes(true);
-               // attrs.put(LdifName.dn.name(), dn.toString());
-               Rdn nameRdn = dn.getRdn(dn.size() - 1);
-               // TODO deal with multiple attr RDN
-               attrs.put(nameRdn.getType(), nameRdn.getValue());
-               if (wc.getDeletedUsers().containsKey(dn)) {
-                       wc.getDeletedUsers().remove(dn);
-                       wc.getModifiedUsers().put(dn, attrs);
-                       return getRole(name);
-               } else {
-                       wc.getModifiedUsers().put(dn, attrs);
-                       DirectoryUser newRole = newRole(dn, type, attrs);
-                       wc.getNewUsers().put(dn, newRole);
-                       return newRole;
-               }
-       }
-
-       protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
-               LdifUser newRole;
-               BasicAttribute objClass = new BasicAttribute(objectClass.name());
-               if (type == Role.USER) {
-                       String userObjClass = newUserObjectClass(dn);
-                       objClass.add(userObjClass);
-                       if (inetOrgPerson.name().equals(userObjClass)) {
-                               objClass.add(organizationalPerson.name());
-                               objClass.add(person.name());
-                       } else if (organizationalPerson.name().equals(userObjClass)) {
-                               objClass.add(person.name());
-                       }
-                       objClass.add(top.name());
-                       objClass.add(extensibleObject.name());
-                       attrs.put(objClass);
-                       newRole = new LdifUser(this, dn, attrs);
-               } else if (type == Role.GROUP) {
-                       String groupObjClass = getGroupObjectClass();
-                       objClass.add(groupObjClass);
-                       // objClass.add(LdifName.extensibleObject.name());
-                       objClass.add(top.name());
-                       attrs.put(objClass);
-                       newRole = new LdifGroup(this, dn, attrs);
-               } else
-                       throw new UserDirectoryException("Unsupported type " + type);
-               return newRole;
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               checkEdit();
-               UserDirectoryWorkingCopy wc = getWorkingCopy();
-               LdapName dn = toDn(name);
-               boolean actuallyDeleted;
-               if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
-                       DirectoryUser user = (DirectoryUser) getRole(name);
-                       wc.getDeletedUsers().put(dn, user);
-                       actuallyDeleted = true;
-               } else {// just removing from groups (e.g. system roles)
-                       actuallyDeleted = false;
-               }
-               for (LdapName groupDn : getDirectGroups(dn)) {
-                       DirectoryUser group = doGetRole(groupDn);
-                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
-               }
-               return actuallyDeleted;
-       }
-
-       // TRANSACTION
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void commit(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-
-       }
-
-       // UTILITIES
-       protected LdapName toDn(String name) {
-               try {
-                       return new LdapName(name);
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formatted name", e);
-               }
-       }
-
-       // GETTERS
-       protected String getMemberAttributeId() {
-               return memberAttributeId;
-       }
-
-       protected List<String> getCredentialAttributeIds() {
-               return credentialAttributeIds;
-       }
-
-       protected String getUri() {
-               return uri;
-       }
-
-       private static boolean readOnlyDefault(String uriStr) {
-               if (uriStr == null)
-                       return true;
-               /// TODO make it more generic
-               URI uri;
-               try {
-                       uri = new URI(uriStr.split(" ")[0]);
-               } catch (URISyntaxException e) {
-                       throw new IllegalArgumentException(e);
-               }
-               if (uri.getScheme() == null)
-                       return false;// assume relative file to be writable
-               if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
-                       File file = new File(uri);
-                       if (file.exists())
-                               return !file.canWrite();
-                       else
-                               return !file.getParentFile().canWrite();
-               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
-                       if (uri.getAuthority() != null)// assume writable if authenticated
-                               return false;
-               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
-                       return true;
-               }
-               return true;// read only by default
-       }
-
-       public boolean isReadOnly() {
-               return readOnly;
-       }
-
-       public boolean isDisabled() {
-               return disabled;
-       }
-
-       protected UserAdmin getExternalRoles() {
-               return externalRoles;
-       }
-
-       protected int roleType(LdapName dn) {
-               if (dn.startsWith(groupBaseDn))
-                       return Role.GROUP;
-               else if (dn.startsWith(userBaseDn))
-                       return Role.USER;
-               else
-                       return Role.GROUP;
-       }
-
-       /** dn can be null, in that case a default should be returned. */
-       public String getUserObjectClass() {
-               return userObjectClass;
-       }
-
-       public String getUserBase() {
-               return userBase;
-       }
-
-       protected String newUserObjectClass(LdapName dn) {
-               return getUserObjectClass();
-       }
-
-       public String getGroupObjectClass() {
-               return groupObjectClass;
-       }
-
-       public String getGroupBase() {
-               return groupBase;
-       }
-
-       public LdapName getBaseDn() {
-               return (LdapName) baseDn.clone();
-       }
-
-       public Dictionary<String, Object> getProperties() {
-               return properties;
-       }
-
-       public Dictionary<String, Object> cloneProperties() {
-               return new Hashtable<>(properties);
-       }
-
-       public void setExternalRoles(UserAdmin externalRoles) {
-               this.externalRoles = externalRoles;
-       }
-
-       public void setTransactionManager(TransactionManager transactionManager) {
-               this.transactionManager = transactionManager;
-       }
-
-       public WcXaResource getXaResource() {
-               return xaResource;
-       }
-
-       public boolean isScoped() {
-               return scoped;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java
deleted file mode 100644 (file)
index 05ba948..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.security.auth.x500.X500Principal;
-
-import org.osgi.service.useradmin.Authorization;
-
-/** An {@link Authorization} which combines roles form various auth sources. */
-class AggregatingAuthorization implements Authorization {
-       private final String name;
-       private final String displayName;
-       private final Set<String> systemRoles;
-       private final Set<String> roles;
-
-       public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
-               this.name = new X500Principal(name).getName();
-               this.displayName = displayName;
-               this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
-               Set<String> temp = new HashSet<>();
-               for (String role : roles) {
-                       if (!temp.contains(role))
-                               temp.add(role);
-               }
-               this.roles = Collections.unmodifiableSet(temp);
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               if (systemRoles.contains(name))
-                       return true;
-               if (roles.contains(name))
-                       return true;
-               return false;
-       }
-
-       @Override
-       public String[] getRoles() {
-               int size = systemRoles.size() + roles.size();
-               List<String> res = new ArrayList<String>(size);
-               res.addAll(systemRoles);
-               res.addAll(roles);
-               return res.toArray(new String[size]);
-       }
-
-       @Override
-       public int hashCode() {
-               if (name == null)
-                       return super.hashCode();
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof Authorization))
-                       return false;
-               Authorization that = (Authorization) obj;
-               if (name == null)
-                       return that.getName() == null;
-               return name.equals(that.getName());
-       }
-
-       @Override
-       public String toString() {
-               return displayName;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
deleted file mode 100644 (file)
index bee5135..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Group;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Aggregates multiple {@link UserDirectory} and integrates them with system
- * roles.
- */
-public class AggregatingUserAdmin implements UserAdmin {
-       private final LdapName systemRolesBaseDn;
-       private final LdapName tokensBaseDn;
-
-       // DAOs
-       private AbstractUserDirectory systemRoles = null;
-       private AbstractUserDirectory tokens = null;
-       private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
-
-       public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
-               try {
-                       this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
-                       if (tokensBaseDn != null)
-                               this.tokensBaseDn = new LdapName(tokensBaseDn);
-                       else
-                               this.tokensBaseDn = null;
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Cannot initialize " + AggregatingUserAdmin.class, e);
-               }
-       }
-
-       @Override
-       public Role createRole(String name, int type) {
-               return findUserAdmin(name).createRole(name, type);
-       }
-
-       @Override
-       public boolean removeRole(String name) {
-               boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
-               systemRoles.removeRole(name);
-               return actuallyDeleted;
-       }
-
-       @Override
-       public Role getRole(String name) {
-               return findUserAdmin(name).getRole(name);
-       }
-
-       @Override
-       public Role[] getRoles(String filter) throws InvalidSyntaxException {
-               List<Role> res = new ArrayList<Role>();
-               for (UserAdmin userAdmin : businessRoles.values()) {
-                       res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
-               }
-               res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
-               return res.toArray(new Role[res.size()]);
-       }
-
-       @Override
-       public User getUser(String key, String value) {
-               List<User> res = new ArrayList<User>();
-               for (UserAdmin userAdmin : businessRoles.values()) {
-                               User u = userAdmin.getUser(key, value);
-                               if (u != null)
-                                       res.add(u);
-               }
-               // Note: node roles cannot contain users, so it is not searched
-               return res.size() == 1 ? res.get(0) : null;
-       }
-
-       @Override
-       public Authorization getAuthorization(User user) {
-               if (user == null) {// anonymous
-                       return systemRoles.getAuthorization(null);
-               }
-               AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
-               Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
-               String usernameToUse;
-               String displayNameToUse;
-               if (user instanceof Group) {
-                       // TODO check whether this is still working
-                       String ownerDn = TokenUtils.userDn((Group) user);
-                       if (ownerDn != null) {// tokens
-                               UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
-                               User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
-                               usernameToUse = ownerDn;
-                               displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
-                       } else {
-                               usernameToUse = rawAuthorization.getName();
-                               displayNameToUse = rawAuthorization.toString();
-                       }
-               } else {// regular users
-                       usernameToUse = rawAuthorization.getName();
-                       displayNameToUse = rawAuthorization.toString();
-               }
-
-               // gather roles from other referentials
-               final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
-               if (user instanceof DirectoryUser) {
-                       userAdminToUse = userReferentialOfThisUser;
-               } else if (user instanceof AuthenticatingUser) {
-                       userAdminToUse = userReferentialOfThisUser.scope(user);
-               } else {
-                       throw new IllegalArgumentException("Unsupported user type " + user.getClass());
-               }
-
-               try {
-                       Set<String> sysRoles = new HashSet<String>();
-                       for (String role : rawAuthorization.getRoles()) {
-                               User userOrGroup = (User) userAdminToUse.getRole(role);
-                               Authorization auth = systemRoles.getAuthorization(userOrGroup);
-                               systemRoles: for (String systemRole : auth.getRoles()) {
-                                       if (role.equals(systemRole))
-                                               continue systemRoles;
-                                       sysRoles.add(systemRole);
-                               }
-//                     sysRoles.addAll(Arrays.asList(auth.getRoles()));
-                       }
-                       addAbstractSystemRoles(rawAuthorization, sysRoles);
-                       Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
-                                       rawAuthorization.getRoles());
-                       return authorization;
-               } finally {
-                       if (userAdminToUse != null && userAdminToUse.isScoped()) {
-                               userAdminToUse.destroy();
-                       }
-               }
-       }
-
-       /**
-        * Enrich with application-specific roles which are strictly programmatic, such
-        * as anonymous/user semantics.
-        */
-       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
-
-       }
-
-       //
-       // USER ADMIN AGGREGATOR
-       //
-       protected void addUserDirectory(AbstractUserDirectory userDirectory) {
-               LdapName baseDn = userDirectory.getBaseDn();
-               if (isSystemRolesBaseDn(baseDn)) {
-                       this.systemRoles = userDirectory;
-                       systemRoles.setExternalRoles(this);
-               } else if (isTokensBaseDn(baseDn)) {
-                       this.tokens = userDirectory;
-                       tokens.setExternalRoles(this);
-               } else {
-                       if (businessRoles.containsKey(baseDn))
-                               throw new UserDirectoryException("There is already a user admin for " + baseDn);
-                       businessRoles.put(baseDn, userDirectory);
-               }
-               userDirectory.init();
-               postAdd(userDirectory);
-       }
-
-       /** Called after a new user directory has been added */
-       protected void postAdd(AbstractUserDirectory userDirectory) {
-       }
-
-//     private UserAdmin findUserAdmin(User user) {
-//             if (user == null)
-//                     throw new IllegalArgumentException("User should not be null");
-//             AbstractUserDirectory userAdmin = findUserAdmin(user.getName());
-//             if (user instanceof DirectoryUser) {
-//                     return userAdmin;
-//             } else {
-//                     return userAdmin.scope(user);
-//             }
-//     }
-
-       private AbstractUserDirectory findUserAdmin(String name) {
-               try {
-                       return findUserAdmin(new LdapName(name));
-               } catch (InvalidNameException e) {
-                       throw new UserDirectoryException("Badly formatted name " + name, e);
-               }
-       }
-
-       private AbstractUserDirectory findUserAdmin(LdapName name) {
-               if (name.startsWith(systemRolesBaseDn))
-                       return systemRoles;
-               if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
-                       return tokens;
-               List<AbstractUserDirectory> res = new ArrayList<>(1);
-               userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(baseDn);
-                       if (name.startsWith(baseDn)) {
-                               if (userDirectory.isDisabled())
-                                       continue userDirectories;
-//                             if (res.isEmpty()) {
-                               res.add(userDirectory);
-//                             } else {
-//                                     for (AbstractUserDirectory ud : res) {
-//                                             LdapName bd = ud.getBaseDn();
-//                                             if (userDirectory.getBaseDn().startsWith(bd)) {
-//                                                     // child user directory
-//                                             }
-//                                     }
-//                             }
-                       }
-               }
-               if (res.size() == 0)
-                       throw new UserDirectoryException("Cannot find user admin for " + name);
-               if (res.size() > 1)
-                       throw new UserDirectoryException("Multiple user admin found for " + name);
-               return res.get(0);
-       }
-
-       protected boolean isSystemRolesBaseDn(LdapName baseDn) {
-               return baseDn.equals(systemRolesBaseDn);
-       }
-
-       protected boolean isTokensBaseDn(LdapName baseDn) {
-               return tokensBaseDn != null && baseDn.equals(tokensBaseDn);
-       }
-
-//     protected Dictionary<String, Object> currentState() {
-//             Dictionary<String, Object> res = new Hashtable<String, Object>();
-//             // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
-//             for (LdapName name : businessRoles.keySet()) {
-//                     AbstractUserDirectory userDirectory = businessRoles.get(name);
-//                     String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
-//                     res.put(uri, "");
-//             }
-//             return res;
-//     }
-
-       public void destroy() {
-               for (LdapName name : businessRoles.keySet()) {
-                       AbstractUserDirectory userDirectory = businessRoles.get(name);
-                       destroy(userDirectory);
-               }
-               businessRoles.clear();
-               businessRoles = null;
-               destroy(systemRoles);
-               systemRoles = null;
-       }
-
-       private void destroy(AbstractUserDirectory userDirectory) {
-               preDestroy(userDirectory);
-               userDirectory.destroy();
-       }
-
-       protected void removeUserDirectory(LdapName baseDn) {
-               if (isSystemRolesBaseDn(baseDn))
-                       throw new UserDirectoryException("System roles cannot be removed ");
-               if (!businessRoles.containsKey(baseDn))
-                       throw new UserDirectoryException("No user directory registered for " + baseDn);
-               AbstractUserDirectory userDirectory = businessRoles.remove(baseDn);
-               destroy(userDirectory);
-       }
-
-       /**
-        * Called before each user directory is destroyed, so that additional actions
-        * can be performed.
-        */
-       protected void preDestroy(AbstractUserDirectory userDirectory) {
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java
deleted file mode 100644 (file)
index 01db8be..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/**
- * A special user type used during authentication in order to provide the
- * credentials required for scoping the user admin.
- */
-public class AuthenticatingUser implements User {
-       /** From com.sun.security.auth.module.*LoginModule */
-       public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
-       /** From com.sun.security.auth.module.*LoginModule */
-       public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
-
-       private final String name;
-       private final Dictionary<String, Object> credentials;
-
-       public AuthenticatingUser(LdapName name) {
-               if (name == null)
-                       throw new NullPointerException("Provided name cannot be null.");
-               this.name = name.toString();
-               this.credentials = new Hashtable<>();
-       }
-
-       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
-               this.name = name;
-               this.credentials = credentials;
-       }
-
-       public AuthenticatingUser(String name, char[] password) {
-               if (name == null)
-                       throw new NullPointerException("Provided name cannot be null.");
-               this.name = name;
-               credentials = new Hashtable<>();
-               credentials.put(SHARED_STATE_NAME, name);
-               byte[] pwd = DigestUtils.charsToBytes(password);
-               credentials.put(SHARED_STATE_PWD, pwd);
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int getType() {
-               return User.USER;
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       @Override
-       public Dictionary getProperties() {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings({ "rawtypes", "unchecked" })
-       @Override
-       public Dictionary getCredentials() {
-               return credentials;
-       }
-
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return "Authenticating user " + name;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java
deleted file mode 100644 (file)
index 511c2fe..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.spec.KeySpec;
-import java.util.Arrays;
-
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-
-/** Utilities around digests, mostly those related to passwords. */
-class DigestUtils {
-       final static String PASSWORD_SCHEME_SHA = "SHA";
-       final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
-
-       static byte[] sha1(byte[] bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance("SHA1");
-                       digest.update(bytes);
-                       byte[] checksum = digest.digest();
-                       return checksum;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot SHA1 digest", e);
-               }
-       }
-
-       static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
-                       Integer keyLength) {
-               try {
-                       if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
-                               MessageDigest digest = MessageDigest.getInstance("SHA1");
-                               byte[] bytes = charsToBytes(password);
-                               digest.update(bytes);
-                               return digest.digest();
-                       } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
-                               KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
-
-                               SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
-                               final int ITERATION_LENGTH = 4;
-                               byte[] key = f.generateSecret(spec).getEncoded();
-                               byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
-                               byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
-                               if (iterationsArr.length < ITERATION_LENGTH) {
-                                       Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
-                                       System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
-                                                       iterationsArr.length);
-                               } else {
-                                       System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
-                               }
-                               System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
-                               System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
-                               return result;
-                       } else {
-                               throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot digest", e);
-               }
-       }
-
-       static char[] bytesToChars(Object obj) {
-               if (obj instanceof char[])
-                       return (char[]) obj;
-               if (!(obj instanceof byte[]))
-                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
-               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
-               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
-               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
-               // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
-               // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
-               // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
-               return res;
-       }
-
-       static byte[] charsToBytes(char[] chars) {
-               CharBuffer charBuffer = CharBuffer.wrap(chars);
-               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
-               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
-               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
-               // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
-               return bytes;
-       }
-
-       static String sha1str(String str) {
-               byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
-               return encodeHexString(hash);
-       }
-
-       final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
-       /**
-        * From
-        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
-        * -a-hex-string-in-java
-        */
-       public static String encodeHexString(byte[] bytes) {
-               char[] hexChars = new char[bytes.length * 2];
-               for (int j = 0; j < bytes.length; j++) {
-                       int v = bytes[j] & 0xFF;
-                       hexChars[j * 2] = hexArray[v >>> 4];
-                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
-               }
-               return new String(hexChars);
-       }
-
-       private DigestUtils() {
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryGroup.java
deleted file mode 100644 (file)
index 7f80463..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.List;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Group;
-
-/** A group in a user directroy. */
-interface DirectoryGroup extends Group, DirectoryUser {
-       List<LdapName> getMemberNames();
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/DirectoryUser.java
deleted file mode 100644 (file)
index 146b805..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/** A user in a user directory. */
-interface DirectoryUser extends User {
-       LdapName getDn();
-
-       Attributes getAttributes();
-
-       void publishAttributes(Attributes modifiedAttributes);
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java
deleted file mode 100644 (file)
index d56c06a..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.DnsBrowser;
-import org.argeo.naming.LdapAttrs;
-
-/** Free IPA specific conventions. */
-public class IpaUtils {
-       public final static String IPA_USER_BASE = "cn=users,cn=accounts";
-       public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
-       public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
-
-       private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
-
-       public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
-                       + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
-
-       @Deprecated
-       static String domainToUserDirectoryConfigPath(String realm) {
-               return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
-       }
-
-       public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
-               properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
-               properties.put(UserAdminConf.realm.name(), realm);
-               properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
-               properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
-               properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
-       }
-
-       public static String domainToBaseDn(String domain) {
-               String[] dcs = domain.split("\\.");
-               StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < dcs.length; i++) {
-                       if (i != 0)
-                               sb.append(',');
-                       String dc = dcs[i];
-                       sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
-               }
-               return sb.toString();
-       }
-
-       public static LdapName kerberosToDn(String kerberosName) {
-               String[] kname = kerberosName.split("@");
-               String username = kname[0];
-               String baseDn = domainToBaseDn(kname[1]);
-               String dn;
-               if (!username.contains("/"))
-                       dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
-               else
-                       dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
-               try {
-                       return new LdapName(dn);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
-               }
-       }
-
-       private IpaUtils() {
-
-       }
-
-       public static String kerberosDomainFromDns() {
-               String kerberosDomain;
-               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                       InetAddress localhost = InetAddress.getLocalHost();
-                       String hostname = localhost.getHostName();
-                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
-                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
-                       return kerberosDomain;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
-               }
-
-       }
-
-       public static Dictionary<String, Object> convertIpaUri(URI uri) {
-               String path = uri.getPath();
-               String kerberosRealm;
-               if (path == null || path.length() <= 1) {
-                       kerberosRealm = kerberosDomainFromDns();
-               } else {
-                       kerberosRealm = path.substring(1);
-               }
-
-               if (kerberosRealm == null)
-                       throw new UserDirectoryException("No Kerberos domain available for " + uri);
-               // TODO intergrate CA certificate in truststore
-               // String schemeToUse = SCHEME_LDAPS;
-               String schemeToUse = UserAdminConf.SCHEME_LDAP;
-               List<String> ldapHosts;
-               String ldapHostsStr = uri.getHost();
-               if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
-                       try (DnsBrowser dnsBrowser = new DnsBrowser()) {
-                               ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
-                                               schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
-                               if (ldapHosts == null || ldapHosts.size() == 0) {
-                                       throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
-                               } else {
-                                       ldapHostsStr = ldapHosts.get(0);
-                               }
-                       } catch (NamingException | IOException e) {
-                               throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
-                       }
-               } else {
-                       ldapHosts = new ArrayList<>();
-                       ldapHosts.add(ldapHostsStr);
-               }
-
-               StringBuilder uriStr = new StringBuilder();
-               try {
-                       for (String host : ldapHosts) {
-                               URI convertedUri = new URI(schemeToUse + "://" + host + "/");
-                               uriStr.append(convertedUri).append(' ');
-                       }
-               } catch (URISyntaxException e) {
-                       throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
-               }
-
-               Hashtable<String, Object> res = new Hashtable<>();
-               res.put(UserAdminConf.uri.name(), uriStr.toString());
-               addIpaConfig(kerberosRealm, res);
-               return res;
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapConnection.java
deleted file mode 100644 (file)
index 3e869f3..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.CommunicationException;
-import javax.naming.Context;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.LdapAttrs;
-
-/** A synchronized wrapper for a single {@link InitialLdapContext}. */
-// TODO implement multiple contexts and connection pooling.
-class LdapConnection {
-       private InitialLdapContext initialLdapContext = null;
-
-       LdapConnection(String url, Dictionary<String, ?> properties) {
-               try {
-                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
-                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
-                       connEnv.put(Context.PROVIDER_URL, url);
-                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
-                       // use pooling in order to avoid connection timeout
-//                     connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
-//                     connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
-
-                       initialLdapContext = new InitialLdapContext(connEnv, null);
-                       // StartTlsResponse tls = (StartTlsResponse) ctx
-                       // .extendedOperation(new StartTlsRequest());
-                       // tls.negotiate();
-                       Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
-                       if (securityAuthentication != null)
-                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
-                       else
-                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
-                       Object principal = properties.get(Context.SECURITY_PRINCIPAL);
-                       if (principal != null) {
-                               initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
-                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
-                               if (creds != null) {
-                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot connect to LDAP", e);
-               }
-
-       }
-
-       public void init() {
-
-       }
-
-       public void destroy() {
-               try {
-                       // tls.close();
-                       initialLdapContext.close();
-                       initialLdapContext = null;
-               } catch (NamingException e) {
-                       e.printStackTrace();
-               }
-       }
-
-       protected InitialLdapContext getLdapContext() {
-               return initialLdapContext;
-       }
-
-       protected void reconnect() throws NamingException {
-               initialLdapContext.reconnect(initialLdapContext.getConnectControls());
-       }
-
-       public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
-                       SearchControls searchControls) throws NamingException {
-               NamingEnumeration<SearchResult> results;
-               try {
-                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
-               } catch (CommunicationException e) {
-                       reconnect();
-                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
-               }
-               return results;
-       }
-
-       public synchronized Attributes getAttributes(LdapName name) throws NamingException {
-               try {
-                       return getLdapContext().getAttributes(name);
-               } catch (CommunicationException e) {
-                       reconnect();
-                       return getLdapContext().getAttributes(name);
-               }
-       }
-
-       synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
-               // make sure connection will work
-               reconnect();
-
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       if (!entryExists(dn))
-                               throw new UserDirectoryException("User to delete no found " + dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       if (entryExists(dn))
-                               throw new UserDirectoryException("User to create found " + dn);
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
-                               throw new UserDirectoryException("User to modify not found " + dn);
-               }
-
-       }
-
-       protected boolean entryExists(LdapName dn) throws NamingException {
-               try {
-                       return getAttributes(dn).size() != 0;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
-       synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       getLdapContext().destroySubcontext(dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
-                       getLdapContext().createSubcontext(dn, user.getAttributes());
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
-                       getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
-               }
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
deleted file mode 100644 (file)
index 2d9a874..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.naming.LdapAttrs.objectClass;
-
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.AuthenticationNotSupportedException;
-import javax.naming.Binding;
-import javax.naming.Context;
-import javax.naming.InvalidNameException;
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.LdapName;
-import javax.transaction.TransactionManager;
-
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/**
- * A user admin based on a LDAP server. Requires a {@link TransactionManager}
- * and an open transaction for write access.
- */
-public class LdapUserAdmin extends AbstractUserDirectory {
-       private LdapConnection ldapConnection;
-
-       public LdapUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-               ldapConnection = new LdapConnection(getUri().toString(), properties);
-       }
-
-       public void destroy() {
-               ldapConnection.destroy();
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DigestUtils.bytesToChars(pwd);
-                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
-               } else {
-                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
-               }
-               return new LdapUserAdmin(properties, true);
-       }
-
-//     protected InitialLdapContext getLdapContext() {
-//             return initialLdapContext;
-//     }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               try {
-                       return daoGetRole(dn) != null;
-               } catch (NameNotFoundException e) {
-                       return false;
-               }
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
-               try {
-                       Attributes attrs = ldapConnection.getAttributes(name);
-                       if (attrs.size() == 0)
-                               return null;
-                       int roleType = roleType(name);
-                       LdifUser res;
-                       if (roleType == Role.GROUP)
-                               res = new LdifGroup(this, name, attrs);
-                       else if (roleType == Role.USER)
-                               res = new LdifUser(this, name, attrs);
-                       else
-                               throw new UserDirectoryException("Unsupported LDAP type for " + name);
-                       return res;
-               } catch (NameNotFoundException e) {
-                       throw e;
-               } catch (NamingException e) {
-                       return null;
-               }
-       }
-
-       @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
-               try {
-                       String searchFilter = f != null ? f.toString()
-                                       : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
-                                                       + getGroupObjectClass() + "))";
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                       LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       results: while (results.hasMoreElements()) {
-                               SearchResult searchResult = results.next();
-                               Attributes attrs = searchResult.getAttributes();
-                               Attribute objectClassAttr = attrs.get(objectClass.name());
-                               LdapName dn = toDn(searchBase, searchResult);
-                               LdifUser role;
-                               if (objectClassAttr.contains(getGroupObjectClass())
-                                               || objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
-                                       role = new LdifGroup(this, dn, attrs);
-                               else if (objectClassAttr.contains(getUserObjectClass())
-                                               || objectClassAttr.contains(getUserObjectClass().toLowerCase()))
-                                       role = new LdifUser(this, dn, attrs);
-                               else {
-//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
-                                       continue results;
-                               }
-                               res.add(role);
-                       }
-                       return res;
-               } catch (AuthenticationNotSupportedException e) {
-                       // ignore (typically an unsupported anonymous bind)
-                       // TODO better logging
-                       return res;
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new UserDirectoryException("Cannot get roles for filter " + f, e);
-               }
-       }
-
-       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
-               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               try {
-                       String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
-                                       + "=" + dn + "))";
-
-                       SearchControls searchControls = new SearchControls();
-                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
-
-                       LdapName searchBase = getBaseDn();
-                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
-
-                       while (results.hasMoreElements()) {
-                               SearchResult searchResult = (SearchResult) results.nextElement();
-                               directGroups.add(toDn(searchBase, searchResult));
-                       }
-                       return directGroups;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot populate direct members of " + dn, e);
-               }
-       }
-
-       @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-               try {
-                       ldapConnection.prepareChanges(wc);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot prepare LDAP", e);
-               }
-       }
-
-       @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
-               try {
-                       ldapConnection.commitChanges(wc);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot commit LDAP", e);
-               }
-       }
-
-       @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-               // prepare not impacting
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifAuthorization.java
deleted file mode 100644 (file)
index 354f8c0..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.List;
-
-import org.argeo.naming.LdapAttrs;
-import org.osgi.service.useradmin.Authorization;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/** Basic authorization. */
-class LdifAuthorization implements Authorization {
-       private final String name;
-       private final String displayName;
-       private final List<String> allRoles;
-
-       public LdifAuthorization(User user, List<Role> allRoles) {
-               if (user == null) {
-                       this.name = null;
-                       this.displayName = "anonymous";
-               } else {
-                       this.name = user.getName();
-                       this.displayName = extractDisplayName(user);
-               }
-               // roles
-               String[] roles = new String[allRoles.size()];
-               for (int i = 0; i < allRoles.size(); i++) {
-                       roles[i] = allRoles.get(i).getName();
-               }
-               this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public boolean hasRole(String name) {
-               return allRoles.contains(name);
-       }
-
-       @Override
-       public String[] getRoles() {
-               return allRoles.toArray(new String[allRoles.size()]);
-       }
-
-       @Override
-       public int hashCode() {
-               if (name == null)
-                       return super.hashCode();
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (!(obj instanceof Authorization))
-                       return false;
-               Authorization that = (Authorization) obj;
-               if (name == null)
-                       return that.getName() == null;
-               return name.equals(that.getName());
-       }
-
-       @Override
-       public String toString() {
-               return displayName;
-       }
-
-       final static String extractDisplayName(User user) {
-               Dictionary<String, Object> props = user.getProperties();
-               Object displayName = props.get(LdapAttrs.displayName);
-               if (displayName == null)
-                       displayName = props.get(LdapAttrs.cn);
-               if (displayName == null)
-                       displayName = props.get(LdapAttrs.uid);
-               if (displayName == null)
-                       displayName = user.getName();
-               if (displayName == null)
-                       throw new UserDirectoryException("Cannot set display name for " + user);
-               return displayName.toString();
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifGroup.java
deleted file mode 100644 (file)
index f4e5583..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.naming.InvalidNameException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.Role;
-
-/** Directory group implementation */
-class LdifGroup extends LdifUser implements DirectoryGroup {
-       private final String memberAttributeId;
-
-       LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
-               super(userAdmin, dn, attributes);
-               memberAttributeId = userAdmin.getMemberAttributeId();
-       }
-
-       @Override
-       public boolean addMember(Role role) {
-               try {
-                       Role foundRole = findRole(new LdapName(role.getName()));
-                       if (foundRole == null)
-                               throw new UnsupportedOperationException(
-                                               "Adding role " + role.getName() + " is unsupported within this context.");
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
-               }
-
-               getUserAdmin().checkEdit();
-               if (!isEditing())
-                       startEditing();
-
-               Attribute member = getAttributes().get(memberAttributeId);
-               if (member != null) {
-                       if (member.contains(role.getName()))
-                               return false;
-                       else
-                               member.add(role.getName());
-               } else
-                       getAttributes().put(memberAttributeId, role.getName());
-               return true;
-       }
-
-       @Override
-       public boolean addRequiredMember(Role role) {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public boolean removeMember(Role role) {
-               getUserAdmin().checkEdit();
-               if (!isEditing())
-                       startEditing();
-
-               Attribute member = getAttributes().get(memberAttributeId);
-               if (member != null) {
-                       if (!member.contains(role.getName()))
-                               return false;
-                       member.remove(role.getName());
-                       return true;
-               } else
-                       return false;
-       }
-
-       @Override
-       public Role[] getMembers() {
-               List<Role> directMembers = new ArrayList<Role>();
-               for (LdapName ldapName : getMemberNames()) {
-                       Role role = findRole(ldapName);
-                       if (role == null) {
-                               throw new UserDirectoryException("Role " + ldapName + " cannot be added.");
-                       }
-                       directMembers.add(role);
-               }
-               return directMembers.toArray(new Role[directMembers.size()]);
-       }
-
-       /**
-        * Whether a role with this name can be found from this context.
-        * 
-        * @return The related {@link Role} or <code>null</code>.
-        */
-       protected Role findRole(LdapName ldapName) {
-               Role role = getUserAdmin().getRole(ldapName.toString());
-               if (role == null) {
-                       if (getUserAdmin().getExternalRoles() != null)
-                               role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
-               }
-               return role;
-       }
-
-       @Override
-       public List<LdapName> getMemberNames() {
-               Attribute memberAttribute = getAttributes().get(memberAttributeId);
-               if (memberAttribute == null)
-                       return new ArrayList<LdapName>();
-               try {
-                       List<LdapName> roles = new ArrayList<LdapName>();
-                       NamingEnumeration<?> values = memberAttribute.getAll();
-                       while (values.hasMore()) {
-                               LdapName dn = new LdapName(values.next().toString());
-                               roles.add(dn);
-                       }
-                       return roles;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot get members", e);
-               }
-       }
-
-       @Override
-       public Role[] getRequiredMembers() {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public int getType() {
-               return GROUP;
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java
deleted file mode 100644 (file)
index b3e7f59..0000000
+++ /dev/null
@@ -1,409 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static java.nio.charset.StandardCharsets.US_ASCII;
-
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttribute;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.AuthPassword;
-import org.argeo.naming.LdapAttrs;
-import org.argeo.naming.SharedSecret;
-
-/** Directory user implementation */
-class LdifUser implements DirectoryUser {
-       private final AbstractUserDirectory userAdmin;
-
-       private final LdapName dn;
-
-       private final boolean frozen;
-       private Attributes publishedAttributes;
-
-       private final AttributeDictionary properties;
-       private final AttributeDictionary credentials;
-
-       LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
-               this(userAdmin, dn, attributes, false);
-       }
-
-       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
-               this.userAdmin = userAdmin;
-               this.dn = dn;
-               this.publishedAttributes = attributes;
-               properties = new AttributeDictionary(false);
-               credentials = new AttributeDictionary(true);
-               this.frozen = frozen;
-       }
-
-       @Override
-       public String getName() {
-               return dn.toString();
-       }
-
-       @Override
-       public int getType() {
-               return USER;
-       }
-
-       @Override
-       public Dictionary<String, Object> getProperties() {
-               return properties;
-       }
-
-       @Override
-       public Dictionary<String, Object> getCredentials() {
-               return credentials;
-       }
-
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               if (key == null) {
-                       // TODO check other sources (like PKCS12)
-                       // String pwd = new String((char[]) value);
-                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
-                       char[] password = DigestUtils.bytesToChars(value);
-                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
-                       if (authPassword != null) {
-                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
-                                       SharedSecret onceToken = new SharedSecret(authPassword);
-                                       if (onceToken.isExpired()) {
-                                               // AuthPassword.remove(getAttributes(), onceToken);
-                                               return false;
-                                       } else {
-                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
-                                               return true;
-                                       }
-                                       // TODO delete expired tokens?
-                               } else {
-                                       // TODO implement SHA
-                                       throw new UnsupportedOperationException(
-                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
-                               }
-                       }
-
-                       // Regular password
-//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
-                       if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
-                               return true;
-                       return false;
-               }
-
-               Object storedValue = getCredentials().get(key);
-               if (storedValue == null || value == null)
-                       return false;
-               if (!(value instanceof String || value instanceof byte[]))
-                       return false;
-               if (storedValue instanceof String && value instanceof String)
-                       return storedValue.equals(value);
-               if (storedValue instanceof byte[] && value instanceof byte[]) {
-                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
-                       String passwordScheme = null;
-                       if (storedBase64.charAt(0) == '{') {
-                               int index = storedBase64.indexOf('}');
-                               if (index > 0) {
-                                       passwordScheme = storedBase64.substring(1, index);
-                                       String storedValueBase64 = storedBase64.substring(index + 1);
-                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
-                                       char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
-                                       byte[] valueBytes;
-                                       if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
-                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
-                                       } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
-                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
-                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
-                                               BigInteger iterations = new BigInteger(iterationsArr);
-                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
-                                                               iterationsArr.length + 64);
-                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
-                                                               storedValueBytes.length);
-                                               int keyLengthBits = keyArr.length * 8;
-                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
-                                                               iterations.intValue(), keyLengthBits);
-                                       } else {
-                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
-                                       }
-                                       return Arrays.equals(storedValueBytes, valueBytes);
-                               }
-                       }
-               }
-//             if (storedValue instanceof byte[] && value instanceof byte[]) {
-//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
-//             }
-               return false;
-       }
-
-       /** Hash the password */
-       byte[] sha1hash(char[] password) {
-               byte[] hashedPassword = ("{SHA}"
-                               + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
-                                               .getBytes(StandardCharsets.UTF_8);
-               return hashedPassword;
-       }
-
-//     byte[] hash(char[] password, String passwordScheme) {
-//             if (passwordScheme == null)
-//                     passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
-//             byte[] hashedPassword = ("{" + passwordScheme + "}"
-//                             + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
-//                                             .getBytes(US_ASCII);
-//             return hashedPassword;
-//     }
-
-       @Override
-       public LdapName getDn() {
-               return dn;
-       }
-
-       @Override
-       public synchronized Attributes getAttributes() {
-               return isEditing() ? getModifiedAttributes() : publishedAttributes;
-       }
-
-       /** Should only be called from working copy thread. */
-       private synchronized Attributes getModifiedAttributes() {
-               assert getWc() != null;
-               return getWc().getAttributes(getDn());
-       }
-
-       protected synchronized boolean isEditing() {
-               return getWc() != null && getModifiedAttributes() != null;
-       }
-
-       private synchronized UserDirectoryWorkingCopy getWc() {
-               return userAdmin.getWorkingCopy();
-       }
-
-       protected synchronized void startEditing() {
-               if (frozen)
-                       throw new UserDirectoryException("Cannot edit frozen view");
-               if (getUserAdmin().isReadOnly())
-                       throw new UserDirectoryException("User directory is read-only");
-               assert getModifiedAttributes() == null;
-               getWc().startEditing(this);
-               // modifiedAttributes = (Attributes) publishedAttributes.clone();
-       }
-
-       public synchronized void publishAttributes(Attributes modifiedAttributes) {
-               publishedAttributes = modifiedAttributes;
-       }
-
-       public DirectoryUser getPublished() {
-               return new LdifUser(userAdmin, dn, publishedAttributes, true);
-       }
-
-       @Override
-       public int hashCode() {
-               return dn.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (this == obj)
-                       return true;
-               if (obj instanceof LdifUser) {
-                       LdifUser that = (LdifUser) obj;
-                       return this.dn.equals(that.dn);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return dn.toString();
-       }
-
-       protected AbstractUserDirectory getUserAdmin() {
-               return userAdmin;
-       }
-
-       private class AttributeDictionary extends Dictionary<String, Object> {
-               private final List<String> effectiveKeys = new ArrayList<String>();
-               private final List<String> attrFilter;
-               private final Boolean includeFilter;
-
-               public AttributeDictionary(Boolean includeFilter) {
-                       this.attrFilter = userAdmin.getCredentialAttributeIds();
-                       this.includeFilter = includeFilter;
-                       try {
-                               NamingEnumeration<String> ids = getAttributes().getIDs();
-                               while (ids.hasMore()) {
-                                       String id = ids.next();
-                                       if (includeFilter && attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                                       else if (!includeFilter && !attrFilter.contains(id))
-                                               effectiveKeys.add(id);
-                               }
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot initialise attribute dictionary", e);
-                       }
-               }
-
-               @Override
-               public int size() {
-                       return effectiveKeys.size();
-               }
-
-               @Override
-               public boolean isEmpty() {
-                       return effectiveKeys.size() == 0;
-               }
-
-               @Override
-               public Enumeration<String> keys() {
-                       return Collections.enumeration(effectiveKeys);
-               }
-
-               @Override
-               public Enumeration<Object> elements() {
-                       final Iterator<String> it = effectiveKeys.iterator();
-                       return new Enumeration<Object>() {
-
-                               @Override
-                               public boolean hasMoreElements() {
-                                       return it.hasNext();
-                               }
-
-                               @Override
-                               public Object nextElement() {
-                                       String key = it.next();
-                                       return get(key);
-                               }
-
-                       };
-               }
-
-               @Override
-               public Object get(Object key) {
-                       try {
-                               Attribute attr = getAttributes().get(key.toString());
-                               if (attr == null)
-                                       return null;
-                               Object value = attr.get();
-                               if (value instanceof byte[]) {
-                                       if (key.equals(LdapAttrs.userPassword.name()))
-                                               // TODO other cases (certificates, images)
-                                               return value;
-                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
-                               }
-                               if (attr.size() == 1)
-                                       return value;
-                               if (!attr.getID().equals(LdapAttrs.objectClass.name()))
-                                       return value;
-                               // special case for object class
-                               NamingEnumeration<?> en = attr.getAll();
-                               Set<String> objectClasses = new HashSet<String>();
-                               while (en.hasMore()) {
-                                       String objectClass = en.next().toString();
-                                       objectClasses.add(objectClass);
-                               }
-
-                               if (objectClasses.contains(userAdmin.getUserObjectClass()))
-                                       return userAdmin.getUserObjectClass();
-                               else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
-                                       return userAdmin.getGroupObjectClass();
-                               else
-                                       return value;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object put(String key, Object value) {
-                       if (key == null) {
-                               // TODO persist to other sources (like PKCS12)
-                               char[] password = DigestUtils.bytesToChars(value);
-                               byte[] hashedPassword = sha1hash(password);
-                               return put(LdapAttrs.userPassword.name(), hashedPassword);
-                       }
-                       if (key.startsWith("X-")) {
-                               return put(LdapAttrs.authPassword.name(), value);
-                       }
-
-                       userAdmin.checkEdit();
-                       if (!isEditing())
-                               startEditing();
-
-                       if (!(value instanceof String || value instanceof byte[]))
-                               throw new IllegalArgumentException("Value must be String or byte[]");
-
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
-
-                       try {
-                               Attribute attribute = getModifiedAttributes().get(key.toString());
-                               // if (attribute == null) // block unit tests
-                               attribute = new BasicAttribute(key.toString());
-                               if (value instanceof String && !isAsciiPrintable(((String) value)))
-                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
-                               else
-                                       attribute.add(value);
-                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
-                               if (previousAttribute != null)
-                                       return previousAttribute.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
-                       }
-               }
-
-               @Override
-               public Object remove(Object key) {
-                       userAdmin.checkEdit();
-                       if (!isEditing())
-                               startEditing();
-
-                       if (includeFilter && !attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " not included");
-                       else if (!includeFilter && attrFilter.contains(key))
-                               throw new IllegalArgumentException("Key " + key + " excluded");
-
-                       try {
-                               Attribute attr = getModifiedAttributes().remove(key.toString());
-                               if (attr != null)
-                                       return attr.get();
-                               else
-                                       return null;
-                       } catch (NamingException e) {
-                               throw new UserDirectoryException("Cannot remove attribute " + key, e);
-                       }
-               }
-       }
-
-       private static boolean isAsciiPrintable(String str) {
-               if (str == null) {
-                       return false;
-               }
-               int sz = str.length();
-               for (int i = 0; i < sz; i++) {
-                       if (isAsciiPrintable(str.charAt(i)) == false) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       private static boolean isAsciiPrintable(char ch) {
-               return ch >= 32 && ch < 127;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
deleted file mode 100644 (file)
index 832e8e5..0000000
+++ /dev/null
@@ -1,264 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.naming.LdapAttrs.objectClass;
-import static org.argeo.naming.LdapObjs.inetOrgPerson;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingEnumeration;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.transaction.TransactionManager;
-
-import org.argeo.naming.LdifParser;
-import org.argeo.naming.LdifWriter;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.Role;
-import org.osgi.service.useradmin.User;
-
-/**
- * A user admin based on a LDIF files. Requires a {@link TransactionManager} and
- * an open transaction for write access.
- */
-public class LdifUserAdmin extends AbstractUserDirectory {
-       private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
-       private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
-
-       public LdifUserAdmin(String uri, String baseDn) {
-               this(fromUri(uri, baseDn), false);
-       }
-
-       public LdifUserAdmin(Dictionary<String, ?> properties) {
-               this(properties, false);
-       }
-
-       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
-               super(null, properties, scoped);
-       }
-
-       public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
-               super(uri, properties, false);
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               Dictionary<String, Object> credentials = user.getCredentials();
-               String username = (String) credentials.get(SHARED_STATE_USERNAME);
-               if (username == null)
-                       username = user.getName();
-               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
-               byte[] pwd = (byte[]) pwdCred;
-               if (pwd != null) {
-                       char[] password = DigestUtils.bytesToChars(pwd);
-                       User directoryUser = (User) getRole(username);
-                       if (!directoryUser.hasCredential(null, password))
-                               throw new UserDirectoryException("Invalid credentials");
-               } else {
-                       throw new UserDirectoryException("Password is required");
-               }
-               Dictionary<String, Object> properties = cloneProperties();
-               properties.put(UserAdminConf.readOnly.name(), "true");
-               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
-               scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
-               scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
-               return scopedUserAdmin;
-       }
-
-       private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
-               Hashtable<String, Object> res = new Hashtable<String, Object>();
-               res.put(UserAdminConf.uri.name(), uri);
-               res.put(UserAdminConf.baseDn.name(), baseDn);
-               return res;
-       }
-
-       public void init() {
-
-               try {
-                       URI u = new URI(getUri());
-                       if (u.getScheme().equals("file")) {
-                               File file = new File(u);
-                               if (!file.exists())
-                                       return;
-                       }
-                       load(u.toURL().openStream());
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot open URL " + getUri(), e);
-               }
-       }
-
-       public void save() {
-               if (getUri() == null)
-                       throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
-               if (isReadOnly())
-                       throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
-               try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
-                       save(out);
-               } catch (IOException | URISyntaxException e) {
-                       throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
-               }
-       }
-
-       public void save(OutputStream out) throws IOException {
-               try {
-                       LdifWriter ldifWriter = new LdifWriter(out);
-                       for (LdapName name : groups.keySet())
-                               ldifWriter.writeEntry(name, groups.get(name).getAttributes());
-                       for (LdapName name : users.keySet())
-                               ldifWriter.writeEntry(name, users.get(name).getAttributes());
-               } finally {
-                       out.close();
-               }
-       }
-
-       protected void load(InputStream in) {
-               try {
-                       users.clear();
-                       groups.clear();
-
-                       LdifParser ldifParser = new LdifParser();
-                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
-                       for (LdapName key : allEntries.keySet()) {
-                               Attributes attributes = allEntries.get(key);
-                               // check for inconsistency
-                               Set<String> lowerCase = new HashSet<String>();
-                               NamingEnumeration<String> ids = attributes.getIDs();
-                               while (ids.hasMoreElements()) {
-                                       String id = ids.nextElement().toLowerCase();
-                                       if (lowerCase.contains(id))
-                                               throw new UserDirectoryException(key + " has duplicate id " + id);
-                                       lowerCase.add(id);
-                               }
-
-                               // analyse object classes
-                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
-                               // System.out.println(key);
-                               objectClasses: while (objectClasses.hasMore()) {
-                                       String objectClass = objectClasses.next().toString();
-                                       // System.out.println(" " + objectClass);
-                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
-                                               users.put(key, new LdifUser(this, key, attributes));
-                                               break objectClasses;
-                                       } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
-                                               groups.put(key, new LdifGroup(this, key, attributes));
-                                               break objectClasses;
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
-               }
-       }
-
-       public void destroy() {
-               if (users == null || groups == null)
-                       throw new UserDirectoryException("User directory " + getBaseDn() + " is already destroyed");
-               users = null;
-               groups = null;
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
-               if (groups.containsKey(key))
-                       return groups.get(key);
-               if (users.containsKey(key))
-                       return users.get(key);
-               throw new NameNotFoundException(key + " not persisted");
-       }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               return users.containsKey(dn) || groups.containsKey(dn);
-       }
-
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
-               if (f == null) {
-                       res.addAll(users.values());
-                       res.addAll(groups.values());
-               } else {
-                       for (DirectoryUser user : users.values()) {
-                               if (f.match(user.getProperties()))
-                                       res.add(user);
-                       }
-                       for (DirectoryUser group : groups.values())
-                               if (f.match(group.getProperties()))
-                                       res.add(group);
-               }
-               return res;
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               List<LdapName> directGroups = new ArrayList<LdapName>();
-               for (LdapName name : groups.keySet()) {
-                       DirectoryGroup group = groups.get(name);
-                       if (group.getMemberNames().contains(dn))
-                               directGroups.add(group.getDn());
-               }
-               return directGroups;
-       }
-
-       @Override
-       protected void prepare(UserDirectoryWorkingCopy wc) {
-               // delete
-               for (LdapName dn : wc.getDeletedUsers().keySet()) {
-                       if (users.containsKey(dn))
-                               users.remove(dn);
-                       else if (groups.containsKey(dn))
-                               groups.remove(dn);
-                       else
-                               throw new UserDirectoryException("User to delete not found " + dn);
-               }
-               // add
-               for (LdapName dn : wc.getNewUsers().keySet()) {
-                       DirectoryUser user = wc.getNewUsers().get(dn);
-                       if (users.containsKey(dn) || groups.containsKey(dn))
-                               throw new UserDirectoryException("User to create found " + dn);
-                       else if (Role.USER == user.getType())
-                               users.put(dn, user);
-                       else if (Role.GROUP == user.getType())
-                               groups.put(dn, (DirectoryGroup) user);
-                       else
-                               throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn);
-               }
-               // modify
-               for (LdapName dn : wc.getModifiedUsers().keySet()) {
-                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
-                       DirectoryUser user;
-                       if (users.containsKey(dn))
-                               user = users.get(dn);
-                       else if (groups.containsKey(dn))
-                               user = groups.get(dn);
-                       else
-                               throw new UserDirectoryException("User to modify no found " + dn);
-                       user.publishAttributes(modifiedAttrs);
-               }
-       }
-
-       @Override
-       protected void commit(UserDirectoryWorkingCopy wc) {
-               save();
-       }
-
-       @Override
-       protected void rollback(UserDirectoryWorkingCopy wc) {
-               init();
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserDirectory.java
deleted file mode 100644 (file)
index fe1ca76..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.List;
-
-import javax.naming.NameNotFoundException;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.LdapAttrs;
-import org.osgi.framework.Filter;
-import org.osgi.service.useradmin.User;
-
-public class OsUserDirectory extends AbstractUserDirectory {
-       private final String osUsername = System.getProperty("user.name");
-       private final LdapName osUserDn;
-       private final LdifUser osUser;
-
-       public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
-               super(uriArg, props, false);
-               try {
-                       osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn());
-                       Attributes attributes = new BasicAttributes();
-                       attributes.put(LdapAttrs.uid.name(), osUsername);
-                       osUser = new LdifUser(this, osUserDn, attributes);
-               } catch (NamingException e) {
-                       throw new UserDirectoryException("Cannot create system user", e);
-               }
-       }
-
-       @Override
-       protected List<LdapName> getDirectGroups(LdapName dn) {
-               return new ArrayList<>();
-       }
-
-       @Override
-       protected Boolean daoHasRole(LdapName dn) {
-               return osUserDn.equals(dn);
-       }
-
-       @Override
-       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
-               if (osUserDn.equals(key))
-                       return osUser;
-               else
-                       throw new NameNotFoundException("Not an OS role");
-       }
-
-       @Override
-       protected List<DirectoryUser> doGetRoles(Filter f) {
-               List<DirectoryUser> res = new ArrayList<>();
-               if (f == null || f.match(osUser.getProperties()))
-                       res.add(osUser);
-               return res;
-       }
-
-       @Override
-       protected AbstractUserDirectory scope(User user) {
-               throw new UnsupportedOperationException();
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/OsUserUtils.java
deleted file mode 100644 (file)
index ad6bf88..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.NoSuchAlgorithmException;
-import java.security.URIParameter;
-
-import javax.security.auth.Subject;
-import javax.security.auth.login.Configuration;
-import javax.security.auth.login.LoginContext;
-import javax.security.auth.login.LoginException;
-
-public class OsUserUtils {
-       private static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
-       private static String LOGIN_CONTEXT_USER_NT = "USER_NT";
-
-       public static String getOsUsername() {
-               return System.getProperty("user.name");
-       }
-
-       public static LoginContext loginAsSystemUser(Subject subject) {
-               try {
-                       URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
-                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
-                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
-                       Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
-                       LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
-                                       null, jaasConfiguration);
-                       lc.login();
-                       return lc;
-               } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
-                       throw new RuntimeException("Cannot login as system user", e);
-               }
-       }
-
-       public static void main(String args[]) {
-               Subject subject = new Subject();
-               LoginContext loginContext = loginAsSystemUser(subject);
-               System.out.println(subject);
-               try {
-                       loginContext.logout();
-               } catch (LoginException e) {
-                       // silent
-               }
-       }
-
-       private static boolean isWindows() {
-               return System.getProperty("os.name").startsWith("Windows");
-       }
-
-       private OsUserUtils() {
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/TokenUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/TokenUtils.java
deleted file mode 100644 (file)
index 83c1d76..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import static org.argeo.naming.LdapAttrs.description;
-import static org.argeo.naming.LdapAttrs.owner;
-
-import java.security.Principal;
-import java.time.Instant;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.security.auth.Subject;
-
-import org.argeo.naming.NamingUtils;
-import org.osgi.service.useradmin.Group;
-
-/**
- * Canonically implements the Argeo token conventions.
- */
-public class TokenUtils {
-       public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
-               Set<String> res = new HashSet<>();
-               for (Principal principal : subject.getPrincipals()) {
-                       String name = principal.getName();
-                       if (name.endsWith(tokensBaseDn)) {
-                               try {
-                                       LdapName ldapName = new LdapName(name);
-                                       String token = ldapName.getRdn(ldapName.size()).getValue().toString();
-                                       res.add(token);
-                               } catch (InvalidNameException e) {
-                                       throw new UserDirectoryException("Invalid principal " + principal, e);
-                               }
-                       }
-               }
-               return res;
-       }
-
-       /** The user related to this token group */
-       public static String userDn(Group tokenGroup) {
-               return (String) tokenGroup.getProperties().get(owner.name());
-       }
-
-       public static boolean isExpired(Group tokenGroup) {
-               return isExpired(tokenGroup, Instant.now());
-
-       }
-
-       public static boolean isExpired(Group tokenGroup, Instant instant) {
-               String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
-               if (expiryDateStr != null) {
-                       Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
-                       if (expiryDate.isBefore(instant)) {
-                               return true;
-                       }
-               }
-               return false;
-       }
-
-//     private final String token;
-//
-//     public TokenUtils(String token) {
-//             this.token = token;
-//     }
-//
-//     public String getToken() {
-//             return token;
-//     }
-//
-//     @Override
-//     public int hashCode() {
-//             return token.hashCode();
-//     }
-//
-//     @Override
-//     public boolean equals(Object obj) {
-//             if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
-//                     return true;
-//             return false;
-//     }
-//
-//     @Override
-//     public String toString() {
-//             return "Token #" + hashCode();
-//     }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
deleted file mode 100644 (file)
index ec41978..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
-import javax.naming.Context;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.naming.NamingUtils;
-
-/** Properties used to configure user admins. */
-public enum UserAdminConf {
-       /** Base DN (cannot be configured externally) */
-       baseDn("dc=example,dc=com"),
-
-       /** URI of the underlying resource (cannot be configured externally) */
-       uri("ldap://localhost:10389"),
-
-       /** User objectClass */
-       userObjectClass("inetOrgPerson"),
-
-       /** Relative base DN for users */
-       userBase("ou=People"),
-
-       /** Groups objectClass */
-       groupObjectClass("groupOfNames"),
-
-       /** Relative base DN for users */
-       groupBase("ou=Groups"),
-
-       /** Read-only source */
-       readOnly(null),
-
-       /** Disabled source */
-       disabled(null),
-
-       /** Authentication realm */
-       realm(null);
-
-       public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
-
-       public final static String SCHEME_LDAP = "ldap";
-       public final static String SCHEME_LDAPS = "ldaps";
-       public final static String SCHEME_FILE = "file";
-       public final static String SCHEME_OS = "os";
-       public final static String SCHEME_IPA = "ipa";
-
-       /** The default value. */
-       private Object def;
-
-       UserAdminConf(Object def) {
-               this.def = def;
-       }
-
-       public Object getDefault() {
-               return def;
-       }
-
-       /**
-        * For use as Java property.
-        * 
-        * @deprecated use {@link #name()} instead
-        */
-       @Deprecated
-       public String property() {
-               return name();
-       }
-
-       public String getValue(Dictionary<String, ?> properties) {
-               Object res = getRawValue(properties);
-               if (res == null)
-                       return null;
-               return res.toString();
-       }
-
-       @SuppressWarnings("unchecked")
-       public <T> T getRawValue(Dictionary<String, ?> properties) {
-               Object res = properties.get(name());
-               if (res == null)
-                       res = getDefault();
-               return (T) res;
-       }
-
-       /** @deprecated use {@link #valueOf(String)} instead */
-       @Deprecated
-       public static UserAdminConf local(String property) {
-               return UserAdminConf.valueOf(property);
-       }
-
-       /** Hides host and credentials. */
-       public static URI propertiesAsUri(Dictionary<String, ?> properties) {
-               StringBuilder query = new StringBuilder();
-
-               boolean first = true;
-//             for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
-//                     String key = keys.nextElement();
-//                     // TODO clarify which keys are relevant (list only the enum?)
-//                     if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
-//                                     && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
-//                                     && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
-//                                     && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
-//                             if (first)
-//                                     first = false;
-//                             else
-//                                     query.append('&');
-//                             query.append(valueOf(key).name());
-//                             query.append('=').append(properties.get(key).toString());
-//                     }
-//             }
-
-               keys: for (UserAdminConf key : UserAdminConf.values()) {
-                       if (key.equals(baseDn) || key.equals(uri))
-                               continue keys;
-                       Object value = properties.get(key.name());
-                       if (value == null)
-                               continue keys;
-                       if (first)
-                               first = false;
-                       else
-                               query.append('&');
-                       query.append(key.name());
-                       query.append('=').append(value.toString());
-
-               }
-
-               Object bDnObj = properties.get(baseDn.name());
-               String bDn = bDnObj != null ? bDnObj.toString() : null;
-               try {
-                       return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
-                                       null);
-               } catch (URISyntaxException e) {
-                       throw new UserDirectoryException("Cannot create URI from properties", e);
-               }
-       }
-
-       public static Dictionary<String, Object> uriAsProperties(String uriStr) {
-               try {
-                       Hashtable<String, Object> res = new Hashtable<String, Object>();
-                       URI u = new URI(uriStr);
-                       String scheme = u.getScheme();
-                       if (scheme != null && scheme.equals(SCHEME_IPA)) {
-                               return IpaUtils.convertIpaUri(u);
-//                             scheme = u.getScheme();
-                       }
-                       String path = u.getPath();
-                       // base DN
-                       String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
-                       if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
-                               bDn = getBaseDnFromHostname();
-                       }
-
-                       if (bDn.endsWith(".ldif"))
-                               bDn = bDn.substring(0, bDn.length() - ".ldif".length());
-
-                       // Normalize base DN as LDAP name
-                       bDn = new LdapName(bDn).toString();
-
-                       String principal = null;
-                       String credentials = null;
-                       if (scheme != null)
-                               if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
-                                       // TODO additional checks
-                                       if (u.getUserInfo() != null) {
-                                               String[] userInfo = u.getUserInfo().split(":");
-                                               principal = userInfo.length > 0 ? userInfo[0] : null;
-                                               credentials = userInfo.length > 1 ? userInfo[1] : null;
-                                       }
-                               } else if (scheme.equals(SCHEME_FILE)) {
-                               } else if (scheme.equals(SCHEME_IPA)) {
-                               } else if (scheme.equals(SCHEME_OS)) {
-                               } else
-                                       throw new UserDirectoryException("Unsupported scheme " + scheme);
-                       Map<String, List<String>> query = NamingUtils.queryToMap(u);
-                       for (String key : query.keySet()) {
-                               UserAdminConf ldapProp = UserAdminConf.valueOf(key);
-                               List<String> values = query.get(key);
-                               if (values.size() == 1) {
-                                       res.put(ldapProp.name(), values.get(0));
-                               } else {
-                                       throw new UserDirectoryException("Only single values are supported");
-                               }
-                       }
-                       res.put(baseDn.name(), bDn);
-                       if (SCHEME_OS.equals(scheme))
-                               res.put(readOnly.name(), "true");
-                       if (principal != null)
-                               res.put(Context.SECURITY_PRINCIPAL, principal);
-                       if (credentials != null)
-                               res.put(Context.SECURITY_CREDENTIALS, credentials);
-                       if (scheme != null) {// relative URIs are dealt with externally
-                               if (SCHEME_OS.equals(scheme)) {
-                                       res.put(uri.name(), SCHEME_OS + ":///");
-                               } else {
-                                       URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
-                                                       scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
-                                       res.put(uri.name(), bareUri.toString());
-                               }
-                       }
-                       return res;
-               } catch (Exception e) {
-                       throw new UserDirectoryException("Cannot convert " + uri + " to properties", e);
-               }
-       }
-
-       private static String getBaseDnFromHostname() {
-               String hostname;
-               try {
-                       hostname = InetAddress.getLocalHost().getHostName();
-               } catch (UnknownHostException e) {
-                       hostname = "localhost.localdomain";
-               }
-               int dotIdx = hostname.indexOf('.');
-               if (dotIdx >= 0) {
-                       String domain = hostname.substring(dotIdx + 1, hostname.length());
-                       String bDn = ("." + domain).replaceAll("\\.", ",dc=");
-                       bDn = bDn.substring(1, bDn.length());
-                       return bDn;
-               } else {
-                       return "dc=" + hostname;
-               }
-       }
-
-       /**
-        * Hash the base DN in order to have a deterministic string to be used as a cn
-        * for the underlying user directory.
-        */
-       public static String baseDnHash(Dictionary<String, Object> properties) {
-               String bDn = (String) properties.get(baseDn.name());
-               if (bDn == null)
-                       throw new UserDirectoryException("No baseDn in " + properties);
-               return DigestUtils.sha1str(bDn);
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectory.java
deleted file mode 100644 (file)
index ff80c5a..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** Information about a user directory. */
-public interface UserDirectory {
-       /** The base DN of all entries in this user directory */
-       LdapName getBaseDn();
-
-       /** The related {@link XAResource} */
-       XAResource getXaResource();
-
-       boolean isReadOnly();
-
-       boolean isDisabled();
-
-       String getUserObjectClass();
-
-       String getUserBase();
-
-       String getGroupObjectClass();
-
-       String getGroupBase();
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryException.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryException.java
deleted file mode 100644 (file)
index 613d0fd..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import org.osgi.service.useradmin.UserAdmin;
-
-/**
- * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin}
- * service.
- */
-public class UserDirectoryException extends RuntimeException {
-       private static final long serialVersionUID = 1419352360062048603L;
-
-       public UserDirectoryException(String message) {
-               super(message);
-       }
-
-       public UserDirectoryException(String message, Throwable e) {
-               super(message, e);
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java
deleted file mode 100644 (file)
index 0e25bdf..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-import javax.transaction.xa.XAResource;
-
-/** {@link XAResource} for a user directory being edited. */
-class UserDirectoryWorkingCopy {
-       // private final static Log log = LogFactory
-       // .getLog(UserDirectoryWorkingCopy.class);
-
-       private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
-       private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
-       private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
-
-       void cleanUp() {
-               // clean collections
-               newUsers.clear();
-               newUsers = null;
-               modifiedUsers.clear();
-               modifiedUsers = null;
-               deletedUsers.clear();
-               deletedUsers = null;
-       }
-
-       public boolean noModifications() {
-               return newUsers.size() == 0 && modifiedUsers.size() == 0
-                               && deletedUsers.size() == 0;
-       }
-
-       public Attributes getAttributes(LdapName dn) {
-               if (modifiedUsers.containsKey(dn))
-                       return modifiedUsers.get(dn);
-               return null;
-       }
-
-       public void startEditing(DirectoryUser user) {
-               LdapName dn = user.getDn();
-               if (modifiedUsers.containsKey(dn))
-                       throw new UserDirectoryException("Already editing " + dn);
-               modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
-       }
-
-       public Map<LdapName, DirectoryUser> getNewUsers() {
-               return newUsers;
-       }
-
-       public Map<LdapName, DirectoryUser> getDeletedUsers() {
-               return deletedUsers;
-       }
-
-       public Map<LdapName, Attributes> getModifiedUsers() {
-               return modifiedUsers;
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/WcXaResource.java
deleted file mode 100644 (file)
index 1630b6b..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.argeo.osgi.useradmin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** {@link XAResource} for a user directory being edited. */
-class WcXaResource implements XAResource {
-       private final AbstractUserDirectory userDirectory;
-
-       private Map<Xid, UserDirectoryWorkingCopy> workingCopies = new HashMap<Xid, UserDirectoryWorkingCopy>();
-       private Xid editingXid = null;
-       private int transactionTimeout = 0;
-
-       public WcXaResource(AbstractUserDirectory userDirectory) {
-               this.userDirectory = userDirectory;
-       }
-
-       @Override
-       public synchronized void start(Xid xid, int flags) throws XAException {
-               if (editingXid != null)
-                       throw new UserDirectoryException("Already editing " + editingXid);
-               UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy());
-               if (wc != null)
-                       throw new UserDirectoryException("There is already a working copy for " + xid);
-               this.editingXid = xid;
-       }
-
-       @Override
-       public void end(Xid xid, int flags) throws XAException {
-               checkXid(xid);
-       }
-
-       private UserDirectoryWorkingCopy wc(Xid xid) {
-               return workingCopies.get(xid);
-       }
-
-       synchronized UserDirectoryWorkingCopy wc() {
-               if (editingXid == null)
-                       return null;
-               UserDirectoryWorkingCopy wc = workingCopies.get(editingXid);
-               if (wc == null)
-                       throw new UserDirectoryException("No working copy found for " + editingXid);
-               return wc;
-       }
-
-       private synchronized void cleanUp(Xid xid) {
-               wc(xid).cleanUp();
-               workingCopies.remove(xid);
-               editingXid = null;
-       }
-
-       @Override
-       public int prepare(Xid xid) throws XAException {
-               checkXid(xid);
-               UserDirectoryWorkingCopy wc = wc(xid);
-               if (wc.noModifications())
-                       return XA_RDONLY;
-               try {
-                       userDirectory.prepare(wc);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               }
-               return XA_OK;
-       }
-
-       @Override
-       public void commit(Xid xid, boolean onePhase) throws XAException {
-               try {
-                       checkXid(xid);
-                       UserDirectoryWorkingCopy wc = wc(xid);
-                       if (wc.noModifications())
-                               return;
-                       if (onePhase)
-                               userDirectory.prepare(wc);
-                       userDirectory.commit(wc);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               } finally {
-                       cleanUp(xid);
-               }
-       }
-
-       @Override
-       public void rollback(Xid xid) throws XAException {
-               try {
-                       checkXid(xid);
-                       userDirectory.rollback(wc(xid));
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       throw new XAException(XAException.XAER_RMERR);
-               } finally {
-                       cleanUp(xid);
-               }
-       }
-
-       @Override
-       public void forget(Xid xid) throws XAException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public boolean isSameRM(XAResource xares) throws XAException {
-               return xares == this;
-       }
-
-       @Override
-       public Xid[] recover(int flag) throws XAException {
-               return new Xid[0];
-       }
-
-       @Override
-       public int getTransactionTimeout() throws XAException {
-               return transactionTimeout;
-       }
-
-       @Override
-       public boolean setTransactionTimeout(int seconds) throws XAException {
-               transactionTimeout = seconds;
-               return true;
-       }
-
-       private void checkXid(Xid xid) throws XAException {
-               if (xid == null)
-                       throw new XAException(XAException.XAER_OUTSIDE);
-               if (!xid.equals(xid))
-                       throw new XAException(XAException.XAER_NOTA);
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/jaas-os.cfg b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/jaas-os.cfg
deleted file mode 100644 (file)
index da04505..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-USER_NIX {
-    com.sun.security.auth.module.UnixLoginModule requisite; 
-};
-
-USER_NT {
-    com.sun.security.auth.module.NTLoginModule requisite; 
-};
-
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/package-info.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/package-info.java
deleted file mode 100644 (file)
index c108d2c..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** LDAP and LDIF based OSGi useradmin implementation. */
-package org.argeo.osgi.useradmin;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.enterprise/src/org/argeo/osgi/util/FilterRequirement.java
deleted file mode 100644 (file)
index 7cb5863..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.argeo.osgi.util;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.osgi.resource.Namespace;
-import org.osgi.resource.Requirement;
-import org.osgi.resource.Resource;
-
-public class FilterRequirement implements Requirement {
-       private String namespace;
-       private String filter;
-       
-       
-
-       public FilterRequirement(String namespace, String filter) {
-               this.namespace = namespace;
-               this.filter = filter;
-       }
-
-       @Override
-       public Resource getResource() {
-               return null;
-       }
-
-       @Override
-       public String getNamespace() {
-               return namespace;
-       }
-
-       @Override
-       public Map<String, String> getDirectives() {
-               Map<String, String> directives = new HashMap<>();
-               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
-               return directives;
-       }
-
-       @Override
-       public Map<String, Object> getAttributes() {
-               return new HashMap<>();
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransaction.java b/org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransaction.java
deleted file mode 100644 (file)
index 43d4cd9..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-package org.argeo.transaction.simple;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.transaction.HeuristicMixedException;
-import javax.transaction.HeuristicRollbackException;
-import javax.transaction.RollbackException;
-import javax.transaction.Status;
-import javax.transaction.Synchronization;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-/** Simple implementation of an XA {@link Transaction}. */
-class SimpleTransaction implements Transaction, Status {
-       private final Xid xid;
-       private int status = Status.STATUS_ACTIVE;
-       private final List<XAResource> xaResources = new ArrayList<XAResource>();
-
-       private final SimpleTransactionManager transactionManager;
-
-       public SimpleTransaction(SimpleTransactionManager transactionManager) {
-               this.xid = new UuidXid();
-               this.transactionManager = transactionManager;
-       }
-
-       @Override
-       public synchronized void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-                       SecurityException, IllegalStateException, SystemException {
-               status = STATUS_PREPARING;
-               for (XAResource xaRes : xaResources) {
-                       if (status == STATUS_MARKED_ROLLBACK)
-                               break;
-                       try {
-                               xaRes.prepare(xid);
-                       } catch (XAException e) {
-                               status = STATUS_MARKED_ROLLBACK;
-                               error("Cannot prepare " + xaRes + " for " + xid, e);
-                       }
-               }
-               if (status == STATUS_MARKED_ROLLBACK) {
-                       rollback();
-                       throw new RollbackException();
-               }
-               status = STATUS_PREPARED;
-
-               status = STATUS_COMMITTING;
-               for (XAResource xaRes : xaResources) {
-                       if (status == STATUS_MARKED_ROLLBACK)
-                               break;
-                       try {
-                               xaRes.commit(xid, false);
-                       } catch (XAException e) {
-                               status = STATUS_MARKED_ROLLBACK;
-                               error("Cannot prepare " + xaRes + " for " + xid, e);
-                       }
-               }
-               if (status == STATUS_MARKED_ROLLBACK) {
-                       rollback();
-                       throw new RollbackException();
-               }
-
-               // complete
-               status = STATUS_COMMITTED;
-               clearResources(XAResource.TMSUCCESS);
-               transactionManager.unregister(xid);
-       }
-
-       @Override
-       public synchronized void rollback() throws IllegalStateException, SystemException {
-               status = STATUS_ROLLING_BACK;
-               for (XAResource xaRes : xaResources) {
-                       try {
-                               xaRes.rollback(xid);
-                       } catch (XAException e) {
-                               error("Cannot rollback " + xaRes + " for " + xid, e);
-                       }
-               }
-
-               // complete
-               status = STATUS_ROLLEDBACK;
-               clearResources(XAResource.TMFAIL);
-               transactionManager.unregister(xid);
-       }
-
-       @Override
-       public synchronized boolean enlistResource(XAResource xaRes)
-                       throws RollbackException, IllegalStateException, SystemException {
-               if (xaResources.add(xaRes)) {
-                       try {
-                               xaRes.start(getXid(), XAResource.TMNOFLAGS);
-                               return true;
-                       } catch (XAException e) {
-                               error("Cannot enlist " + xaRes, e);
-                               return false;
-                       }
-               } else
-                       return false;
-       }
-
-       @Override
-       public synchronized boolean delistResource(XAResource xaRes, int flag)
-                       throws IllegalStateException, SystemException {
-               if (xaResources.remove(xaRes)) {
-                       try {
-                               xaRes.end(getXid(), flag);
-                       } catch (XAException e) {
-                               error("Cannot delist " + xaRes, e);
-                               return false;
-                       }
-                       return true;
-               } else
-                       return false;
-       }
-
-       protected void clearResources(int flag) {
-               for (XAResource xaRes : xaResources)
-                       try {
-                               xaRes.end(getXid(), flag);
-                       } catch (XAException e) {
-                               error("Cannot end " + xaRes, e);
-                       }
-               xaResources.clear();
-       }
-
-       protected void error(Object obj, Exception e) {
-               System.err.println(obj);
-               e.printStackTrace();
-       }
-
-       @Override
-       public synchronized int getStatus() throws SystemException {
-               return status;
-       }
-
-       @Override
-       public void registerSynchronization(Synchronization sync)
-                       throws RollbackException, IllegalStateException, SystemException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public void setRollbackOnly() throws IllegalStateException, SystemException {
-               status = STATUS_MARKED_ROLLBACK;
-       }
-
-       @Override
-       public int hashCode() {
-               return xid.hashCode();
-       }
-
-       Xid getXid() {
-               return xid;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransactionManager.java b/org.argeo.enterprise/src/org/argeo/transaction/simple/SimpleTransactionManager.java
deleted file mode 100644 (file)
index 7dcf7d9..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-package org.argeo.transaction.simple;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.HeuristicMixedException;
-import javax.transaction.HeuristicRollbackException;
-import javax.transaction.InvalidTransactionException;
-import javax.transaction.NotSupportedException;
-import javax.transaction.RollbackException;
-import javax.transaction.Status;
-import javax.transaction.Synchronization;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-import javax.transaction.TransactionSynchronizationRegistry;
-import javax.transaction.UserTransaction;
-import javax.transaction.xa.Xid;
-
-/**
- * Simple implementation of an XA {@link TransactionManager} and
- * {@link UserTransaction}.
- */
-public class SimpleTransactionManager implements TransactionManager, UserTransaction {
-       private ThreadLocal<SimpleTransaction> current = new ThreadLocal<SimpleTransaction>();
-
-       private Map<Xid, SimpleTransaction> knownTransactions = Collections
-                       .synchronizedMap(new HashMap<Xid, SimpleTransaction>());
-       private SyncRegistry syncRegistry = new SyncRegistry();
-
-       @Override
-       public void begin() throws NotSupportedException, SystemException {
-               if (getCurrent() != null)
-                       throw new NotSupportedException("Nested transactions are not supported");
-               SimpleTransaction transaction = new SimpleTransaction(this);
-               knownTransactions.put(transaction.getXid(), transaction);
-               current.set(transaction);
-       }
-
-       @Override
-       public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
-                       SecurityException, IllegalStateException, SystemException {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().commit();
-       }
-
-       @Override
-       public int getStatus() throws SystemException {
-               if (getCurrent() == null)
-                       return Status.STATUS_NO_TRANSACTION;
-               return getTransaction().getStatus();
-       }
-
-       @Override
-       public Transaction getTransaction() throws SystemException {
-               return getCurrent();
-       }
-
-       protected SimpleTransaction getCurrent() throws SystemException {
-               SimpleTransaction transaction = current.get();
-               if (transaction == null)
-                       return null;
-               int status = transaction.getStatus();
-               if (Status.STATUS_COMMITTED == status || Status.STATUS_ROLLEDBACK == status) {
-                       current.remove();
-                       return null;
-               }
-               return transaction;
-       }
-
-       void unregister(Xid xid) {
-               knownTransactions.remove(xid);
-       }
-
-       @Override
-       public void resume(Transaction tobj) throws InvalidTransactionException, IllegalStateException, SystemException {
-               if (getCurrent() != null)
-                       throw new IllegalStateException("Transaction " + current.get() + " already registered");
-               current.set((SimpleTransaction) tobj);
-       }
-
-       @Override
-       public void rollback() throws IllegalStateException, SecurityException, SystemException {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().rollback();
-       }
-
-       @Override
-       public void setRollbackOnly() throws IllegalStateException, SystemException {
-               if (getCurrent() == null)
-                       throw new IllegalStateException("No transaction registered with the current thread.");
-               getCurrent().setRollbackOnly();
-       }
-
-       @Override
-       public void setTransactionTimeout(int seconds) throws SystemException {
-               throw new UnsupportedOperationException();
-       }
-
-       @Override
-       public Transaction suspend() throws SystemException {
-               Transaction transaction = getCurrent();
-               current.remove();
-               return transaction;
-       }
-
-       public TransactionSynchronizationRegistry getTsr() {
-               return syncRegistry;
-       }
-
-       private class SyncRegistry implements TransactionSynchronizationRegistry {
-               @Override
-               public Object getTransactionKey() {
-                       try {
-                               SimpleTransaction transaction = getCurrent();
-                               if (transaction == null)
-                                       return null;
-                               return getCurrent().getXid();
-                       } catch (SystemException e) {
-                               throw new IllegalStateException("Cannot get transaction key", e);
-                       }
-               }
-
-               @Override
-               public void putResource(Object key, Object value) {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public Object getResource(Object key) {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public void registerInterposedSynchronization(Synchronization sync) {
-                       throw new UnsupportedOperationException();
-               }
-
-               @Override
-               public int getTransactionStatus() {
-                       try {
-                               return getStatus();
-                       } catch (SystemException e) {
-                               throw new IllegalStateException("Cannot get status", e);
-                       }
-               }
-
-               @Override
-               public boolean getRollbackOnly() {
-                       try {
-                               return getStatus() == Status.STATUS_MARKED_ROLLBACK;
-                       } catch (SystemException e) {
-                               throw new IllegalStateException("Cannot get status", e);
-                       }
-               }
-
-               @Override
-               public void setRollbackOnly() {
-                       try {
-                               getCurrent().setRollbackOnly();
-                       } catch (Exception e) {
-                               throw new IllegalStateException("Cannot set rollback only", e);
-                       }
-               }
-
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/transaction/simple/UuidXid.java b/org.argeo.enterprise/src/org/argeo/transaction/simple/UuidXid.java
deleted file mode 100644 (file)
index 1009c82..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.argeo.transaction.simple;
-
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.UUID;
-
-import javax.transaction.xa.Xid;
-
-/**
- * Implementation of {@link Xid} based on {@link UUID}, using max significant
- * bits as global transaction id, and least significant bits as branch
- * qualifier.
- */
-public class UuidXid implements Xid, Serializable {
-       private static final long serialVersionUID = -5380531989917886819L;
-       public final static int FORMAT = (int) serialVersionUID;
-
-       private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
-
-       private final int format;
-       private final byte[] globalTransactionId;
-       private final byte[] branchQualifier;
-       private final String uuid;
-       private final int hashCode;
-
-       public UuidXid() {
-               this(UUID.randomUUID());
-       }
-
-       public UuidXid(UUID uuid) {
-               this.format = FORMAT;
-               this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
-               this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
-               this.uuid = uuid.toString();
-               this.hashCode = uuid.hashCode();
-       }
-
-       public UuidXid(Xid xid) {
-               this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
-                               .getBranchQualifier());
-       }
-
-       private UuidXid(int format, byte[] globalTransactionId,
-                       byte[] branchQualifier) {
-               this.format = format;
-               this.globalTransactionId = globalTransactionId;
-               this.branchQualifier = branchQualifier;
-               this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
-                               .toString();
-               this.hashCode = uuid.hashCode();
-       }
-
-       @Override
-       public int getFormatId() {
-               return format;
-       }
-
-       @Override
-       public byte[] getGlobalTransactionId() {
-               return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
-       }
-
-       @Override
-       public byte[] getBranchQualifier() {
-               return Arrays.copyOf(branchQualifier, branchQualifier.length);
-       }
-
-       @Override
-       public int hashCode() {
-               return hashCode;
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (this == obj)
-                       return true;
-               if (obj instanceof UuidXid) {
-                       UuidXid that = (UuidXid) obj;
-                       return Arrays.equals(globalTransactionId, that.globalTransactionId)
-                                       && Arrays.equals(branchQualifier, that.branchQualifier);
-               }
-               if (obj instanceof Xid) {
-                       Xid that = (Xid) obj;
-                       return Arrays.equals(globalTransactionId,
-                                       that.getGlobalTransactionId())
-                                       && Arrays
-                                                       .equals(branchQualifier, that.getBranchQualifier());
-               }
-               return uuid.equals(obj.toString());
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               return new UuidXid(format, globalTransactionId, branchQualifier);
-       }
-
-       @Override
-       public String toString() {
-               return uuid;
-       }
-
-       public UUID asUuid() {
-               return bytesToUUID(globalTransactionId, branchQualifier);
-       }
-
-       public static byte[] uuidToBytes(long bits) {
-               ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
-               buffer.putLong(0, bits);
-               return buffer.array();
-       }
-
-       public static UUID bytesToUUID(byte[] most, byte[] least) {
-               if (most.length < BYTES_PER_LONG)
-                       most = Arrays.copyOf(most, BYTES_PER_LONG);
-               if (least.length < BYTES_PER_LONG)
-                       least = Arrays.copyOf(least, BYTES_PER_LONG);
-               ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
-               buffer.put(most, 0, BYTES_PER_LONG);
-               buffer.put(least, 0, BYTES_PER_LONG);
-               buffer.flip();
-               return new UUID(buffer.getLong(), buffer.getLong());
-       }
-
-       // public static void main(String[] args) {
-       // UUID uuid = UUID.randomUUID();
-       // System.out.println(uuid);
-       // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
-       // uuidToBytes(uuid.getLeastSignificantBits()));
-       // System.out.println(uuid);
-       // }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/transaction/simple/package-info.java b/org.argeo.enterprise/src/org/argeo/transaction/simple/package-info.java
deleted file mode 100644 (file)
index 19adecc..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Minimalistic and partial XA transaction manager implementation. */
-package org.argeo.transaction.simple;
\ No newline at end of file
diff --git a/org.argeo.enterprise/src/org/argeo/util/CsvParser.java b/org.argeo.enterprise/src/org/argeo/util/CsvParser.java
deleted file mode 100644 (file)
index b903f77..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-package org.argeo.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Parses a CSV file interpreting the first line as a header. The
- * {@link #parse(InputStream)} method and the setters are synchronized so that
- * the object cannot be modified when parsing.
- */
-public abstract class CsvParser {
-       private char separator = ',';
-       private char quote = '\"';
-
-       private Boolean noHeader = false;
-       private Boolean strictLineAsLongAsHeader = true;
-
-       /**
-        * Actually process a parsed line. If
-        * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
-        * and the tokens are guaranteed to have the same size.
-        * 
-        * @param lineNumber the current line number, starts at 1 (the header, if header
-        *                   processing is enabled, the first line otherwise)
-        * @param header     the read-only header or null if
-        *                   {@link #setNoHeader(Boolean)} is true (default is false)
-        * @param tokens     the parsed tokens
-        */
-       protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in the stream to parse
-        * 
-        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
-        */
-       @Deprecated
-       public synchronized void parse(InputStream in) {
-               parse(in, (Charset) null);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in       the stream to parse
-        * @param encoding the encoding to use.
-        * 
-        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
-        */
-       @Deprecated
-       public synchronized void parse(InputStream in, String encoding) {
-               Reader reader;
-               if (encoding == null)
-                       reader = new InputStreamReader(in);
-               else
-                       try {
-                               reader = new InputStreamReader(in, encoding);
-                       } catch (UnsupportedEncodingException e) {
-                               throw new IllegalArgumentException(e);
-                       }
-               parse(reader);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param in      the stream to parse
-        * @param charset the charset to use
-        */
-       public synchronized void parse(InputStream in, Charset charset) {
-               Reader reader;
-               if (charset == null)
-                       reader = new InputStreamReader(in);
-               else
-                       reader = new InputStreamReader(in, charset);
-               parse(reader);
-       }
-
-       /**
-        * Parses the CSV file (stream is closed at the end)
-        * 
-        * @param reader the reader to use (it will be buffered)
-        */
-       public synchronized void parse(Reader reader) {
-               Integer lineCount = 0;
-               try (BufferedReader bufferedReader = new BufferedReader(reader)) {
-                       List<String> header = null;
-                       if (!noHeader) {
-                               String headerStr = bufferedReader.readLine();
-                               if (headerStr == null)// empty file
-                                       return;
-                               lineCount++;
-                               header = new ArrayList<String>();
-                               StringBuffer currStr = new StringBuffer("");
-                               Boolean wasInquote = false;
-                               while (parseLine(headerStr, header, currStr, wasInquote)) {
-                                       headerStr = bufferedReader.readLine();
-                                       if (headerStr == null)
-                                               break;
-                                       wasInquote = true;
-                               }
-                               header = Collections.unmodifiableList(header);
-                       }
-
-                       String line = null;
-                       lines: while ((line = bufferedReader.readLine()) != null) {
-                               line = preProcessLine(line);
-                               if (line == null) {
-                                       // skip line
-                                       continue lines;
-                               }
-                               lineCount++;
-                               List<String> tokens = new ArrayList<String>();
-                               StringBuffer currStr = new StringBuffer("");
-                               Boolean wasInquote = false;
-                               sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
-                                       line = bufferedReader.readLine();
-                                       if (line == null)
-                                               break sublines;
-                                       wasInquote = true;
-                               }
-                               if (!noHeader && strictLineAsLongAsHeader) {
-                                       int headerSize = header.size();
-                                       int tokenSize = tokens.size();
-                                       if (tokenSize == 1 && line.trim().equals(""))
-                                               continue lines;// empty line
-                                       if (headerSize != tokenSize) {
-                                               throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
-                                                               + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
-                                                               + ", tokens: " + tokens);
-                                       }
-                               }
-                               processLine(lineCount, header, tokens);
-                       }
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
-               }
-       }
-
-       /**
-        * Called before each (logical) line is processed, giving a change to modify it
-        * (typically for cleaning dirty files). To be overridden, return the line
-        * unchanged by default. Skip the line if 'null' is returned.
-        */
-       protected String preProcessLine(String line) {
-               return line;
-       }
-
-       /**
-        * Parses a line character by character for performance purpose
-        * 
-        * @return whether to continue parsing this line
-        */
-       protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
-               if (wasInquote)
-                       currStr.append('\n');
-
-               char[] arr = str.toCharArray();
-               boolean inQuote = wasInquote;
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       if (c == separator) {
-                               if (!inQuote) {
-                                       tokens.add(currStr.toString());
-//                                     currStr.delete(0, currStr.length());
-                                       currStr.setLength(0);
-                                       currStr.trimToSize();
-                               } else {
-                                       // we don't remove separator that are in a quoted substring
-                                       // System.out
-                                       // .println("IN QUOTE, got a separator: [" + c + "]");
-                                       currStr.append(c);
-                               }
-                       } else if (c == quote) {
-                               if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
-                                       // case of double quote
-                                       currStr.append(quote);
-                                       i++;
-                               } else {// standard
-                                       inQuote = inQuote ? false : true;
-                               }
-                       } else {
-                               currStr.append(c);
-                       }
-               }
-
-               if (!inQuote) {
-                       tokens.add(currStr.toString());
-                       // System.out.println("# TOKEN: " + currStr);
-               }
-               // if (inQuote)
-               // throw new ArgeoException("Missing quote at the end of the line "
-               // + str + " (parsed: " + tokens + ")");
-               if (inQuote)
-                       return true;
-               else
-                       return false;
-               // return tokens;
-       }
-
-       public char getSeparator() {
-               return separator;
-       }
-
-       public synchronized void setSeparator(char separator) {
-               this.separator = separator;
-       }
-
-       public char getQuote() {
-               return quote;
-       }
-
-       public synchronized void setQuote(char quote) {
-               this.quote = quote;
-       }
-
-       public Boolean getNoHeader() {
-               return noHeader;
-       }
-
-       public synchronized void setNoHeader(Boolean noHeader) {
-               this.noHeader = noHeader;
-       }
-
-       public Boolean getStrictLineAsLongAsHeader() {
-               return strictLineAsLongAsHeader;
-       }
-
-       public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
-               this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/CsvParserWithLinesAsMap.java b/org.argeo.enterprise/src/org/argeo/util/CsvParserWithLinesAsMap.java
deleted file mode 100644 (file)
index 8eb6e94..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.argeo.util;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * CSV parser allowing to process lines as maps whose keys are the header
- * fields.
- */
-public abstract class CsvParserWithLinesAsMap extends CsvParser {
-
-       /**
-        * Actually processes a line.
-        * 
-        * @param lineNumber the current line number, starts at 1 (the header, if header
-        *                   processing is enabled, the first lien otherwise)
-        * @param line       the parsed tokens as a map whose keys are the header fields
-        */
-       protected abstract void processLine(Integer lineNumber, Map<String, String> line);
-
-       protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
-               if (header == null)
-                       throw new IllegalArgumentException("Only CSV with header is supported");
-               Map<String, String> line = new HashMap<String, String>();
-               for (int i = 0; i < header.size(); i++) {
-                       String key = header.get(i);
-                       String value = null;
-                       if (i < tokens.size())
-                               value = tokens.get(i);
-                       line.put(key, value);
-               }
-               processLine(lineNumber, line);
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/CsvWriter.java b/org.argeo.enterprise/src/org/argeo/util/CsvWriter.java
deleted file mode 100644 (file)
index c3b3a3a..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UnsupportedEncodingException;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.util.Iterator;
-import java.util.List;
-
-/** Write in CSV format. */
-public class CsvWriter {
-       private final Writer out;
-
-       private char separator = ',';
-       private char quote = '\"';
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out the stream to write to. Caller is responsible for closing it.
-        * 
-        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
-        * 
-        */
-       @Deprecated
-       public CsvWriter(OutputStream out) {
-               this.out = new OutputStreamWriter(out);
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out      the stream to write to. Caller is responsible for closing it.
-        * @param encoding the encoding to use.
-        * 
-        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
-        */
-       @Deprecated
-       public CsvWriter(OutputStream out, String encoding) {
-               try {
-                       this.out = new OutputStreamWriter(out, encoding);
-               } catch (UnsupportedEncodingException e) {
-                       throw new IllegalArgumentException(e);
-               }
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out     the stream to write to. Caller is responsible for closing it.
-        * @param charset the charset to use
-        */
-       public CsvWriter(OutputStream out, Charset charset) {
-               this.out = new OutputStreamWriter(out, charset);
-       }
-
-       /**
-        * Creates a CSV writer.
-        * 
-        * @param out the stream to write to. Caller is responsible for closing it.
-        */
-       public CsvWriter(Writer writer) {
-               this.out = writer;
-       }
-
-       /**
-        * Write a CSV line. Also used to write a header if needed (this is transparent
-        * for the CSV writer): simply call it first, before writing the lines.
-        */
-       public void writeLine(List<?> tokens) {
-               try {
-                       Iterator<?> it = tokens.iterator();
-                       while (it.hasNext()) {
-                               Object obj = it.next();
-                               writeToken(obj != null ? obj.toString() : null);
-                               if (it.hasNext())
-                                       out.write(separator);
-                       }
-                       out.write('\n');
-                       out.flush();
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not write " + tokens, e);
-               }
-       }
-
-       /**
-        * Write a CSV line. Also used to write a header if needed (this is transparent
-        * for the CSV writer): simply call it first, before writing the lines.
-        */
-       public void writeLine(Object[] tokens) {
-               try {
-                       for (int i = 0; i < tokens.length; i++) {
-                               if (tokens[i] == null) {
-                                       writeToken(null);
-                               } else {
-                                       writeToken(tokens[i].toString());
-                               }
-                               if (i != (tokens.length - 1))
-                                       out.write(separator);
-                       }
-                       out.write('\n');
-                       out.flush();
-               } catch (IOException e) {
-                       throw new RuntimeException("Could not write " + tokens, e);
-               }
-       }
-
-       protected void writeToken(String token) throws IOException {
-               if (token == null) {
-                       // TODO configure how to deal with null
-                       out.write("");
-                       return;
-               }
-               // +2 for possible quotes, another +2 assuming there would be an already
-               // quoted string where quotes needs to be duplicated
-               // another +2 for safety
-               // we don't want to increase buffer size while writing
-               StringBuffer buf = new StringBuffer(token.length() + 6);
-               char[] arr = token.toCharArray();
-               boolean shouldQuote = false;
-               for (char c : arr) {
-                       if (!shouldQuote) {
-                               if (c == separator)
-                                       shouldQuote = true;
-                               if (c == '\n')
-                                       shouldQuote = true;
-                       }
-
-                       if (c == quote) {
-                               shouldQuote = true;
-                               // duplicate quote
-                               buf.append(quote);
-                       }
-
-                       // generic case
-                       buf.append(c);
-               }
-
-               if (shouldQuote == true)
-                       out.write(quote);
-               out.write(buf.toString());
-               if (shouldQuote == true)
-                       out.write(quote);
-       }
-
-       public void setSeparator(char separator) {
-               this.separator = separator;
-       }
-
-       public void setQuote(char quote) {
-               this.quote = quote;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/DictionaryKeys.java b/org.argeo.enterprise/src/org/argeo/util/DictionaryKeys.java
deleted file mode 100644 (file)
index d17c86f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.argeo.util;
-
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.Iterator;
-
-/**
- * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
- * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
- * for-each loops.
- */
-class DictionaryKeys implements Iterable<String> {
-       private final Dictionary<String, ?> dictionary;
-
-       public DictionaryKeys(Dictionary<String, ?> dictionary) {
-               this.dictionary = dictionary;
-       }
-
-       @Override
-       public Iterator<String> iterator() {
-               return new KeyIterator(dictionary.keys());
-       }
-
-       private static class KeyIterator implements Iterator<String> {
-               private final Enumeration<String> keys;
-
-               KeyIterator(Enumeration<String> keys) {
-                       this.keys = keys;
-               }
-
-               @Override
-               public boolean hasNext() {
-                       return keys.hasMoreElements();
-               }
-
-               @Override
-               public String next() {
-                       return keys.nextElement();
-               }
-
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/DigestUtils.java b/org.argeo.enterprise/src/org/argeo/util/DigestUtils.java
deleted file mode 100644 (file)
index ce01800..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.argeo.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileChannel.MapMode;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/** Utilities around cryptographic digests */
-public class DigestUtils {
-       public final static String MD5 = "MD5";
-       public final static String SHA1 = "SHA1";
-       public final static String SHA256 = "SHA-256";
-       public final static String SHA512 = "SHA-512";
-
-       private static Boolean debug = false;
-       // TODO: make it configurable
-       private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
-
-       public static byte[] sha1(byte[] bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(SHA1);
-                       digest.update(bytes);
-                       byte[] checksum = digest.digest();
-                       return checksum;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException(e);
-               }
-       }
-
-       public static String digest(String algorithm, byte[] bytes) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       digest.update(bytes);
-                       byte[] checksum = digest.digest();
-                       String res = encodeHexString(checksum);
-                       return res;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               }
-       }
-
-       public static String digest(String algorithm, InputStream in) {
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       // ReadableByteChannel channel = Channels.newChannel(in);
-                       // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
-                       // while (channel.read(bb) > 0)
-                       // digest.update(bb);
-                       byte[] buffer = new byte[byteBufferCapacity];
-                       int read = 0;
-                       while ((read = in.read(buffer)) > 0) {
-                               digest.update(buffer, 0, read);
-                       }
-
-                       byte[] checksum = digest.digest();
-                       String res = encodeHexString(checksum);
-                       return res;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(in);
-               }
-       }
-
-       public static String digest(String algorithm, File file) {
-               FileInputStream fis = null;
-               FileChannel fc = null;
-               try {
-                       fis = new FileInputStream(file);
-                       fc = fis.getChannel();
-
-                       // Get the file's size and then map it into memory
-                       int sz = (int) fc.size();
-                       ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
-                       return digest(algorithm, bb);
-               } catch (IOException e) {
-                       throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
-               } finally {
-                       StreamUtils.closeQuietly(fis);
-                       if (fc.isOpen())
-                               try {
-                                       fc.close();
-                               } catch (IOException e) {
-                                       // silent
-                               }
-               }
-       }
-
-       protected static String digest(String algorithm, ByteBuffer bb) {
-               long begin = System.currentTimeMillis();
-               try {
-                       MessageDigest digest = MessageDigest.getInstance(algorithm);
-                       digest.update(bb);
-                       byte[] checksum = digest.digest();
-                       String res = encodeHexString(checksum);
-                       long end = System.currentTimeMillis();
-                       if (debug)
-                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
-                       return res;
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
-               }
-       }
-
-       public static String sha1hex(Path path) {
-               return digest(SHA1, path, byteBufferCapacity);
-       }
-
-       public static String digest(String algorithm, Path path, long bufferSize) {
-               byte[] digest = digestRaw(algorithm, path, bufferSize);
-               return encodeHexString(digest);
-       }
-
-       public static byte[] digestRaw(String algorithm, Path file, long bufferSize) {
-               long begin = System.currentTimeMillis();
-               try {
-                       MessageDigest md = MessageDigest.getInstance(algorithm);
-                       FileChannel fc = FileChannel.open(file);
-                       long fileSize = Files.size(file);
-                       if (fileSize <= bufferSize) {
-                               ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
-                               md.update(bb);
-                       } else {
-                               long lastCycle = (fileSize / bufferSize) - 1;
-                               long position = 0;
-                               for (int i = 0; i <= lastCycle; i++) {
-                                       ByteBuffer bb;
-                                       if (i != lastCycle) {
-                                               bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
-                                               position = position + bufferSize;
-                                       } else {
-                                               bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
-                                               position = fileSize;
-                                       }
-                                       md.update(bb);
-                               }
-                       }
-                       long end = System.currentTimeMillis();
-                       if (debug)
-                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
-                       return md.digest();
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest " + file + "  with algorithm " + algorithm, e);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot digest " + file + "  with algorithm " + algorithm, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               File file;
-               if (args.length > 0)
-                       file = new File(args[0]);
-               else {
-                       System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
-                                       + "docs/guide/security/CryptoSpec.html#AppA)");
-                       return;
-               }
-
-               if (args.length > 1) {
-                       String algorithm = args[1];
-                       System.out.println(digest(algorithm, file));
-               } else {
-                       String algorithm = "MD5";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       algorithm = "SHA";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       System.out.println(algorithm + ": " + sha1hex(file.toPath()));
-                       algorithm = "SHA-256";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-                       algorithm = "SHA-512";
-                       System.out.println(algorithm + ": " + digest(algorithm, file));
-               }
-       }
-
-       final private static char[] hexArray = "0123456789abcdef".toCharArray();
-
-       /**
-        * From
-        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
-        * -a-hex-string-in-java
-        */
-       public static String encodeHexString(byte[] bytes) {
-               char[] hexChars = new char[bytes.length * 2];
-               for (int j = 0; j < bytes.length; j++) {
-                       int v = bytes[j] & 0xFF;
-                       hexChars[j * 2] = hexArray[v >>> 4];
-                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
-               }
-               return new String(hexChars);
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/DirH.java b/org.argeo.enterprise/src/org/argeo/util/DirH.java
deleted file mode 100644 (file)
index b6d962f..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/** Hashes the hashes of the files in a directory. */
-public class DirH {
-
-       private final static Charset charset = Charset.forName("UTF-16");
-       private final static long bufferSize = 200 * 1024 * 1024;
-       private final static String algorithm = "SHA";
-
-       private final static byte EOL = (byte) '\n';
-       private final static byte SPACE = (byte) ' ';
-
-       private final int hashSize;
-
-       private final byte[][] hashes;
-       private final byte[][] fileNames;
-       private final byte[] digest;
-       private final byte[] dirName;
-
-       /**
-        * @param dirName can be null or empty
-        */
-       private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
-               if (hashes.length != fileNames.length)
-                       throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
-               this.hashes = hashes;
-               this.fileNames = fileNames;
-               this.dirName = dirName == null ? new byte[0] : dirName;
-               if (hashes.length == 0) {// empty dir
-                       hashSize = 20;
-                       // FIXME what is the digest of an empty dir?
-                       digest = new byte[hashSize];
-                       Arrays.fill(digest, SPACE);
-                       return;
-               }
-               hashSize = hashes[0].length;
-               for (int i = 0; i < hashes.length; i++) {
-                       if (hashes[i].length != hashSize)
-                               throw new IllegalArgumentException(
-                                               "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
-               }
-
-               try {
-                       MessageDigest md = MessageDigest.getInstance(algorithm);
-                       for (int i = 0; i < hashes.length; i++) {
-                               md.update(this.hashes[i]);
-                               md.update(SPACE);
-                               md.update(this.fileNames[i]);
-                               md.update(EOL);
-                       }
-                       digest = md.digest();
-               } catch (NoSuchAlgorithmException e) {
-                       throw new IllegalArgumentException("Cannot digest", e);
-               }
-       }
-
-       public void print(PrintStream out) {
-               out.print(DigestUtils.encodeHexString(digest));
-               if (dirName.length > 0) {
-                       out.print(' ');
-                       out.print(new String(dirName, charset));
-               }
-               out.print('\n');
-               for (int i = 0; i < hashes.length; i++) {
-                       out.print(DigestUtils.encodeHexString(hashes[i]));
-                       out.print(' ');
-                       out.print(new String(fileNames[i], charset));
-                       out.print('\n');
-               }
-       }
-
-       public static DirH digest(Path dir) {
-               try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
-                       List<byte[]> hs = new ArrayList<byte[]>();
-                       List<String> fNames = new ArrayList<>();
-                       for (Path file : files) {
-                               if (!Files.isDirectory(file)) {
-                                       byte[] digest = DigestUtils.digestRaw(algorithm, file, bufferSize);
-                                       hs.add(digest);
-                                       fNames.add(file.getFileName().toString());
-                               }
-                       }
-
-                       byte[][] fileNames = new byte[fNames.size()][];
-                       for (int i = 0; i < fNames.size(); i++) {
-                               fileNames[i] = fNames.get(i).getBytes(charset);
-                       }
-                       byte[][] hashes = hs.toArray(new byte[hs.size()][]);
-                       return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot digest " + dir, e);
-               }
-       }
-
-       public static void main(String[] args) {
-               try {
-                       DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
-                       dirH.print(System.out);
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/LangUtils.java b/org.argeo.enterprise/src/org/argeo/util/LangUtils.java
deleted file mode 100644 (file)
index 1622945..0000000
+++ /dev/null
@@ -1,284 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.time.temporal.Temporal;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-
-/** Utilities around Java basic features. */
-public class LangUtils {
-       /*
-        * NON-API OSGi
-        */
-       /**
-        * Returns an array with the names of the provided classes. Useful when
-        * registering services with multiple interfaces in OSGi.
-        */
-       public static String[] names(Class<?>... clzz) {
-               String[] res = new String[clzz.length];
-               for (int i = 0; i < clzz.length; i++)
-                       res[i] = clzz[i].getName();
-               return res;
-       }
-
-       /*
-        * MAP
-        */
-       /**
-        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
-        * null, but if the value is null, it returns an empty {@link Dictionary}.
-        */
-       public static Map<String, Object> map(String key, Object value) {
-               assert key != null;
-               HashMap<String, Object> props = new HashMap<>();
-               if (value != null)
-                       props.put(key, value);
-               return props;
-       }
-
-       /*
-        * DICTIONARY
-        */
-
-       /**
-        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
-        * null, but if the value is null, it returns an empty {@link Dictionary}.
-        */
-       public static Dictionary<String, Object> dict(String key, Object value) {
-               assert key != null;
-               Hashtable<String, Object> props = new Hashtable<>();
-               if (value != null)
-                       props.put(key, value);
-               return props;
-       }
-
-       /** @deprecated Use {@link #dict(String, Object)} instead. */
-       @Deprecated
-       public static Dictionary<String, Object> dico(String key, Object value) {
-               return dict(key, value);
-       }
-
-       /** Converts a {@link Dictionary} to a {@link Map} of strings. */
-       public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
-               if (properties == null) {
-                       return null;
-               }
-               Map<String, String> res = new HashMap<>(properties.size());
-               Enumeration<String> keys = properties.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       res.put(key, properties.get(key).toString());
-               }
-               return res;
-       }
-
-       /**
-        * Get a string property from this map, expecting to find it, or
-        * <code>null</code> if not found.
-        */
-       public static String get(Map<String, ?> map, String key) {
-               Object res = map.get(key);
-               if (res == null)
-                       return null;
-               return res.toString();
-       }
-
-       /**
-        * Get a string property from this map, expecting to find it.
-        * 
-        * @throws IllegalArgumentException if the key was not found
-        */
-       public static String getNotNull(Map<String, ?> map, String key) {
-               Object res = map.get(key);
-               if (res == null)
-                       throw new IllegalArgumentException("Map " + map + " should contain key " + key);
-               return res.toString();
-       }
-
-       /**
-        * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
-        */
-       public static Iterable<String> keys(Dictionary<String, ?> props) {
-               assert props != null;
-               return new DictionaryKeys(props);
-       }
-
-       static String toJson(Dictionary<String, ?> props) {
-               return toJson(props, false);
-       }
-
-       static String toJson(Dictionary<String, ?> props, boolean pretty) {
-               StringBuilder sb = new StringBuilder();
-               sb.append('{');
-               if (pretty)
-                       sb.append('\n');
-               Enumeration<String> keys = props.keys();
-               while (keys.hasMoreElements()) {
-                       String key = keys.nextElement();
-                       if (pretty)
-                               sb.append(' ');
-                       sb.append('\"').append(key).append('\"');
-                       if (pretty)
-                               sb.append(" : ");
-                       else
-                               sb.append(':');
-                       sb.append('\"').append(props.get(key)).append('\"');
-                       if (keys.hasMoreElements())
-                               sb.append(", ");
-                       if (pretty)
-                               sb.append('\n');
-               }
-               sb.append('}');
-               return sb.toString();
-       }
-
-       static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
-               if (props == null)
-                       throw new IllegalArgumentException("Props cannot be null");
-               Properties toStore = new Properties();
-               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                       String key = keys.nextElement();
-                       toStore.setProperty(key, props.get(key).toString());
-               }
-               try (OutputStream out = Files.newOutputStream(path)) {
-                       toStore.store(out, null);
-               }
-       }
-
-       static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
-                       throws IOException {
-               if (props == null)
-                       throw new IllegalArgumentException("Props cannot be null");
-               Object dnValue = props.get(dnKey);
-               String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
-               LdapName dn;
-               try {
-                       dn = new LdapName(dnStr);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
-               }
-               if (dnValue == null)
-                       throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
-               try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
-                       writer.append("\ndn: ");
-                       writer.append(dn.toString());
-                       writer.append('\n');
-                       for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
-                               String key = keys.nextElement();
-                               Object value = props.get(key);
-                               writer.append(key);
-                               writer.append(": ");
-                               // FIXME deal with binary and multiple values
-                               writer.append(value.toString());
-                               writer.append('\n');
-                       }
-               }
-       }
-
-       static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
-               Properties toLoad = new Properties();
-               try (InputStream in = Files.newInputStream(path)) {
-                       toLoad.load(in);
-               }
-               Dictionary<String, Object> res = new Hashtable<String, Object>();
-               for (Object key : toLoad.keySet())
-                       res.put(key.toString(), toLoad.get(key));
-               return res;
-       }
-
-       /*
-        * COLLECTIONS
-        */
-       /**
-        * Convert a comma-separated separated {@link String} or a {@link String} array
-        * to a {@link List} of {@link String}, trimming them. Useful to quickly
-        * interpret OSGi services properties.
-        * 
-        * @return a {@link List} containing the trimmed {@link String}s, or an empty
-        *         {@link List} if the argument was <code>null</code>.
-        */
-       public static List<String> toStringList(Object value) {
-               List<String> values = new ArrayList<>();
-               if (value == null)
-                       return values;
-               String[] arr;
-               if (value instanceof String) {
-                       arr = ((String) value).split(",");
-               } else if (value instanceof String[]) {
-                       arr = (String[]) value;
-               } else {
-                       throw new IllegalArgumentException("Unsupported value type " + value.getClass());
-               }
-               for (String str : arr) {
-                       values.add(str.trim());
-               }
-               return values;
-       }
-
-       /*
-        * EXCEPTIONS
-        */
-       /**
-        * Chain the messages of all causes (one per line, <b>starts with a line
-        * return</b>) without all the stack
-        */
-       public static String chainCausesMessages(Throwable t) {
-               StringBuffer buf = new StringBuffer();
-               chainCauseMessage(buf, t);
-               return buf.toString();
-       }
-
-       /** Recursive chaining of messages */
-       private static void chainCauseMessage(StringBuffer buf, Throwable t) {
-               buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
-               if (t.getCause() != null)
-                       chainCauseMessage(buf, t.getCause());
-       }
-
-       /*
-        * TIME
-        */
-       /** Formats time elapsed since start. */
-       public static String since(ZonedDateTime start) {
-               ZonedDateTime now = ZonedDateTime.now();
-               return duration(start, now);
-       }
-
-       /** Formats a duration. */
-       public static String duration(Temporal start, Temporal end) {
-               long count = ChronoUnit.DAYS.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " days" : count + " day";
-               count = ChronoUnit.HOURS.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " hours" : count + " hours";
-               count = ChronoUnit.MINUTES.between(start, end);
-               if (count != 0)
-                       return count > 1 ? count + " minutes" : count + " minute";
-               count = ChronoUnit.SECONDS.between(start, end);
-               return count > 1 ? count + " seconds" : count + " second";
-       }
-
-       /** Singleton constructor. */
-       private LangUtils() {
-
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/OS.java b/org.argeo.enterprise/src/org/argeo/util/OS.java
deleted file mode 100644 (file)
index d8127b6..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.argeo.util;
-
-import java.io.File;
-import java.lang.management.ManagementFactory;
-
-/** When OS specific informations are needed. */
-public class OS {
-       public final static OS LOCAL = new OS();
-
-       private final String arch, name, version;
-
-       /** The OS of the running JVM */
-       protected OS() {
-               arch = System.getProperty("os.arch");
-               name = System.getProperty("os.name");
-               version = System.getProperty("os.version");
-       }
-
-       public String getArch() {
-               return arch;
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       public String getVersion() {
-               return version;
-       }
-
-       public boolean isMSWindows() {
-               // only MS Windows would use such an horrendous separator...
-               return File.separatorChar == '\\';
-       }
-
-       public String[] getDefaultShellCommand() {
-               if (!isMSWindows())
-                       return new String[] { "/bin/sh", "-l", "-i" };
-               else
-                       return new String[] { "cmd.exe", "/C" };
-       }
-
-       public static Integer getJvmPid() {
-               /*
-                * This method works on most platforms (including Linux). Although when Java 9
-                * comes along, there is a better way: long pid =
-                * ProcessHandle.current().getPid();
-                *
-                * See:
-                * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-
-                * process-id
-                */
-               String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
-               return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/PasswordEncryption.java b/org.argeo.enterprise/src/org/argeo/util/PasswordEncryption.java
deleted file mode 100644 (file)
index c95c787..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-package org.argeo.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-public class PasswordEncryption {
-       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
-       /** Stronger with 256, but causes problem with Oracle JVM */
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
-       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
-       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
-       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
-       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
-//     public final static String DEFAULT_CHARSET = "UTF-8";
-       public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
-
-       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
-       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
-       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
-       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
-       private String cipherName = DEFAULT_CIPHER_NAME;
-
-       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
-                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
-
-       private Key key;
-       private Cipher ecipher;
-       private Cipher dcipher;
-
-       private String securityProviderName = null;
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copy of nor
-        * reference to the passed array is kept
-        */
-       public PasswordEncryption(char[] password) {
-               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
-       }
-
-       /**
-        * This is up to the caller to clear the passed array. Neither copies of nor
-        * references to the passed arrays are kept
-        */
-       public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
-               try {
-                       initKeyAndCiphers(password, passwordSalt, initializationVector);
-               } catch (InvalidKeyException e) {
-                       Integer previousSecreteKeyLength = secreteKeyLength;
-                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
-                       System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
-                                       + " secrete key length instead of " + previousSecreteKeyLength);
-                       try {
-                               initKeyAndCiphers(password, passwordSalt, initializationVector);
-                       } catch (GeneralSecurityException e1) {
-                               throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
-                       }
-               } catch (GeneralSecurityException e) {
-                       throw new IllegalStateException("Cannot get secret key", e);
-               }
-       }
-
-       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
-                       throws GeneralSecurityException {
-               byte[] salt = new byte[8];
-               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
-               // for (int i = 0; i < password.length && i < salt.length; i++)
-               // salt[i] = (byte) password[i];
-               byte[] iv = new byte[16];
-               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
-
-               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
-               PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
-               String secKeyEncryption = getSecretKeyEncryption();
-               if (secKeyEncryption != null) {
-                       SecretKey tmp = keyFac.generateSecret(keySpec);
-                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
-               } else {
-                       key = keyFac.generateSecret(keySpec);
-               }
-               if (securityProviderName != null)
-                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
-               else
-                       ecipher = Cipher.getInstance(getCipherName());
-               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
-               dcipher = Cipher.getInstance(getCipherName());
-               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
-       }
-
-       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
-               try {
-                       CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
-                       StreamUtils.copy(decryptedIn, out);
-                       StreamUtils.closeQuietly(out);
-               } catch (IOException e) {
-                       throw e;
-               } finally {
-                       StreamUtils.closeQuietly(decryptedIn);
-               }
-       }
-
-       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
-               try {
-                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
-                       StreamUtils.copy(decryptedIn, decryptedOut);
-               } catch (IOException e) {
-                       throw e;
-               } finally {
-                       StreamUtils.closeQuietly(encryptedIn);
-               }
-       }
-
-       public byte[] encryptString(String str) {
-               ByteArrayOutputStream out = null;
-               ByteArrayInputStream in = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
-                       encrypt(in, out);
-                       return out.toByteArray();
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(out);
-               }
-       }
-
-       /** Closes the input stream */
-       public String decryptAsString(InputStream in) {
-               ByteArrayOutputStream out = null;
-               try {
-                       out = new ByteArrayOutputStream();
-                       decrypt(in, out);
-                       return new String(out.toByteArray(), DEFAULT_CHARSET);
-               } catch (IOException e) {
-                       throw new RuntimeException(e);
-               } finally {
-                       StreamUtils.closeQuietly(out);
-               }
-       }
-
-       protected Key getKey() {
-               return key;
-       }
-
-       protected Cipher getEcipher() {
-               return ecipher;
-       }
-
-       protected Cipher getDcipher() {
-               return dcipher;
-       }
-
-       protected Integer getIterationCount() {
-               return iterationCount;
-       }
-
-       protected Integer getKeyLength() {
-               return secreteKeyLength;
-       }
-
-       protected String getSecretKeyFactoryName() {
-               return secreteKeyFactoryName;
-       }
-
-       protected String getSecretKeyEncryption() {
-               return secreteKeyEncryption;
-       }
-
-       protected String getCipherName() {
-               return cipherName;
-       }
-
-       public void setIterationCount(Integer iterationCount) {
-               this.iterationCount = iterationCount;
-       }
-
-       public void setSecreteKeyLength(Integer keyLength) {
-               this.secreteKeyLength = keyLength;
-       }
-
-       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
-               this.secreteKeyFactoryName = secreteKeyFactoryName;
-       }
-
-       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
-               this.secreteKeyEncryption = secreteKeyEncryption;
-       }
-
-       public void setCipherName(String cipherName) {
-               this.cipherName = cipherName;
-       }
-
-       public void setSecurityProviderName(String securityProviderName) {
-               this.securityProviderName = securityProviderName;
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/ServiceChannel.java b/org.argeo.enterprise/src/org/argeo/util/ServiceChannel.java
deleted file mode 100644 (file)
index 7997384..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.AsynchronousByteChannel;
-import java.nio.channels.CompletionHandler;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
-public class ServiceChannel implements AsynchronousByteChannel {
-       private final ReadableByteChannel in;
-       private final WritableByteChannel out;
-
-       private boolean open = true;
-
-       private ExecutorService executor;
-
-       public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
-               this.in = in;
-               this.out = out;
-               this.executor = executor;
-       }
-
-       @Override
-       public Future<Integer> read(ByteBuffer dst) {
-               return executor.submit(() -> in.read(dst));
-       }
-
-       @Override
-       public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
-               try {
-                       Future<Integer> res = read(dst);
-                       handler.completed(res.get(), attachment);
-               } catch (Exception e) {
-                       handler.failed(e, attachment);
-               }
-       }
-
-       @Override
-       public Future<Integer> write(ByteBuffer src) {
-               return executor.submit(() -> out.write(src));
-       }
-
-       @Override
-       public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
-               try {
-                       Future<Integer> res = write(src);
-                       handler.completed(res.get(), attachment);
-               } catch (Exception e) {
-                       handler.failed(e, attachment);
-               }
-       }
-
-       @Override
-       public synchronized void close() throws IOException {
-               try {
-                       in.close();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-               try {
-                       out.close();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-               open = false;
-               notifyAll();
-       }
-
-       @Override
-       public synchronized boolean isOpen() {
-               return open;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/StreamUtils.java b/org.argeo.enterprise/src/org/argeo/util/StreamUtils.java
deleted file mode 100644 (file)
index 6d7d940..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.argeo.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.io.Writer;
-
-/** Utilities to be used when Apache Commons IO is not available. */
-class StreamUtils {
-       private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
-
-       /*
-        * APACHE COMMONS IO (inspired)
-        */
-
-       /** @return the number of bytes */
-       public static Long copy(InputStream in, OutputStream out)
-                       throws IOException {
-               Long count = 0l;
-               byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
-               while (true) {
-                       int length = in.read(buf);
-                       if (length < 0)
-                               break;
-                       out.write(buf, 0, length);
-                       count = count + length;
-               }
-               return count;
-       }
-
-       /** @return the number of chars */
-       public static Long copy(Reader in, Writer out) throws IOException {
-               Long count = 0l;
-               char[] buf = new char[DEFAULT_BUFFER_SIZE];
-               while (true) {
-                       int length = in.read(buf);
-                       if (length < 0)
-                               break;
-                       out.write(buf, 0, length);
-                       count = count + length;
-               }
-               return count;
-       }
-
-       public static void closeQuietly(InputStream in) {
-               if (in != null)
-                       try {
-                               in.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(OutputStream out) {
-               if (out != null)
-                       try {
-                               out.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(Reader in) {
-               if (in != null)
-                       try {
-                               in.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-
-       public static void closeQuietly(Writer out) {
-               if (out != null)
-                       try {
-                               out.close();
-                       } catch (Exception e) {
-                               //
-                       }
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/Tester.java b/org.argeo.enterprise/src/org/argeo/util/Tester.java
deleted file mode 100644 (file)
index 31a2be4..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.argeo.util;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/** A generic tester based on Java assertions and functional programming. */
-public class Tester {
-       private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       private ClassLoader classLoader;
-
-       /** Use {@link Thread#getContextClassLoader()} by default. */
-       public Tester() {
-               this(Thread.currentThread().getContextClassLoader());
-       }
-
-       public Tester(ClassLoader classLoader) {
-               this.classLoader = classLoader;
-       }
-
-       public void execute(String className) {
-               Class<?> clss;
-               try {
-                       clss = classLoader.loadClass(className);
-                       boolean assertionsEnabled = clss.desiredAssertionStatus();
-                       if (!assertionsEnabled)
-                               throw new IllegalStateException("Test runner " + getClass().getName()
-                                               + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
-               } catch (Exception e1) {
-                       throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
-
-               }
-               List<Method> methods = findMethods(clss);
-               if (methods.size() == 0)
-                       throw new IllegalArgumentException("No test method found in " + clss);
-               // TODO make order more predictable?
-               for (Method method : methods) {
-                       String uid = method.getDeclaringClass().getName() + "#" + method.getName();
-                       TesterStatus testStatus = new TesterStatus(uid);
-                       Object obj = null;
-                       try {
-                               beforeTest(uid, method);
-                               obj = clss.getDeclaredConstructor().newInstance();
-                               method.invoke(obj);
-                               testStatus.setPassed();
-                               afterTestPassed(uid, method, obj);
-                       } catch (Exception e) {
-                               testStatus.setFailed(e);
-                               afterTestFailed(uid, method, obj, e);
-                       } finally {
-                               results.put(uid, testStatus);
-                       }
-               }
-       }
-
-       protected void beforeTest(String uid, Method method) {
-               // System.out.println(uid + ": STARTING");
-       }
-
-       protected void afterTestPassed(String uid, Method method, Object obj) {
-               System.out.println(uid + ": PASSED");
-       }
-
-       protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
-               System.out.println(uid + ": FAILED");
-               e.printStackTrace();
-       }
-
-       protected List<Method> findMethods(Class<?> clss) {
-               List<Method> methods = new ArrayList<Method>();
-//             Method call = getMethod(clss, "call");
-//             if (call != null)
-//                     methods.add(call);
-//
-               for (Method method : clss.getMethods()) {
-                       if (method.getName().startsWith("test")) {
-                               methods.add(method);
-                       }
-               }
-               return methods;
-       }
-
-       protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
-               try {
-                       return clss.getMethod(name, parameterTypes);
-               } catch (NoSuchMethodException e) {
-                       return null;
-               } catch (SecurityException e) {
-                       throw new IllegalStateException(e);
-               }
-       }
-
-       public static void main(String[] args) {
-               // deal with arguments
-               String className;
-               if (args.length < 1) {
-                       System.err.println(usage());
-                       System.exit(1);
-                       throw new IllegalArgumentException();
-               } else {
-                       className = args[0];
-               }
-
-               Tester test = new Tester();
-               try {
-                       test.execute(className);
-               } catch (Throwable e) {
-                       e.printStackTrace();
-               }
-
-               Map<String, TesterStatus> r = test.results;
-               for (String uid : r.keySet()) {
-                       TesterStatus testStatus = r.get(uid);
-                       System.out.println(testStatus);
-               }
-       }
-
-       public static String usage() {
-               return "java " + Tester.class.getName() + " [test class name]";
-
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/TesterStatus.java b/org.argeo.enterprise/src/org/argeo/util/TesterStatus.java
deleted file mode 100644 (file)
index d1d14ed..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.argeo.util;
-
-import java.io.Serializable;
-
-/** The status of a test. */
-public class TesterStatus implements Serializable {
-       private static final long serialVersionUID = 6272975746885487000L;
-
-       private Boolean passed = null;
-       private final String uid;
-       private Throwable throwable = null;
-
-       public TesterStatus(String uid) {
-               this.uid = uid;
-       }
-
-       /** For cloning. */
-       public TesterStatus(String uid, Boolean passed, Throwable throwable) {
-               this(uid);
-               this.passed = passed;
-               this.throwable = throwable;
-       }
-
-       public synchronized Boolean isRunning() {
-               return passed == null;
-       }
-
-       public synchronized Boolean isPassed() {
-               assert passed != null;
-               return passed;
-       }
-
-       public synchronized Boolean isFailed() {
-               assert passed != null;
-               return !passed;
-       }
-
-       public synchronized void setPassed() {
-               setStatus(true);
-       }
-
-       public synchronized void setFailed() {
-               setStatus(false);
-       }
-
-       public synchronized void setFailed(Throwable throwable) {
-               setStatus(false);
-               setThrowable(throwable);
-       }
-
-       protected void setStatus(Boolean passed) {
-               if (this.passed != null)
-                       throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
-               this.passed = passed;
-       }
-
-       protected void setThrowable(Throwable throwable) {
-               if (this.throwable != null)
-                       throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
-               this.throwable = throwable;
-       }
-
-       public String getUid() {
-               return uid;
-       }
-
-       public Throwable getThrowable() {
-               return throwable;
-       }
-
-       @Override
-       protected Object clone() throws CloneNotSupportedException {
-               // TODO Auto-generated method stub
-               return super.clone();
-       }
-
-       @Override
-       public boolean equals(Object o) {
-               if (o instanceof TesterStatus) {
-                       TesterStatus other = (TesterStatus) o;
-                       // we don't check consistency for performance purposes
-                       // this equals() is supposed to be used in collections or for transfer
-                       return other.uid.equals(uid);
-               }
-               return false;
-       }
-
-       @Override
-       public int hashCode() {
-               return uid.hashCode();
-       }
-
-       @Override
-       public String toString() {
-               return uid + "\t" + (passed ? "passed" : "failed");
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/Throughput.java b/org.argeo.enterprise/src/org/argeo/util/Throughput.java
deleted file mode 100644 (file)
index 266ddbc..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-package org.argeo.util;
-
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.Locale;
-
-/** A throughput, that is, a value per unit of time. */
-public class Throughput {
-       private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
-
-       public enum Unit {
-               s, m, h, d
-       }
-
-       private final Double value;
-       private final Unit unit;
-
-       public Throughput(Double value, Unit unit) {
-               this.value = value;
-               this.unit = unit;
-       }
-
-       public Throughput(Long periodMs, Long count, Unit unit) {
-               if (unit.equals(Unit.s))
-                       value = ((double) count * 1000d) / periodMs;
-               else if (unit.equals(Unit.m))
-                       value = ((double) count * 60d * 1000d) / periodMs;
-               else if (unit.equals(Unit.h))
-                       value = ((double) count * 60d * 60d * 1000d) / periodMs;
-               else if (unit.equals(Unit.d))
-                       value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
-               else
-                       throw new IllegalArgumentException("Unsupported unit " + unit);
-               this.unit = unit;
-       }
-
-       public Throughput(Double value, String unitStr) {
-               this(value, Unit.valueOf(unitStr));
-       }
-
-       public Throughput(String def) {
-               int index = def.indexOf('/');
-               if (def.length() < 3 || index <= 0 || index != def.length() - 2)
-                       throw new IllegalArgumentException(
-                                       def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
-               String valueStr = def.substring(0, index);
-               String unitStr = def.substring(index + 1);
-               try {
-                       this.value = usNumberFormat.parse(valueStr).doubleValue();
-               } catch (ParseException e) {
-                       throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
-               }
-               this.unit = Unit.valueOf(unitStr);
-       }
-
-       public Long asMsPeriod() {
-               if (unit.equals(Unit.s))
-                       return Math.round(1000d / value);
-               else if (unit.equals(Unit.m))
-                       return Math.round((60d * 1000d) / value);
-               else if (unit.equals(Unit.h))
-                       return Math.round((60d * 60d * 1000d) / value);
-               else if (unit.equals(Unit.d))
-                       return Math.round((24d * 60d * 60d * 1000d) / value);
-               else
-                       throw new IllegalArgumentException("Unsupported unit " + unit);
-       }
-
-       @Override
-       public String toString() {
-               return usNumberFormat.format(value) + '/' + unit;
-       }
-
-       public Double getValue() {
-               return value;
-       }
-
-       public Unit getUnit() {
-               return unit;
-       }
-
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/UuidUtils.java b/org.argeo.enterprise/src/org/argeo/util/UuidUtils.java
deleted file mode 100644 (file)
index 7584abc..0000000
+++ /dev/null
@@ -1,378 +0,0 @@
-package org.argeo.util;
-
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.security.SecureRandom;
-import java.time.Duration;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.BitSet;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Utilities to simplify and extends usage of {@link UUID}. Only the RFC 4122
- * variant (also known as Leach–Salz variant) is supported.
- */
-public class UuidUtils {
-       /** Nil UUID (00000000-0000-0000-0000-000000000000). */
-       public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
-       public final static LocalDateTime GREGORIAN_START = LocalDateTime.of(1582, 10, 15, 0, 0, 0);
-
-       private final static long MOST_SIG_VERSION1 = (1l << 12);
-       private final static long LEAST_SIG_RFC4122_VARIANT = (1l << 63);
-
-       private final static SecureRandom RANDOM;
-       private final static AtomicInteger CLOCK_SEQUENCE;
-       private final static byte[] HARDWARE_ADDRESS;
-       /** A start timestamp to which {@link System#nanoTime()}/100 can be added. */
-       private final static long START_TIMESTAMP;
-       static {
-               RANDOM = new SecureRandom();
-               CLOCK_SEQUENCE = new AtomicInteger(RANDOM.nextInt(16384));
-               HARDWARE_ADDRESS = getHardwareAddress();
-
-               long nowVm = System.nanoTime() / 100;
-               Duration duration = Duration.between(GREGORIAN_START, LocalDateTime.now(ZoneOffset.UTC));
-               START_TIMESTAMP = (duration.getSeconds() * 10000000 + duration.getNano() / 100) - nowVm;
-       }
-
-       private static byte[] getHardwareAddress() {
-               InetAddress localHost;
-               try {
-                       localHost = InetAddress.getLocalHost();
-                       try {
-                               NetworkInterface nic = NetworkInterface.getByInetAddress(localHost);
-                               return nic.getHardwareAddress();
-                       } catch (SocketException e) {
-                               return null;
-                       }
-               } catch (UnknownHostException e) {
-                       return null;
-               }
-
-       }
-
-       public static UUID timeUUIDwithRandomNode() {
-               long timestamp = START_TIMESTAMP + System.nanoTime() / 100;
-               return timeUUID(timestamp, RANDOM);
-       }
-
-       public static UUID timeUUID(long timestamp, Random random) {
-               byte[] node = new byte[6];
-               random.nextBytes(node);
-               node[0] = (byte) (node[0] | 1);
-               long clockSequence = CLOCK_SEQUENCE.incrementAndGet();
-               return timeUUID(timestamp, clockSequence, node);
-       }
-
-       public static UUID timeUUID() {
-               long timestamp = START_TIMESTAMP + System.nanoTime() / 100;
-               return timeUUID(timestamp);
-       }
-
-       public static UUID timeUUID(long timestamp) {
-               if (HARDWARE_ADDRESS == null)
-                       return timeUUID(timestamp, RANDOM);
-               long clockSequence = CLOCK_SEQUENCE.incrementAndGet();
-               return timeUUID(timestamp, clockSequence, HARDWARE_ADDRESS);
-       }
-
-       public static UUID timeUUID(long timestamp, NetworkInterface nic) {
-               byte[] node;
-               try {
-                       node = nic.getHardwareAddress();
-               } catch (SocketException e) {
-                       throw new IllegalStateException("Cannot get hardware address", e);
-               }
-               long clockSequence = CLOCK_SEQUENCE.incrementAndGet();
-               return timeUUID(timestamp, clockSequence, node);
-       }
-
-       public static UUID timeUUID(LocalDateTime time, long clockSequence, byte[] node) {
-               Duration duration = Duration.between(GREGORIAN_START, time);
-               // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
-               long timestamp = duration.getSeconds() * 10000000 + duration.getNano() / 100;
-               return timeUUID(timestamp, clockSequence, node);
-       }
-
-       public static UUID timeUUID(long timestamp, long clockSequence, byte[] node) {
-               assert node.length >= 6;
-
-               long mostSig = MOST_SIG_VERSION1 // base for version 1 UUID
-                               | ((timestamp & 0xFFFFFFFFL) << 32) // time_low
-                               | (((timestamp >> 32) & 0xFFFFL) << 16) // time_mid
-                               | ((timestamp >> 48) & 0x0FFFL);// time_hi_and_version
-
-               long leastSig = LEAST_SIG_RFC4122_VARIANT // base for Leach–Salz UUID
-                               | (((clockSequence & 0x3F00) >> 8) << 56) // clk_seq_hi_res
-                               | ((clockSequence & 0xFF) << 48) // clk_seq_low
-                               | (node[0] & 0xFFL) //
-                               | ((node[1] & 0xFFL) << 8) //
-                               | ((node[2] & 0xFFL) << 16) //
-                               | ((node[3] & 0xFFL) << 24) //
-                               | ((node[4] & 0xFFL) << 32) //
-                               | ((node[5] & 0xFFL) << 40); //
-//             for (int i = 0; i < 6; i++) {
-//                     leastSig = leastSig | ((node[i] & 0xFFL) << (8 * i));
-//             }
-               UUID uuid = new UUID(mostSig, leastSig);
-
-               // tests
-               assert uuid.node() == BitSet.valueOf(node).toLongArray()[0];
-               assert uuid.timestamp() == timestamp;
-               assert uuid.clockSequence() == clockSequence;
-               assert uuid.version() == 1;
-               assert uuid.variant() == 2;
-               return uuid;
-       }
-
-       @Deprecated
-       public static UUID timeBasedUUID() {
-               return timeBasedUUID(LocalDateTime.now(ZoneOffset.UTC));
-       }
-
-       @Deprecated
-       public static UUID timeBasedRandomUUID() {
-               return timeBasedRandomUUID(LocalDateTime.now(ZoneOffset.UTC), RANDOM);
-       }
-
-       @Deprecated
-       public static UUID timeBasedUUID(LocalDateTime time) {
-               if (HARDWARE_ADDRESS == null)
-                       return timeBasedRandomUUID(time, RANDOM);
-               return timeBasedUUID(time, BitSet.valueOf(HARDWARE_ADDRESS));
-       }
-
-       @Deprecated
-       public static UUID timeBasedAddressUUID(LocalDateTime time, NetworkInterface nic) throws SocketException {
-               byte[] nodeBytes = nic.getHardwareAddress();
-               BitSet node = BitSet.valueOf(nodeBytes);
-               return timeBasedUUID(time, node);
-       }
-
-       @Deprecated
-       public static UUID timeBasedRandomUUID(LocalDateTime time, Random random) {
-               byte[] nodeBytes = new byte[6];
-               random.nextBytes(nodeBytes);
-               BitSet node = BitSet.valueOf(nodeBytes);
-               // set random marker
-               node.set(0, true);
-               return timeBasedUUID(time, node);
-       }
-
-       @Deprecated
-       public static UUID timeBasedUUID(LocalDateTime time, BitSet node) {
-               // most significant
-               Duration duration = Duration.between(GREGORIAN_START, time);
-
-               // Number of 100 ns intervals in one second: 1000000000 / 100 = 10000000
-               long timeNanos = duration.getSeconds() * 10000000 + duration.getNano() / 100;
-               BitSet timeBits = BitSet.valueOf(new long[] { timeNanos });
-               assert timeBits.length() <= 60;
-
-               int clockSequence;
-               synchronized (CLOCK_SEQUENCE) {
-                       clockSequence = CLOCK_SEQUENCE.incrementAndGet();
-                       if (clockSequence > 16384)
-                               CLOCK_SEQUENCE.set(0);
-               }
-               BitSet clockSequenceBits = BitSet.valueOf(new long[] { clockSequence });
-
-               // Build the UUID, bit by bit
-               // see https://tools.ietf.org/html/rfc4122#section-4.2.2
-               // time
-               BitSet time_low = new BitSet(32);
-               BitSet time_mid = new BitSet(16);
-               BitSet time_hi_and_version = new BitSet(16);
-
-               for (int i = 0; i < 60; i++) {
-                       if (i < 32)
-                               time_low.set(i, timeBits.get(i));
-                       else if (i < 48)
-                               time_mid.set(i - 32, timeBits.get(i));
-                       else
-                               time_hi_and_version.set(i - 48, timeBits.get(i));
-               }
-               // version
-               time_hi_and_version.set(12, true);
-               time_hi_and_version.set(13, false);
-               time_hi_and_version.set(14, false);
-               time_hi_and_version.set(15, false);
-
-               // clock sequence
-               BitSet clk_seq_hi_res = new BitSet(8);
-               BitSet clk_seq_low = new BitSet(8);
-               for (int i = 0; i < 8; i++) {
-                       clk_seq_low.set(i, clockSequenceBits.get(i));
-               }
-               for (int i = 8; i < 14; i++) {
-                       clk_seq_hi_res.set(i - 8, clockSequenceBits.get(i));
-               }
-               // variant
-               clk_seq_hi_res.set(6, false);
-               clk_seq_hi_res.set(7, true);
-
-//             String str = toHexString(time_low.toLongArray()[0]) + "-" + toHexString(time_mid.toLongArray()[0]) + "-"
-//                             + toHexString(time_hi_and_version.toLongArray()[0]) + "-"
-//                             + toHexString(clock_seq_hi_and_reserved.toLongArray()[0]) + toHexString(clock_seq_low.toLongArray()[0])
-//                             + "-" + toHexString(node.toLongArray()[0]);
-//             UUID uuid = UUID.fromString(str);
-
-               BitSet uuidBits = new BitSet(128);
-               for (int i = 0; i < 128; i++) {
-                       if (i < 48)
-                               uuidBits.set(i, node.get(i));
-                       else if (i < 56)
-                               uuidBits.set(i, clk_seq_low.get(i - 48));
-                       else if (i < 64)
-                               uuidBits.set(i, clk_seq_hi_res.get(i - 56));
-                       else if (i < 80)
-                               uuidBits.set(i, time_hi_and_version.get(i - 64));
-                       else if (i < 96)
-                               uuidBits.set(i, time_mid.get(i - 80));
-                       else
-                               uuidBits.set(i, time_low.get(i - 96));
-               }
-
-               long[] uuidLongs = uuidBits.toLongArray();
-               assert uuidLongs.length == 2;
-               UUID uuid = new UUID(uuidLongs[1], uuidLongs[0]);
-
-               // tests
-               assert uuid.node() == node.toLongArray()[0];
-               assert uuid.timestamp() == timeNanos;
-               assert uuid.clockSequence() == clockSequence;
-               assert uuid.version() == 1;
-               assert uuid.variant() == 2;
-               return uuid;
-       }
-
-       public static String toBinaryString(UUID uuid, int charsPerSegment, char separator) {
-               String binaryString = toBinaryString(uuid);
-               StringBuilder sb = new StringBuilder(128 + (128 / charsPerSegment));
-               for (int i = 0; i < binaryString.length(); i++) {
-                       if (i != 0 && i % charsPerSegment == 0)
-                               sb.append(separator);
-                       sb.append(binaryString.charAt(i));
-               }
-               return sb.toString();
-       }
-
-       public static String toBinaryString(UUID uuid) {
-               String most = zeroTo64Chars(Long.toBinaryString(uuid.getMostSignificantBits()));
-               String least = zeroTo64Chars(Long.toBinaryString(uuid.getLeastSignificantBits()));
-               String binaryString = most + least;
-               assert binaryString.length() == 128;
-               return binaryString;
-       }
-
-       private static String zeroTo64Chars(String str) {
-               assert str.length() <= 64;
-               if (str.length() < 64) {
-                       StringBuilder sb = new StringBuilder(64);
-                       for (int i = 0; i < 64 - str.length(); i++)
-                               sb.append('0');
-                       sb.append(str);
-                       return sb.toString();
-               } else
-                       return str;
-       }
-
-       public static String compactToStd(String compact) {
-               if (compact.length() != 32)
-                       throw new IllegalArgumentException(
-                                       "Compact UUID '" + compact + "' has length " + compact.length() + " and not 32.");
-               StringBuilder sb = new StringBuilder(36);
-               for (int i = 0; i < 32; i++) {
-                       if (i == 8 || i == 12 || i == 16 || i == 20)
-                               sb.append('-');
-                       sb.append(compact.charAt(i));
-               }
-               String std = sb.toString();
-               assert std.length() == 36;
-               assert UUID.fromString(std).toString().equals(std);
-               return std;
-       }
-
-       public static UUID compactToUuid(String compact) {
-               return UUID.fromString(compactToStd(compact));
-       }
-       
-       public static String firstBlock(UUID uuid) {
-               return uuid.toString().substring(0, 8);
-       }
-
-       public static boolean isRandom(UUID uuid) {
-               return uuid.version() == 4;
-       }
-
-       public static boolean isTimeBased(UUID uuid) {
-               return uuid.version() == 1;
-       }
-
-       public static boolean isTimeBasedRandom(UUID uuid) {
-               if (uuid.version() == 1) {
-                       BitSet node = BitSet.valueOf(new long[] { uuid.node() });
-                       return node.get(0);
-               } else
-                       return false;
-       }
-
-       public static boolean isNameBased(UUID uuid) {
-               return uuid.version() == 3 || uuid.version() == 5;
-       }
-
-       /** Singleton. */
-       private UuidUtils() {
-       }
-
-       public final static void main(String[] args) throws Exception {
-               UUID uuid;
-
-//             uuid = compactToUuid("996b1f5122de4b2f94e49168d32f22d1");
-//             System.out.println(uuid.toString() + ", isRandom=" + isRandom(uuid));
-
-               // warm up before measuring perf
-               for (int i = 0; i < 10; i++) {
-                       UUID.randomUUID();
-                       timeUUID();
-                       timeUUIDwithRandomNode();
-                       timeBasedRandomUUID();
-                       timeBasedUUID();
-               }
-
-               long begin;
-               long duration;
-
-               begin = System.nanoTime();
-               uuid = UUID.randomUUID();
-               duration = System.nanoTime() - begin;
-               System.out.println(uuid.toString() + " in " + duration + " ns, isRandom=" + isRandom(uuid));
-
-               begin = System.nanoTime();
-               uuid = timeUUID();
-               duration = System.nanoTime() - begin;
-               System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
-
-               begin = System.nanoTime();
-               uuid = timeUUIDwithRandomNode();
-               duration = System.nanoTime() - begin;
-               System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
-
-               begin = System.nanoTime();
-               uuid = timeBasedUUID();
-               duration = System.nanoTime() - begin;
-               System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
-
-               begin = System.nanoTime();
-               uuid = timeBasedRandomUUID();
-               duration = System.nanoTime() - begin;
-               System.out.println(uuid.toString() + " in " + duration + " ns, isTimeBasedRandom=" + isTimeBasedRandom(uuid));
-//             System.out.println(toBinaryString(uuid, 8, ' '));
-//             System.out.println(toBinaryString(uuid, 16, '\n'));
-       }
-}
diff --git a/org.argeo.enterprise/src/org/argeo/util/package-info.java b/org.argeo.enterprise/src/org/argeo/util/package-info.java
deleted file mode 100644 (file)
index 4354b0a..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generic Java utilities. */
-package org.argeo.util;
\ No newline at end of file
diff --git a/org.argeo.init/.classpath b/org.argeo.init/.classpath
new file mode 100644 (file)
index 0000000..71eb167
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+               <attributes>
+                       <attribute name="module" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.init/.project b/org.argeo.init/.project
new file mode 100644 (file)
index 0000000..13443df
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.init</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>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.init/META-INF/.gitignore b/org.argeo.init/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/org.argeo.init/bnd.bnd b/org.argeo.init/bnd.bnd
new file mode 100644 (file)
index 0000000..3dbe683
--- /dev/null
@@ -0,0 +1,8 @@
+Main-Class: org.argeo.init.Service
+Class-Path: org.eclipse.osgi.jar
+
+Bundle-Activator: org.argeo.init.osgi.Activator
+
+Import-Package: \
+org.osgi.*;version=0.0.0,\
+java.util.logging;resolution:=optional
diff --git a/org.argeo.init/build.properties b/org.argeo.init/build.properties
new file mode 100644 (file)
index 0000000..ae2abc5
--- /dev/null
@@ -0,0 +1 @@
+source.. = src/
\ No newline at end of file
diff --git a/org.argeo.init/src/META-INF/services/java.lang.System$LoggerFinder b/org.argeo.init/src/META-INF/services/java.lang.System$LoggerFinder
new file mode 100644 (file)
index 0000000..f63dfce
--- /dev/null
@@ -0,0 +1 @@
+org.argeo.init.logging.ThinLoggerFinder
\ No newline at end of file
diff --git a/org.argeo.init/src/org/argeo/init/RuntimeContext.java b/org.argeo.init/src/org/argeo/init/RuntimeContext.java
new file mode 100644 (file)
index 0000000..7dd8e6c
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.init;
+
+public interface RuntimeContext extends Runnable, AutoCloseable {
+       void waitForStop(long timeout) throws InterruptedException;
+}
diff --git a/org.argeo.init/src/org/argeo/init/Service.java b/org.argeo.init/src/org/argeo/init/Service.java
new file mode 100644 (file)
index 0000000..ef65d02
--- /dev/null
@@ -0,0 +1,67 @@
+package org.argeo.init;
+
+import java.lang.System.Logger;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.init.osgi.OsgiRuntimeContext;
+
+/** Configure and launch an Argeo service. */
+public class Service implements Runnable, AutoCloseable {
+       private final static Logger log = System.getLogger(Service.class.getName());
+
+       private static RuntimeContext runtimeContext = null;
+
+       protected Service(String[] args) {
+       }
+
+       @Override
+       public void run() {
+       }
+
+       @Override
+       public void close() throws Exception {
+       }
+
+       public static void main(String[] args) {
+               long pid = ProcessHandle.current().pid();
+               log.log(Logger.Level.DEBUG, "Starting with PID " + pid);
+
+               // shutdown on exit
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                       try {
+                               if (Service.runtimeContext != null) {
+                                       Service.runtimeContext.close();
+                                       //Service.runtimeContext.waitForStop(0);
+                               }
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                               System.exit(1);
+                       }
+               }, "Runtime shutdown"));
+
+               Map<String, String> config = new HashMap<>();
+               config.put("osgi.framework.useSystemProperties", "true");
+//             for (Object key : System.getProperties().keySet()) {
+//                     config.put(key.toString(), System.getProperty(key.toString()));
+//                     log.log(Logger.Level.DEBUG, key + "=" + System.getProperty(key.toString()));
+//             }
+               try {
+                       try (OsgiRuntimeContext osgiRuntimeContext = new OsgiRuntimeContext((Map<String, String>) config)) {
+                               osgiRuntimeContext.run();
+                               Service.runtimeContext = osgiRuntimeContext;
+                               Service.runtimeContext.waitForStop(0);
+                       } catch (NoClassDefFoundError e) {
+                               try (StaticRuntimeContext staticRuntimeContext = new StaticRuntimeContext((Map<String, String>) config)) {
+                                       staticRuntimeContext.run();
+                                       Service.runtimeContext = staticRuntimeContext;
+                                       Service.runtimeContext.waitForStop(0);
+                               }
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java b/org.argeo.init/src/org/argeo/init/StaticRuntimeContext.java
new file mode 100644 (file)
index 0000000..e01e619
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.init;
+
+import java.util.Map;
+
+public class StaticRuntimeContext implements RuntimeContext {
+       private Map<String, String> config;
+
+       private boolean running = false;
+
+       protected StaticRuntimeContext(Map<String, String> config) {
+               this.config = config;
+       }
+
+       @Override
+       public synchronized void run() {
+               running = true;
+               notifyAll();
+       }
+
+       @Override
+       public void waitForStop(long timeout) throws InterruptedException {
+               long begin = System.currentTimeMillis();
+               while (running && (timeout == 0 || System.currentTimeMillis() - begin < timeout)) {
+                       synchronized (this) {
+                               wait(500);
+                       }
+               }
+       }
+
+       @Override
+       public synchronized void close() throws Exception {
+               running = false;
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Branch.java b/org.argeo.init/src/org/argeo/init/a2/A2Branch.java
new file mode 100644 (file)
index 0000000..9713d01
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.init.a2;
+
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Version;
+
+/**
+ * A logical linear sequence of versions of a given {@link A2Component}. This is
+ * typically a combination of major and minor version, indicating backward
+ * compatibility.
+ */
+public class A2Branch implements Comparable<A2Branch> {
+       private final A2Component component;
+       private final String id;
+
+       final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public A2Branch(A2Component component, String id) {
+               this.component = component;
+               this.id = id;
+               component.branches.put(id, this);
+       }
+
+       A2Module getOrAddModule(Version version, Object locator) {
+               if (modules.containsKey(version)) {
+                       A2Module res = modules.get(version);
+                       if (OsgiBootUtils.isDebug() && !res.getLocator().equals(locator)) {
+                               OsgiBootUtils.debug("Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")");
+                       }
+                       return res;
+               } else
+                       return new A2Module(this, version, locator);
+       }
+
+       A2Module last() {
+               return modules.get(modules.lastKey());
+       }
+
+       A2Module first() {
+               return modules.get(modules.firstKey());
+       }
+
+       A2Component getComponent() {
+               return component;
+       }
+
+       String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Branch o) {
+               return id.compareTo(id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Branch) {
+                       A2Branch o = (A2Branch) obj;
+                       return component.equals(o.component) && id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return getCoordinates();
+       }
+
+       public String getCoordinates() {
+               return component + ":" + id;
+       }
+
+       static String versionToBranchId(Version version) {
+               return version.getMajor() + "." + version.getMinor();
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Component.java b/org.argeo.init/src/org/argeo/init/a2/A2Component.java
new file mode 100644 (file)
index 0000000..2b6814f
--- /dev/null
@@ -0,0 +1,100 @@
+package org.argeo.init.a2;
+
+import java.util.Collections;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.osgi.framework.Version;
+
+/**
+ * The logical name of a software package. In OSGi's case this is
+ * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
+ * id.
+ */
+public class A2Component implements Comparable<A2Component> {
+       private final A2Contribution contribution;
+       private final String id;
+
+       final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public A2Component(A2Contribution contribution, String id) {
+               this.contribution = contribution;
+               this.id = id;
+               contribution.components.put(id, this);
+       }
+
+       A2Branch getOrAddBranch(String branchId) {
+               if (branches.containsKey(branchId))
+                       return branches.get(branchId);
+               else
+                       return new A2Branch(this, branchId);
+       }
+
+       A2Module getOrAddModule(Version version, Object locator) {
+               A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version));
+               A2Module module = branch.getOrAddModule(version, locator);
+               return module;
+       }
+
+       A2Branch last() {
+               return branches.get(branches.lastKey());
+       }
+
+       A2Contribution getContribution() {
+               return contribution;
+       }
+
+       String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Component o) {
+               return id.compareTo(o.id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Component) {
+                       A2Component o = (A2Component) obj;
+                       return contribution.equals(o.contribution) && id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return contribution.getId() + ":" + id;
+       }
+
+       void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               A2Branch lastBranch = last();
+               SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
+               displayMap.putAll(branches);
+               for (String branchId : displayMap.keySet()) {
+                       A2Branch branch = displayMap.get(branchId);
+                       if (!lastBranch.equals(branch)) {
+                               buf.append('\n');
+                               buf.append(prefix);
+                       } else {
+                               buf.append(" -");
+                       }
+                       buf.append(prefix);
+                       buf.append(branchId);
+                       A2Module first = branch.first();
+                       A2Module last = branch.last();
+                       buf.append(" (").append(last.getVersion());
+                       if (!first.equals(last))
+                               buf.append(" ... ").append(first.getVersion());
+                       buf.append(')');
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java b/org.argeo.init/src/org/argeo/init/a2/A2Contribution.java
new file mode 100644 (file)
index 0000000..3d33b55
--- /dev/null
@@ -0,0 +1,84 @@
+package org.argeo.init.a2;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A category grouping a set of {@link A2Component}, typically based on the
+ * provider of these components. This is the equivalent of Maven's group Id.
+ */
+public class A2Contribution implements Comparable<A2Contribution> {
+       final static String BOOT = "boot";
+       final static String RUNTIME = "runtime";
+       final static String CLASSPATH = "classpath";
+
+       private final ProvisioningSource source;
+       private final String id;
+
+       final Map<String, A2Component> components = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       /**
+        * The contribution must be added to the source. Rather use
+        * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this
+        * contructor directly.
+        */
+       public A2Contribution(ProvisioningSource context, String id) {
+               this.source = context;
+               this.id = id;
+//             if (context != null)
+//                     context.contributions.put(id, this);
+       }
+
+       A2Component getOrAddComponent(String componentId) {
+               if (components.containsKey(componentId))
+                       return components.get(componentId);
+               else
+                       return new A2Component(this, componentId);
+       }
+
+       public ProvisioningSource getSource() {
+               return source;
+       }
+
+       public String getId() {
+               return id;
+       }
+
+       @Override
+       public int compareTo(A2Contribution o) {
+               return id.compareTo(o.id);
+       }
+
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Contribution) {
+                       A2Contribution o = (A2Contribution) obj;
+                       return id.equals(o.id);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return id;
+       }
+
+       void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               for (String componentId : components.keySet()) {
+                       buf.append(prefix);
+                       buf.append(componentId);
+                       A2Component component = components.get(componentId);
+                       component.asTree(prefix, buf);
+                       buf.append('\n');
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Exception.java b/org.argeo.init/src/org/argeo/init/a2/A2Exception.java
new file mode 100644 (file)
index 0000000..6ba87a7
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.init.a2;
+
+/** Unchecked A2 provisioning exception. */
+public class A2Exception extends RuntimeException {
+       private static final long serialVersionUID = 1927603558545397360L;
+
+       public A2Exception(String message, Throwable e) {
+               super(message, e);
+       }
+
+       public A2Exception(String message) {
+               super(message);
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Module.java b/org.argeo.init/src/org/argeo/init/a2/A2Module.java
new file mode 100644 (file)
index 0000000..b862c54
--- /dev/null
@@ -0,0 +1,62 @@
+package org.argeo.init.a2;
+
+import org.osgi.framework.Version;
+
+/**
+ * An identified software package. In OSGi's case this is the combination of
+ * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
+ * equivalent of the full coordinates of a Maven artifact version.
+ */
+class A2Module implements Comparable<A2Module> {
+       private final A2Branch branch;
+       private final Version version;
+       private final Object locator;
+
+       public A2Module(A2Branch branch, Version version, Object locator) {
+               this.branch = branch;
+               this.version = version;
+               this.locator = locator;
+               branch.modules.put(version, this);
+       }
+
+       A2Branch getBranch() {
+               return branch;
+       }
+
+       Version getVersion() {
+               return version;
+       }
+
+       Object getLocator() {
+               return locator;
+       }
+
+       @Override
+       public int compareTo(A2Module o) {
+               return version.compareTo(o.version);
+       }
+
+       @Override
+       public int hashCode() {
+               return version.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof A2Module) {
+                       A2Module o = (A2Module) obj;
+                       return branch.equals(o.branch) && version.equals(o.version);
+               } else
+                       return false;
+       }
+
+       @Override
+       public String toString() {
+               return getCoordinates();
+       }
+
+       public String getCoordinates() {
+               return branch.getComponent() + ":" + version;
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/A2Source.java b/org.argeo.init/src/org/argeo/init/a2/A2Source.java
new file mode 100644 (file)
index 0000000..388a850
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.init.a2;
+
+/** A provisioning source in A2 format. */
+public interface A2Source extends ProvisioningSource {
+       final static String SCHEME_A2 = "a2";
+       final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/AbstractProvisioningSource.java
new file mode 100644 (file)
index 0000000..f43a616
--- /dev/null
@@ -0,0 +1,212 @@
+package org.argeo.init.a2;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Collections;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+/** Where components are retrieved from. */
+public abstract class AbstractProvisioningSource implements ProvisioningSource {
+       protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public Iterable<A2Contribution> listContributions(Object filter) {
+               return contributions.values();
+       }
+
+       @Override
+       public Bundle install(BundleContext bc, A2Module module) {
+               try {
+                       Path tempJar = null;
+                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
+                               tempJar = toTempJar((Path) module.getLocator());
+                       Bundle bundle;
+                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
+                               bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+                       }
+                       if (tempJar != null)
+                               Files.deleteIfExists(tempJar);
+                       return bundle;
+               } catch (BundleException | IOException e) {
+                       throw new A2Exception("Cannot install module " + module, e);
+               }
+       }
+
+       @Override
+       public void update(Bundle bundle, A2Module module) {
+               try {
+                       Path tempJar = null;
+                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
+                               tempJar = toTempJar((Path) module.getLocator());
+                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
+                               bundle.update(in);
+                       }
+                       if (tempJar != null)
+                               Files.deleteIfExists(tempJar);
+               } catch (BundleException | IOException e) {
+                       throw new A2Exception("Cannot update module " + module, e);
+               }
+       }
+
+       @Override
+       public A2Branch findBranch(String componentId, Version version) {
+               A2Component component = findComponent(componentId);
+               if (component == null)
+                       return null;
+               String branchId = version.getMajor() + "." + version.getMinor();
+               if (!component.branches.containsKey(branchId))
+                       return null;
+               return component.branches.get(branchId);
+       }
+
+       protected A2Contribution getOrAddContribution(String contributionId) {
+               if (contributions.containsKey(contributionId))
+                       return contributions.get(contributionId);
+               else {
+                       A2Contribution contribution = new A2Contribution(this, contributionId);
+                       contributions.put(contributionId, contribution);
+                       return contribution;
+               }
+       }
+
+       protected void asTree(String prefix, StringBuffer buf) {
+               if (prefix == null)
+                       prefix = "";
+               for (String contributionId : contributions.keySet()) {
+                       buf.append(prefix);
+                       buf.append(contributionId);
+                       buf.append('\n');
+                       A2Contribution contribution = contributions.get(contributionId);
+                       contribution.asTree(prefix + " ", buf);
+               }
+       }
+
+       protected void asTree() {
+               StringBuffer buf = new StringBuffer();
+               asTree("", buf);
+               System.out.println(buf);
+       }
+
+       protected A2Component findComponent(String componentId) {
+               SortedMap<A2Contribution, A2Component> res = new TreeMap<>();
+               for (A2Contribution contribution : contributions.values()) {
+                       components: for (String componentIdKey : contribution.components.keySet()) {
+                               if (componentId.equals(componentIdKey)) {
+                                       res.put(contribution, contribution.components.get(componentIdKey));
+                                       break components;
+                               }
+                       }
+               }
+               if (res.size() == 0)
+                       return null;
+               // TODO explicit contribution priorities
+               return res.get(res.lastKey());
+
+       }
+
+       protected String readVersionFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+               return versionStr;
+       }
+
+       protected String readSymbolicNameFromModule(Path modulePath) {
+               Manifest manifest;
+               if (Files.isDirectory(modulePath)) {
+                       manifest = findManifest(modulePath);
+               } else {
+                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
+                               manifest = in.getManifest();
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
+                       }
+               }
+               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+               int semiColIndex = symbolicName.indexOf(';');
+               if (semiColIndex >= 0)
+                       symbolicName = symbolicName.substring(0, semiColIndex);
+               return symbolicName;
+       }
+
+       private static Manifest findManifest(Path currentPath) {
+               Path metaInfPath = currentPath.resolve("META-INF");
+               if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
+                       Path manifestPath = metaInfPath.resolve("MANIFEST.MF");
+                       try {
+                               try (InputStream in = Files.newInputStream(manifestPath)) {
+                                       Manifest manifest = new Manifest(in);
+                                       return manifest;
+                               }
+                       } catch (IOException e) {
+                               throw new A2Exception("Cannot read manifest from " + manifestPath, e);
+                       }
+               } else {
+                       Path parentPath = currentPath.getParent();
+                       if (parentPath == null)
+                               throw new A2Exception("MANIFEST.MF file not found.");
+                       return findManifest(currentPath.getParent());
+               }
+       }
+
+       private static Path toTempJar(Path dir) {
+               try {
+                       Manifest manifest = findManifest(dir);
+                       Path jarPath = Files.createTempFile("a2Source", ".jar");
+                       try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
+                               Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
+                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                               Path relPath = dir.relativize(file);
+                                               // skip MANIFEST from folder
+                                               if (relPath.toString().contentEquals("META-INF/MANIFEST.MF"))
+                                                       return FileVisitResult.CONTINUE;
+                                               zos.putNextEntry(new ZipEntry(relPath.toString()));
+                                               Files.copy(file, zos);
+                                               zos.closeEntry();
+                                               return FileVisitResult.CONTINUE;
+                                       }
+                               });
+                       }
+                       return jarPath;
+               } catch (IOException e) {
+                       throw new A2Exception("Cannot install OSGi bundle from " + dir, e);
+               }
+
+       }
+
+       private InputStream newInputStream(Object locator) throws IOException {
+               if (locator instanceof Path) {
+                       return Files.newInputStream((Path) locator);
+               } else if (locator instanceof URL) {
+                       return ((URL) locator).openStream();
+               } else {
+                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
+               }
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java b/org.argeo.init/src/org/argeo/init/a2/ClasspathSource.java
new file mode 100644 (file)
index 0000000..8a9e5e6
--- /dev/null
@@ -0,0 +1,38 @@
+package org.argeo.init.a2;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Version;
+
+/**
+ * A provisioning source based on the linear classpath with which the JCM has
+ * been started.
+ */
+public class ClasspathSource extends AbstractProvisioningSource {
+       void load() throws IOException {
+               A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH);
+               List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
+               parts: for (String part : classpath) {
+                       Path file = Paths.get(part);
+                       Version version;
+                       try {
+                               version = new Version(readVersionFromModule(file));
+                       } catch (Exception e) {
+                               // ignore non OSGi
+                               continue parts;
+                       }
+                       String moduleName = readSymbolicNameFromModule(file);
+                       A2Component component = classpathContribution.getOrAddComponent(moduleName);
+                       A2Module module = component.getOrAddModule(version, file);
+                       if (OsgiBootUtils.isDebug())
+                               OsgiBootUtils.debug("Registered " + module);
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsA2Source.java
new file mode 100644 (file)
index 0000000..067537d
--- /dev/null
@@ -0,0 +1,89 @@
+package org.argeo.init.a2;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Version;
+
+/** A file system {@link AbstractProvisioningSource} in A2 format. */
+public class FsA2Source extends AbstractProvisioningSource implements A2Source {
+       private final Path base;
+
+       public FsA2Source(Path base) {
+               super();
+               this.base = base;
+       }
+
+       void load() throws IOException {
+               DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
+               SortedSet<A2Contribution> contributions = new TreeSet<>();
+               contributions: for (Path contributionPath : contributionPaths) {
+                       if (Files.isDirectory(contributionPath)) {
+                               String contributionId = contributionPath.getFileName().toString();
+                               if (A2Contribution.BOOT.equals(contributionId))// skip boot
+                                       continue contributions;
+                               A2Contribution contribution = getOrAddContribution(contributionId);
+                               contributions.add(contribution);
+                       }
+               }
+
+               for (A2Contribution contribution : contributions) {
+                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId()));
+                       modules: for (Path modulePath : modulePaths) {
+                               if (!Files.isDirectory(modulePath)) {
+                                       // OsgiBootUtils.debug("Registering " + modulePath);
+                                       String moduleFileName = modulePath.getFileName().toString();
+                                       int lastDot = moduleFileName.lastIndexOf('.');
+                                       String ext = moduleFileName.substring(lastDot + 1);
+                                       if (!"jar".equals(ext))
+                                               continue modules;
+//                                     String moduleName = moduleFileName.substring(0, lastDot);
+//                                     if (moduleName.endsWith("-SNAPSHOT"))
+//                                             moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length());
+//                                     int lastDash = moduleName.lastIndexOf('-');
+//                                     String versionStr = moduleName.substring(lastDash + 1);
+//                                     String componentName = moduleName.substring(0, lastDash);
+                                       // if(versionStr.endsWith("-SNAPSHOT")) {
+                                       // versionStr = readVersionFromModule(modulePath);
+                                       // }
+                                       Version version;
+//                                     try {
+//                                             version = new Version(versionStr);
+//                                     } catch (Exception e) {
+                                       String versionStr = readVersionFromModule(modulePath);
+                                       String componentName = readSymbolicNameFromModule(modulePath);
+                                       if (versionStr != null) {
+                                               version = new Version(versionStr);
+                                       } else {
+                                               OsgiBootUtils.debug("Ignore " + modulePath + " since version cannot be found");
+                                               continue modules;
+                                       }
+//                                     }
+                                       A2Component component = contribution.getOrAddComponent(componentName);
+                                       A2Module module = component.getOrAddModule(version, modulePath);
+                                       if (OsgiBootUtils.isDebug())
+                                               OsgiBootUtils.debug("Registered " + module);
+                               }
+                       }
+               }
+
+       }
+
+       public static void main(String[] args) {
+               try {
+                       FsA2Source context = new FsA2Source(Paths.get(
+                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi"));
+                       context.load();
+                       context.asTree();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java b/org.argeo.init/src/org/argeo/init/a2/FsM2Source.java
new file mode 100644 (file)
index 0000000..1657fad
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.init.a2;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Version;
+
+/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */
+public class FsM2Source extends AbstractProvisioningSource {
+       private final Path base;
+
+       public FsM2Source(Path base) {
+               super();
+               this.base = base;
+       }
+
+       void load() throws IOException {
+               Files.walkFileTree(base, new ArtifactFileVisitor());
+       }
+
+       class ArtifactFileVisitor extends SimpleFileVisitor<Path> {
+
+               @Override
+               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                       // OsgiBootUtils.debug("Processing " + file);
+                       if (file.toString().endsWith(".jar")) {
+                               Version version;
+                               try {
+                                       version = new Version(readVersionFromModule(file));
+                               } catch (Exception e) {
+                                       // ignore non OSGi
+                                       return FileVisitResult.CONTINUE;
+                               }
+                               String moduleName = readSymbolicNameFromModule(file);
+                               Path groupPath = file.getParent().getParent().getParent();
+                               Path relGroupPath = base.relativize(groupPath);
+                               String contributionName = relGroupPath.toString().replace(File.separatorChar, '.');
+                               A2Contribution contribution = getOrAddContribution(contributionName);
+                               A2Component component = contribution.getOrAddComponent(moduleName);
+                               A2Module module = component.getOrAddModule(version, file);
+                               if (OsgiBootUtils.isDebug())
+                                       OsgiBootUtils.debug("Registered " + module);
+                       }
+                       return super.visitFile(file, attrs);
+               }
+
+       }
+
+       public static void main(String[] args) {
+               try {
+                       FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository"));
+                       context.load();
+                       context.asTree();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java b/org.argeo.init/src/org/argeo/init/a2/OsgiContext.java
new file mode 100644 (file)
index 0000000..35fbee3
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.init.a2;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.Version;
+
+/** A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. */
+class OsgiContext extends AbstractProvisioningSource {
+       private final BundleContext bc;
+
+       public OsgiContext(BundleContext bc) {
+               super();
+               this.bc = bc;
+       }
+
+       public OsgiContext() {
+               Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
+               if (bundle == null)
+                       throw new IllegalArgumentException(
+                                       "OSGi Boot bundle must be started or a bundle context must be specified");
+               this.bc = bundle.getBundleContext();
+       }
+
+       void load() {
+               A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME);
+               for (Bundle bundle : bc.getBundles()) {
+                       // OsgiBootUtils.debug(bundle.getDataFile("/"));
+                       String componentId = bundle.getSymbolicName();
+                       Version version = bundle.getVersion();
+                       A2Component component = runtimeContribution.getOrAddComponent(componentId);
+                       A2Module module = component.getOrAddModule(version, bundle);
+                       if (OsgiBootUtils.isDebug())
+                               OsgiBootUtils.debug("Registered " + module + " (location id: " + bundle.getLocation() + ")");
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningManager.java
new file mode 100644 (file)
index 0000000..6012c34
--- /dev/null
@@ -0,0 +1,200 @@
+package org.argeo.init.a2;
+
+import java.io.File;
+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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.argeo.init.osgi.OsgiBootUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+/** Loads provisioning sources into an OSGi context. */
+public class ProvisioningManager {
+       BundleContext bc;
+       OsgiContext osgiContext;
+       List<ProvisioningSource> sources = Collections.synchronizedList(new ArrayList<>());
+
+       public ProvisioningManager(BundleContext bc) {
+               this.bc = bc;
+               osgiContext = new OsgiContext(bc);
+               osgiContext.load();
+       }
+
+       protected void addSource(ProvisioningSource source) {
+               sources.add(source);
+       }
+
+       void installWholeSource(ProvisioningSource source) {
+               Set<Bundle> updatedBundles = new HashSet<>();
+               for (A2Contribution contribution : source.listContributions(null)) {
+                       for (A2Component component : contribution.components.values()) {
+                               A2Module module = component.last().last();
+                               Bundle bundle = installOrUpdate(module);
+                               if (bundle != null)
+                                       updatedBundles.add(bundle);
+                       }
+               }
+               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+               frameworkWiring.refreshBundles(updatedBundles);
+       }
+
+       public void registerSource(String uri) {
+               try {
+                       URI u = new URI(uri);
+                       if (A2Source.SCHEME_A2.equals(u.getScheme())) {
+                               if (u.getHost() == null || "".equals(u.getHost())) {
+                                       String baseStr = u.getPath();
+                                       if (File.separatorChar == '\\') {// MS Windows
+                                               baseStr = baseStr.substring(1).replace('/', File.separatorChar);
+                                       }
+                                       Path base = Paths.get(baseStr);
+                                       if (Files.exists(base)) {
+                                               FsA2Source source = new FsA2Source(base);
+                                               source.load();
+                                               addSource(source);
+                                               OsgiBootUtils.info("Registered " + uri + " as source");
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new A2Exception("Cannot add source " + uri, e);
+               }
+       }
+
+       public boolean registerDefaultSource() {
+               String frameworkLocation = bc.getProperty("osgi.framework");
+               try {
+                       URI frameworkLocationUri = new URI(frameworkLocation);
+                       if ("file".equals(frameworkLocationUri.getScheme())) {
+                               Path frameworkPath = Paths.get(frameworkLocationUri);
+                               if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) {
+                                       Path base = frameworkPath.getParent().getParent();
+                                       String baseStr = base.toString();
+                                       if (File.separatorChar == '\\')// MS Windows
+                                               baseStr = '/' + baseStr.replace(File.separatorChar, '/');
+                                       URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null);
+                                       registerSource(baseUri.toString());
+                                       OsgiBootUtils.debug("Default source from framework location " + frameworkLocation);
+                                       return true;
+                               }
+                       }
+               } catch (Exception e) {
+                       OsgiBootUtils.error("Cannot register default source based on framework location " + frameworkLocation, e);
+               }
+               return false;
+       }
+
+       public void install(String spec) {
+               if (spec == null) {
+                       for (ProvisioningSource source : sources) {
+                               installWholeSource(source);
+                       }
+               }
+       }
+
+       /** @return the new/updated bundle, or null if nothing was done. */
+       protected Bundle installOrUpdate(A2Module module) {
+               try {
+                       ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource();
+                       Version moduleVersion = module.getVersion();
+                       A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
+                       if (osgiBranch == null) {
+//                             Bundle bundle = bc.installBundle(module.getBranch().getCoordinates(),
+//                                             moduleSource.newInputStream(module.getLocator()));
+                               Bundle bundle = moduleSource.install(bc, module);
+                               if (OsgiBootUtils.isDebug())
+                                       OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
+                               return bundle;
+                       } else {
+                               A2Module lastOsgiModule = osgiBranch.last();
+                               int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
+                               if (compare > 0) {// update
+                                       Bundle bundle = (Bundle) lastOsgiModule.getLocator();
+//                                     bundle.update(moduleSource.newInputStream(module.getLocator()));
+                                       moduleSource.update(bundle, module);
+                                       OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
+                                       return bundle;
+                               }
+                       }
+               } catch (Exception e) {
+                       OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e);
+               }
+               return null;
+       }
+
+       public Collection<Bundle> update() {
+               boolean fragmentsUpdated = false;
+               Set<Bundle> updatedBundles = new HashSet<>();
+               bundles: for (Bundle bundle : bc.getBundles()) {
+                       for (ProvisioningSource source : sources) {
+                               String componentId = bundle.getSymbolicName();
+                               Version version = bundle.getVersion();
+                               A2Branch branch = source.findBranch(componentId, version);
+                               if (branch == null)
+                                       continue bundles;
+                               A2Module module = branch.last();
+                               Version moduleVersion = module.getVersion();
+                               int compare = moduleVersion.compareTo(version);
+                               if (compare > 0) {// update
+                                       try {
+                                               source.update(bundle, module);
+//                                             bundle.update(in);
+                                               String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
+                                               if (fragmentHost != null)
+                                                       fragmentsUpdated = true;
+                                               OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
+                                               updatedBundles.add(bundle);
+                                       } catch (Exception e) {
+                                               OsgiBootUtils.error("Cannot update with module " + module, e);
+                                       }
+                               }
+                       }
+               }
+               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
+               if (fragmentsUpdated)// refresh all
+                       frameworkWiring.refreshBundles(null);
+               else
+                       frameworkWiring.refreshBundles(updatedBundles);
+               return updatedBundles;
+       }
+
+       public static void main(String[] args) {
+               Map<String, String> configuration = new HashMap<>();
+               configuration.put("osgi.console", "2323");
+               Framework framework = OsgiBootUtils.launch(configuration);
+               try {
+                       ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
+                       FsA2Source context = new FsA2Source(Paths.get(
+                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi"));
+                       context.load();
+                       if (framework.getBundleContext().getBundles().length == 1) {// initial
+                               pm.install(null);
+                       } else {
+                               pm.update();
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+               } finally {
+                       try {
+                               // framework.stop();
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java b/org.argeo.init/src/org/argeo/init/a2/ProvisioningSource.java
new file mode 100644 (file)
index 0000000..9935630
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.init.a2;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+
+/** Where components are retrieved from. */
+public interface ProvisioningSource {
+       /** List all contributions of this source. */
+       Iterable<A2Contribution> listContributions(Object filter);
+
+       /** Install a module in the OSGi runtime. */
+       Bundle install(BundleContext bc, A2Module module);
+
+       /** Update a module in the OSGi runtime. */
+       void update(Bundle bundle, A2Module module);
+
+       /** Finds the {@link A2Branch} related to this component and version. */
+       A2Branch findBranch(String componentId, Version version);
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/a2/package-info.java b/org.argeo.init/src/org/argeo/init/a2/package-info.java
new file mode 100644 (file)
index 0000000..bb8fa6e
--- /dev/null
@@ -0,0 +1,2 @@
+/** A2 OSGi repository format. */
+package org.argeo.init.a2;
\ No newline at end of file
diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinJavaUtilLogging.java
new file mode 100644 (file)
index 0000000..1bfab8e
--- /dev/null
@@ -0,0 +1,107 @@
+package org.argeo.init.logging;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+
+/**
+ * Fallback wrapper around the java.util.logging framework, when thin logging
+ * could not be instantiated directly.
+ */
+class ThinJavaUtilLogging {
+       private LogManager logManager;
+
+       private ThinJavaUtilLogging(LogManager logManager) {
+               this.logManager = logManager;
+               this.logManager.reset();
+       }
+
+       static ThinJavaUtilLogging init() {
+               LogManager logManager = LogManager.getLogManager();
+               ThinJavaUtilLogging thinJul = new ThinJavaUtilLogging(logManager);
+               return thinJul;
+       }
+
+       private static Level fromJulLevel(java.util.logging.Level julLevel) {
+               if (java.util.logging.Level.ALL.equals(julLevel))
+                       return Level.ALL;
+               else if (java.util.logging.Level.FINER.equals(julLevel))
+                       return Level.TRACE;
+               else if (java.util.logging.Level.FINE.equals(julLevel))
+                       return Level.DEBUG;
+               else if (java.util.logging.Level.INFO.equals(julLevel))
+                       return Level.INFO;
+               else if (java.util.logging.Level.WARNING.equals(julLevel))
+                       return Level.WARNING;
+               else if (java.util.logging.Level.SEVERE.equals(julLevel))
+                       return Level.ERROR;
+               else if (java.util.logging.Level.OFF.equals(julLevel))
+                       return Level.OFF;
+               else
+                       throw new IllegalArgumentException("Unsupported JUL level " + julLevel);
+       }
+
+       private static java.util.logging.Level toJulLevel(Level level) {
+               if (Level.ALL.equals(level))
+                       return java.util.logging.Level.ALL;
+               else if (Level.TRACE.equals(level))
+                       return java.util.logging.Level.FINER;
+               else if (Level.DEBUG.equals(level))
+                       return java.util.logging.Level.FINE;
+               else if (Level.INFO.equals(level))
+                       return java.util.logging.Level.INFO;
+               else if (Level.WARNING.equals(level))
+                       return java.util.logging.Level.WARNING;
+               else if (Level.ERROR.equals(level))
+                       return java.util.logging.Level.SEVERE;
+               else if (Level.OFF.equals(level))
+                       return java.util.logging.Level.OFF;
+               else
+                       throw new IllegalArgumentException("Unsupported logging level " + level);
+       }
+
+       void readConfiguration(Map<String, Level> configuration) {
+               this.logManager.reset();
+               Properties properties = new Properties();
+               for (String name : configuration.keySet()) {
+                       properties.put(name + ".level", toJulLevel(configuration.get(name)).toString());
+               }
+               try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+                       properties.store(out, null);
+                       try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
+                               logManager.readConfiguration(in);
+                       }
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot apply JUL configuration", e);
+               }
+               logManager.getLogger("").addHandler(new ThinHandler());
+       }
+
+       /**
+        * A fallback {@link Handler} forwarding only messages and logger name (all
+        * other {@link LogRecord} information is lost.
+        */
+       private static class ThinHandler extends Handler {
+               @Override
+               public void publish(LogRecord record) {
+                       java.lang.System.Logger systemLogger = ThinLoggerFinder.getLogger(record.getLoggerName());
+                       systemLogger.log(ThinJavaUtilLogging.fromJulLevel(record.getLevel()), record.getMessage(),
+                                       record.getThrown());
+               }
+
+               @Override
+               public void flush() {
+               }
+
+               @Override
+               public void close() throws SecurityException {
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java b/org.argeo.init/src/org/argeo/init/logging/ThinLoggerFinder.java
new file mode 100644 (file)
index 0000000..f443b2e
--- /dev/null
@@ -0,0 +1,81 @@
+package org.argeo.init.logging;
+
+import java.io.Serializable;
+import java.lang.System.Logger;
+import java.lang.System.LoggerFinder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Flow;
+import java.util.function.Consumer;
+
+/**
+ * Factory for Java system logging. As it has to be a public class in order to
+ * be exposed as a service provider, it is also the main entry point for the
+ * thin logging system, via static methos.
+ */
+public class ThinLoggerFinder extends LoggerFinder {
+       private static ThinLogging logging;
+       private static ThinJavaUtilLogging javaUtilLogging;
+
+       public ThinLoggerFinder() {
+               if (logging != null)
+                       throw new IllegalStateException("Only one logging can be initialised.");
+               init();
+       }
+
+       @Override
+       public Logger getLogger(String name, Module module) {
+               return logging.getLogger(name, module);
+       }
+
+       private static void init() {
+               logging = new ThinLogging();
+
+               Map<String, Object> configuration = new HashMap<>();
+               for (Object key : System.getProperties().keySet()) {
+                       Objects.requireNonNull(key);
+                       String property = key.toString();
+                       if (property.startsWith(ThinLogging.LEVEL_PROPERTY_PREFIX)
+                                       || property.equals(ThinLogging.DEFAULT_LEVEL_PROPERTY))
+                               configuration.put(property, System.getProperty(property));
+               }
+               logging.accept(configuration);
+       }
+
+       /**
+        * Falls back to java.util.logging if thin logging was not already initialised
+        * by the {@link LoggerFinder} mechanism.
+        */
+       public static void lazyInit() {
+               if (logging != null)
+                       return;
+               if (javaUtilLogging != null)
+                       return;
+               init();
+               javaUtilLogging = ThinJavaUtilLogging.init();
+               javaUtilLogging.readConfiguration(logging.getLevels());
+       }
+
+       public static Consumer<Map<String, Object>> getConfigurationConsumer() {
+               Objects.requireNonNull(logging);
+               return logging;
+       }
+
+       public static Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
+               Objects.requireNonNull(logging);
+               return logging.getLogEntryPublisher();
+       }
+
+       static void update(Map<String, Object> configuration) {
+               if (logging == null)
+                       throw new IllegalStateException("Thin logging must be initialized first");
+               logging.accept(configuration);
+               if (javaUtilLogging != null)
+                       javaUtilLogging.readConfiguration(logging.getLevels());
+       }
+
+       static Logger getLogger(String name) {
+               return logging.getLogger(name, null);
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java b/org.argeo.init/src/org/argeo/init/logging/ThinLogging.java
new file mode 100644 (file)
index 0000000..e02dae0
--- /dev/null
@@ -0,0 +1,552 @@
+package org.argeo.init.logging;
+
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.SortedMap;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.SubmissionPublisher;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+/**
+ * A thin logging system based on the {@link Logger} framework. It is a
+ * {@link Consumer} of configuration, and can be registered as such.
+ */
+class ThinLogging implements Consumer<Map<String, Object>> {
+       final static String DEFAULT_LEVEL_NAME = "";
+
+       final static String DEFAULT_LEVEL_PROPERTY = "log";
+       final static String LEVEL_PROPERTY_PREFIX = DEFAULT_LEVEL_PROPERTY + ".";
+
+       final static String JOURNALD_PROPERTY = "argeo.logging.journald";
+       final static String CALL_LOCATION_PROPERTY = "argeo.logging.callLocation";
+
+       private final static AtomicLong nextEntry = new AtomicLong(0l);
+
+       // we don't synchronize maps on purpose as it would be
+       // too expensive during normal operation
+       // updates to the config may be shortly inconsistent
+       private SortedMap<String, ThinLogger> loggers = new TreeMap<>();
+       private NavigableMap<String, Level> levels = new TreeMap<>();
+       private volatile boolean updatingConfiguration = false;
+
+       private final ExecutorService executor;
+       private final LogEntryPublisher publisher;
+
+       private final boolean journald;
+       private final Level callLocationLevel;
+
+       ThinLogging() {
+               executor = Executors.newCachedThreadPool((r) -> {
+                       Thread t = new Thread(r);
+                       t.setDaemon(true);
+                       return t;
+               });
+               publisher = new LogEntryPublisher(executor, Flow.defaultBufferSize());
+
+               PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
+               publisher.subscribe(subscriber);
+
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
+
+               // initial default level
+               levels.put("", Level.WARNING);
+
+               // Logging system config
+               // journald
+
+//             Map<String, String> env = new TreeMap<>(System.getenv());
+//             for (String key : env.keySet()) {
+//                     System.out.println(key + "=" + env.get(key));
+//             }
+
+               String journaldStr = System.getProperty(JOURNALD_PROPERTY, "auto");
+               switch (journaldStr) {
+               case "auto":
+                       String systemdInvocationId = System.getenv("INVOCATION_ID");
+                       if (systemdInvocationId != null) {// in systemd
+                               // check whether we are indirectly in a desktop app (e.g. eclipse)
+                               String desktopFilePid = System.getenv("GIO_LAUNCHED_DESKTOP_FILE_PID");
+                               if (desktopFilePid != null) {
+                                       Long javaPid = ProcessHandle.current().pid();
+                                       if (!javaPid.toString().equals(desktopFilePid)) {
+                                               journald = false;
+                                               break;
+                                       }
+                               }
+                               journald = true;
+                               break;
+                       }
+                       journald = false;
+                       break;
+               case "true":
+               case "on":
+                       journald = true;
+                       break;
+               case "false":
+               case "off":
+                       journald = false;
+                       break;
+               default:
+                       throw new IllegalArgumentException(
+                                       "Unsupported value '" + journaldStr + "' for property " + JOURNALD_PROPERTY);
+               }
+
+               String callLocationStr = System.getProperty(CALL_LOCATION_PROPERTY, Level.WARNING.getName());
+               callLocationLevel = Level.valueOf(callLocationStr);
+       }
+
+       private void close() {
+               publisher.close();
+               try {
+                       // we ait a bit in order to make sure all messages are flushed
+                       // TODO synchronize more efficiently
+                       executor.awaitTermination(300, TimeUnit.MILLISECONDS);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+       }
+
+       private Level computeApplicableLevel(String name) {
+               Map.Entry<String, Level> entry = levels.floorEntry(name);
+               assert entry != null;
+               return entry.getValue();
+
+       }
+
+//     private boolean isLoggable(String name, Level level) {
+//             Objects.requireNonNull(name);
+//             Objects.requireNonNull(level);
+//
+//             if (updatingConfiguration) {
+//                     synchronized (levels) {
+//                             try {
+//                                     levels.wait();
+//                                     // TODO make exit more robust
+//                             } catch (InterruptedException e) {
+//                                     throw new IllegalStateException(e);
+//                             }
+//                     }
+//             }
+//
+//             return level.getSeverity() >= computeApplicableLevel(name).getSeverity();
+//     }
+
+       public Logger getLogger(String name, Module module) {
+               if (!loggers.containsKey(name)) {
+                       ThinLogger logger = new ThinLogger(name, computeApplicableLevel(name));
+                       loggers.put(name, logger);
+               }
+               return loggers.get(name);
+       }
+
+       public void accept(Map<String, Object> configuration) {
+               synchronized (levels) {
+                       updatingConfiguration = true;
+
+                       Map<String, Level> backup = new TreeMap<>(levels);
+
+                       boolean fullReset = configuration.containsKey(DEFAULT_LEVEL_PROPERTY);
+                       try {
+                               properties: for (String property : configuration.keySet()) {
+                                       if (!property.startsWith(LEVEL_PROPERTY_PREFIX))
+                                               continue properties;
+                                       String levelStr = configuration.get(property).toString();
+                                       Level level = Level.valueOf(levelStr);
+                                       levels.put(property.substring(LEVEL_PROPERTY_PREFIX.length()), level);
+                               }
+
+                               if (fullReset) {
+                                       Iterator<Map.Entry<String, Level>> it = levels.entrySet().iterator();
+                                       while (it.hasNext()) {
+                                               Map.Entry<String, Level> entry = it.next();
+                                               String name = entry.getKey();
+                                               if (!configuration.containsKey(LEVEL_PROPERTY_PREFIX + name)) {
+                                                       it.remove();
+                                               }
+                                       }
+                                       Level newDefaultLevel = Level.valueOf(configuration.get(DEFAULT_LEVEL_PROPERTY).toString());
+                                       levels.put(DEFAULT_LEVEL_NAME, newDefaultLevel);
+                                       // TODO notify everyone?
+                               }
+                               assert levels.containsKey(DEFAULT_LEVEL_NAME);
+
+                               // recompute all levels
+                               for (String name : loggers.keySet()) {
+                                       ThinLogger logger = loggers.get(name);
+                                       logger.setLevel(computeApplicableLevel(name));
+                               }
+                       } catch (IllegalArgumentException e) {
+                               e.printStackTrace();
+                               levels.clear();
+                               levels.putAll(backup);
+                       }
+                       updatingConfiguration = false;
+                       levels.notifyAll();
+               }
+
+       }
+
+       Flow.Publisher<Map<String, Serializable>> getLogEntryPublisher() {
+               return publisher;
+       }
+
+       Map<String, Level> getLevels() {
+               return Collections.unmodifiableNavigableMap(levels);
+       }
+
+       /*
+        * INTERNAL CLASSES
+        */
+
+       private class ThinLogger implements System.Logger {
+               private final String name;
+
+               private Level level;
+
+               protected ThinLogger(String name, Level level) {
+                       assert Objects.nonNull(name);
+                       this.name = name;
+                       this.level = level;
+               }
+
+               @Override
+               public String getName() {
+                       return name;
+               }
+
+               @Override
+               public boolean isLoggable(Level level) {
+                       return level.getSeverity() >= getLevel().getSeverity();
+                       // TODO optimise by referencing the applicable level in this class?
+//                     return ThinLogging.this.isLoggable(name, level);
+               }
+
+               private Level getLevel() {
+                       if (updatingConfiguration) {
+                               synchronized (levels) {
+                                       try {
+                                               levels.wait();
+                                               // TODO make exit more robust
+                                       } catch (InterruptedException e) {
+                                               throw new IllegalStateException(e);
+                                       }
+                               }
+                       }
+                       return level;
+               }
+
+               @Override
+               public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
+                       if (!isLoggable(level))
+                               return;
+                       // measure timestamp first
+                       Instant now = Instant.now();
+                       Thread thread = Thread.currentThread();
+                       publisher.log(this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread));
+               }
+
+               @Override
+               public void log(Level level, ResourceBundle bundle, String format, Object... params) {
+                       if (!isLoggable(level))
+                               return;
+                       // measure timestamp first
+                       Instant now = Instant.now();
+                       Thread thread = Thread.currentThread();
+
+                       // NOTE: this is the method called when logging a plain message without
+                       // exception, so it should be considered as a format only when args are not null
+                       if (format.contains("{}"))// workaround for weird Jetty formatting
+                               params = null;
+                       String msg = params == null ? format : MessageFormat.format(format, params);
+                       publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread));
+               }
+
+               private void setLevel(Level level) {
+                       this.level = level;
+               }
+
+               private StackTraceElement findCallLocation(Level level, Thread thread) {
+                       assert level != null;
+                       assert thread != null;
+                       // TODO rather use a StackWalker and make it smarter
+                       StackTraceElement callLocation = null;
+                       if (level.getSeverity() >= callLocationLevel.getSeverity()) {
+                               StackTraceElement[] stack = thread.getStackTrace();
+                               int lowestLoggerInterface = 0;
+                               stack: for (int i = 2; i < stack.length; i++) {
+                                       String className = stack[i].getClassName();
+                                       switch (className) {
+                                       // TODO make it more configurable
+                                       // FIXME deal with privileges stacks (in Equinox)
+                                       case "java.lang.System$Logger":
+                                       case "java.util.logging.Logger":
+                                       case "org.apache.commons.logging.Log":
+                                       case "org.osgi.service.log.Logger":
+                                       case "org.eclipse.osgi.internal.log.LoggerImpl":
+                                       case "org.argeo.api.cms.CmsLog":
+                                       case "org.slf4j.impl.ArgeoLogger":
+                                       case "org.argeo.cms.internal.osgi.CmsOsgiLogger":
+                                       case "org.eclipse.jetty.util.log.Slf4jLog":
+                                       case "sun.util.logging.internal.LoggingProviderImpl$JULWrapper":
+                                               lowestLoggerInterface = i;
+                                               continue stack;
+                                       default:
+                                       }
+                               }
+                               if (stack.length > lowestLoggerInterface + 1)
+                                       callLocation = stack[lowestLoggerInterface + 1];
+                       }
+                       return callLocation;
+               }
+
+       }
+
+       private final static String KEY_LOGGER = Logger.class.getName();
+       private final static String KEY_LEVEL = Level.class.getName();
+       private final static String KEY_MSG = String.class.getName();
+       private final static String KEY_THROWABLE = Throwable.class.getName();
+       private final static String KEY_INSTANT = Instant.class.getName();
+       private final static String KEY_CALL_LOCATION = StackTraceElement.class.getName();
+       private final static String KEY_THREAD = Thread.class.getName();
+
+       private class LogEntryPublisher extends SubmissionPublisher<Map<String, Serializable>> {
+
+               private LogEntryPublisher(Executor executor, int maxBufferCapacity) {
+                       super(executor, maxBufferCapacity);
+               }
+
+               private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
+                               Thread thread, Throwable thrown, StackTraceElement callLocation) {
+                       assert level != null;
+                       assert logger != null;
+                       assert msg != null;
+                       assert instant != null;
+                       assert thread != null;
+
+                       final long sequence = nextEntry.incrementAndGet();
+
+                       Map<String, Serializable> logEntry = new LogEntryMap(sequence);
+
+                       // same object as key class name
+                       logEntry.put(KEY_LEVEL, level);
+                       logEntry.put(KEY_MSG, msg);
+                       logEntry.put(KEY_INSTANT, instant);
+                       if (thrown != null)
+                               logEntry.put(KEY_THROWABLE, thrown);
+                       if (callLocation != null)
+                               logEntry.put(KEY_CALL_LOCATION, callLocation);
+
+                       // object is a string
+                       logEntry.put(KEY_LOGGER, logger.getName());
+                       logEntry.put(KEY_THREAD, thread.getName());
+
+                       // should be unmodifiable for security reasons
+                       if (!isClosed())
+                               submit(Collections.unmodifiableMap(logEntry));
+               }
+
+       }
+
+       /**
+        * An internal optimisation for collections. It should not be referred to
+        * directly as a type.
+        */
+       // TODO optimise memory with a custom map implementation?
+       // but access may be slower
+       private static class LogEntryMap extends HashMap<String, Serializable> {
+               private static final long serialVersionUID = 7361434381922521356L;
+
+               private final long sequence;
+
+               private LogEntryMap(long sequence) {
+                       // maximum 7 fields, so using default load factor 0.75
+                       // an initial size of 10 should prevent rehashing (7 / 0.75 ~ 9.333)
+                       // see HashMap class description for more details
+                       super(10, 0.75f);
+                       this.sequence = sequence;
+               }
+
+               @Override
+               public boolean equals(Object o) {
+                       if (o instanceof LogEntryMap)
+                               return sequence == ((LogEntryMap) o).sequence;
+                       else if (o instanceof Map) {
+                               Map<?, ?> map = (Map<?, ?>) o;
+                               return get(KEY_INSTANT).equals(map.get(KEY_INSTANT)) && get(KEY_THREAD).equals(map.get(KEY_THREAD));
+                       } else
+                               return false;
+               }
+
+               @Override
+               public int hashCode() {
+                       return (int) sequence;
+               }
+
+       }
+
+       private class PrintStreamSubscriber implements Flow.Subscriber<Map<String, Serializable>> {
+               private PrintStream out;
+               private PrintStream err;
+               private int writeToErrLevel = Level.WARNING.getSeverity();
+
+               protected PrintStreamSubscriber() {
+                       this(System.out, System.err);
+               }
+
+               protected PrintStreamSubscriber(PrintStream out, PrintStream err) {
+                       this.out = out;
+                       this.err = err;
+               }
+
+               private Level getLevel(Map<String, Serializable> logEntry) {
+                       return (Level) logEntry.get(KEY_LEVEL);
+               }
+
+               @Override
+               public void onSubscribe(Subscription subscription) {
+                       subscription.request(Long.MAX_VALUE);
+               }
+
+               @Override
+               public void onNext(Map<String, Serializable> item) {
+                       if (getLevel(item).getSeverity() >= writeToErrLevel) {
+                               err.print(toPrint(item));
+                       } else {
+                               out.print(toPrint(item));
+                       }
+                       // TODO flush for journald?
+               }
+
+               @Override
+               public void onError(Throwable throwable) {
+                       throwable.printStackTrace(err);
+               }
+
+               @Override
+               public void onComplete() {
+                       out.flush();
+                       err.flush();
+               }
+
+               protected String firstLinePrefix(Map<String, Serializable> logEntry) {
+                       Level level = getLevel(logEntry);
+                       String spaces;
+                       switch (level) {
+                       case ERROR:
+                       case DEBUG:
+                       case TRACE:
+                               spaces = "   ";
+                               break;
+                       case INFO:
+                               spaces = "    ";
+                               break;
+                       case WARNING:
+                               spaces = " ";
+                               break;
+                       case ALL:
+                               spaces = "     ";
+                               break;
+                       default:
+                               throw new IllegalArgumentException("Unsupported level " + level);
+                       }
+                       return journald ? linePrefix(logEntry) : logEntry.get(KEY_INSTANT) + " " + level + spaces;
+               }
+
+               protected String firstLineSuffix(Map<String, Serializable> logEntry) {
+                       return " - " + (logEntry.containsKey(KEY_CALL_LOCATION) ? logEntry.get(KEY_CALL_LOCATION)
+                                       : logEntry.get(KEY_LOGGER)) + " [" + logEntry.get(KEY_THREAD) + "]";
+               }
+
+               protected String linePrefix(Map<String, Serializable> logEntry) {
+                       return journald ? "<" + levelToJournald(getLevel(logEntry)) + ">" : "";
+               }
+
+               protected int levelToJournald(Level level) {
+                       int severity = level.getSeverity();
+                       if (severity >= Level.ERROR.getSeverity())
+                               return 3;
+                       else if (severity >= Level.WARNING.getSeverity())
+                               return 4;
+                       else if (severity >= Level.INFO.getSeverity())
+                               return 6;
+                       else
+                               return 7;
+               }
+
+               protected String toPrint(Map<String, Serializable> logEntry) {
+                       StringBuilder sb = new StringBuilder();
+                       StringTokenizer st = new StringTokenizer((String) logEntry.get(KEY_MSG), "\r\n");
+                       assert st.hasMoreTokens();
+
+                       // first line
+                       String firstLine = st.nextToken();
+                       sb.append(firstLinePrefix(logEntry));
+                       sb.append(firstLine);
+                       sb.append(firstLineSuffix(logEntry));
+                       sb.append('\n');
+
+                       // other lines
+                       String prefix = linePrefix(logEntry);
+                       while (st.hasMoreTokens()) {
+                               sb.append(prefix);
+                               sb.append(st.nextToken());
+                               sb.append('\n');
+                       }
+
+                       if (logEntry.containsKey(KEY_THROWABLE)) {
+                               Throwable throwable = (Throwable) logEntry.get(KEY_THROWABLE);
+                               sb.append(prefix);
+                               addThrowable(sb, prefix, throwable);
+                       }
+                       return sb.toString();
+               }
+
+               protected void addThrowable(StringBuilder sb, String prefix, Throwable throwable) {
+                       sb.append(throwable.getClass().getName());
+                       sb.append(": ");
+                       sb.append(throwable.getMessage());
+                       sb.append('\n');
+                       for (StackTraceElement ste : throwable.getStackTrace()) {
+                               sb.append(prefix);
+                               sb.append('\t');
+                               sb.append(ste.toString());
+                               sb.append('\n');
+                       }
+                       if (throwable.getCause() != null) {
+                               sb.append(prefix);
+                               sb.append("Caused by: ");
+                               addThrowable(sb, prefix, throwable.getCause());
+                       }
+               }
+       }
+
+       public static void main(String args[]) {
+               Logger logger = System.getLogger(ThinLogging.class.getName());
+               logger.log(Logger.Level.ALL, "Log all");
+               logger.log(Logger.Level.TRACE, "Multi\nline\ntrace");
+               logger.log(Logger.Level.DEBUG, "Log debug");
+               logger.log(Logger.Level.INFO, "Log info");
+               logger.log(Logger.Level.WARNING, "Log warning");
+               logger.log(Logger.Level.ERROR, "Log exception", new Throwable());
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/Activator.java b/org.argeo.init/src/org/argeo/init/osgi/Activator.java
new file mode 100644 (file)
index 0000000..a4f5a37
--- /dev/null
@@ -0,0 +1,51 @@
+package org.argeo.init.osgi;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.util.Objects;
+
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * An OSGi configurator. See
+ * <a href="http://wiki.eclipse.org/Configurator">http:
+ * //wiki.eclipse.org/Configurator</a>
+ */
+public class Activator implements BundleActivator {
+       static {
+               // must be called first
+               ThinLoggerFinder.lazyInit();
+       }
+       private Logger logger = System.getLogger(Activator.class.getName());
+
+       private Long checkpoint = null;
+       private OsgiRuntimeContext runtimeContext;
+
+       public void start(final BundleContext bundleContext) throws Exception {
+               if (runtimeContext == null) {
+                       runtimeContext = new OsgiRuntimeContext(bundleContext);
+               }
+               logger.log(Level.DEBUG, () -> "Argeo init via OSGi activator");
+
+               // admin thread
+//             Thread adminThread = new AdminThread(bundleContext);
+//             adminThread.start();
+
+               // bootstrap
+//             OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+               if (checkpoint == null) {
+//                     osgiBoot.bootstrap();
+                       checkpoint = System.currentTimeMillis();
+               } else {
+                       runtimeContext.update();
+                       checkpoint = System.currentTimeMillis();
+               }
+       }
+
+       public void stop(BundleContext context) throws Exception {
+               Objects.requireNonNull(runtimeContext);
+               runtimeContext.stop(context);
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/AdminThread.java b/org.argeo.init/src/org/argeo/init/osgi/AdminThread.java
new file mode 100644 (file)
index 0000000..e493e2c
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.init.osgi;
+
+import java.io.File;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.launch.Framework;
+
+/** Monitors the runtime and can shut it down. */
+@Deprecated
+public class AdminThread extends Thread {
+       public final static String PROP_ARGEO_OSGI_SHUTDOWN_FILE = "argeo.osgi.shutdownFile";
+       private File shutdownFile;
+       private final BundleContext bundleContext;
+
+       public AdminThread(BundleContext bundleContext) {
+               super("OSGi Boot Admin");
+               this.bundleContext = bundleContext;
+               if (System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE) != null) {
+                       shutdownFile = new File(
+                                       System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE));
+                       if (!shutdownFile.exists()) {
+                               shutdownFile = null;
+                               OsgiBootUtils.warn("Shutdown file " + shutdownFile
+                                               + " not found, feature deactivated");
+                       }
+               }
+       }
+
+       public void run() {
+               if (shutdownFile != null) {
+                       // wait for file to be removed
+                       while (shutdownFile.exists()) {
+                               try {
+                                       Thread.sleep(1000);
+                               } catch (InterruptedException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+
+                       Framework framework = (Framework) bundleContext.getBundle(0);
+                       try {
+                               // shutdown framework
+                               framework.stop();
+                               // wait 10 mins for shutdown
+                               framework.waitForStop(10 * 60 * 1000);
+                               // close VM
+                               System.exit(0);
+                       } catch (Exception e) {
+                               e.printStackTrace();
+                               System.exit(1);
+                       }
+               }
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java b/org.argeo.init/src/org/argeo/init/osgi/BundlesSet.java
new file mode 100644 (file)
index 0000000..73ab9af
--- /dev/null
@@ -0,0 +1,71 @@
+package org.argeo.init.osgi;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/** Intermediary structure used by path matching */
+class BundlesSet {
+       private String baseUrl = "reference:file";// not used yet
+       private final String dir;
+       private List<String> includes = new ArrayList<String>();
+       private List<String> excludes = new ArrayList<String>();
+
+       public BundlesSet(String def) {
+               StringTokenizer st = new StringTokenizer(def, ";");
+
+               if (!st.hasMoreTokens())
+                       throw new RuntimeException("Base dir not defined.");
+               try {
+                       String dirPath = st.nextToken();
+
+                       if (dirPath.startsWith("file:"))
+                               dirPath = dirPath.substring("file:".length());
+
+                       dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath();
+                       if (OsgiBootUtils.debug)
+                               OsgiBootUtils.debug("Base dir: " + dir);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot convert to absolute path", e);
+               }
+
+               while (st.hasMoreTokens()) {
+                       String tk = st.nextToken();
+                       StringTokenizer stEq = new StringTokenizer(tk, "=");
+                       String type = stEq.nextToken();
+                       String pattern = stEq.nextToken();
+                       if ("in".equals(type) || "include".equals(type)) {
+                               includes.add(pattern);
+                       } else if ("ex".equals(type) || "exclude".equals(type)) {
+                               excludes.add(pattern);
+                       } else if ("baseUrl".equals(type)) {
+                               baseUrl = pattern;
+                       } else {
+                               System.err.println("Unkown bundles pattern type " + type);
+                       }
+               }
+
+               // if (excludeSvn && !excludes.contains(EXCLUDES_SVN_PATTERN)) {
+               // excludes.add(EXCLUDES_SVN_PATTERN);
+               // }
+       }
+
+       public String getDir() {
+               return dir;
+       }
+
+       public List<String> getIncludes() {
+               return includes;
+       }
+
+       public List<String> getExcludes() {
+               return excludes;
+       }
+
+       public String getBaseUrl() {
+               return baseUrl;
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/DistributionBundle.java b/org.argeo.init/src/org/argeo/init/osgi/DistributionBundle.java
new file mode 100644 (file)
index 0000000..35b66e6
--- /dev/null
@@ -0,0 +1,269 @@
+package org.argeo.init.osgi;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Version;
+
+/**
+ * A distribution bundle is a bundle within a maven-like distribution
+ * groupId:Bundle-SymbolicName:Bundle-Version which references others OSGi
+ * bundle. It is not required to be OSGi complete also it will generally be
+ * expected that it is. The root of the repository is computed based on the file
+ * name of the URL and of the content of the index.
+ */
+public class DistributionBundle {
+       private final static String INDEX_FILE_NAME = "modularDistribution.csv";
+
+       private final String url;
+
+       private Manifest manifest;
+       private String symbolicName;
+       private String version;
+
+       /** can be null */
+       private String baseUrl;
+       /** can be null */
+       private String relativeUrl;
+       private String localCache;
+
+       private List<OsgiArtifact> artifacts;
+
+       private String separator = ",";
+
+       public DistributionBundle(String url) {
+               this.url = url;
+       }
+
+       public DistributionBundle(String baseUrl, String relativeUrl, String localCache) {
+               if (baseUrl == null || !baseUrl.endsWith("/"))
+                       throw new IllegalArgumentException("Base url " + baseUrl + " badly formatted");
+               if (relativeUrl.startsWith("http") || relativeUrl.startsWith("file:"))
+                       throw new IllegalArgumentException("Relative URL " + relativeUrl + " badly formatted");
+               this.url = constructUrl(baseUrl, relativeUrl);
+               this.baseUrl = baseUrl;
+               this.relativeUrl = relativeUrl;
+               this.localCache = localCache;
+       }
+
+       protected String constructUrl(String baseUrl, String relativeUrl) {
+               try {
+                       if (relativeUrl.indexOf('*') >= 0) {
+                               if (!baseUrl.startsWith("file:"))
+                                       throw new IllegalArgumentException(
+                                                       "Wildcard support only for file:, badly formatted " + baseUrl + " and " + relativeUrl);
+                               Path basePath = Paths.get(new URI(baseUrl));
+                               // Path basePath = Paths.get(new URI(baseUrl));
+                               // int li = relativeUrl.lastIndexOf('/');
+                               // String relativeDir = relativeUrl.substring(0, li);
+                               // String relativeFile = relativeUrl.substring(li,
+                               // relativeUrl.length());
+                               String pattern = "glob:" + basePath + '/' + relativeUrl;
+                               PathMatcher pm = basePath.getFileSystem().getPathMatcher(pattern);
+                               SortedMap<Version, Path> res = new TreeMap<>();
+                               checkDir(basePath, pm, res);
+                               if (res.size() == 0)
+                                       throw new IllegalArgumentException("No file matching " + relativeUrl + " found in " + baseUrl);
+                               return res.get(res.firstKey()).toUri().toString();
+                       } else {
+                               return baseUrl + relativeUrl;
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot build URL from " + baseUrl + " and " + relativeUrl, e);
+               }
+       }
+
+       private void checkDir(Path dir, PathMatcher pm, SortedMap<Version, Path> res) throws IOException {
+               try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
+                       for (Path path : ds) {
+                               if (Files.isDirectory(path))
+                                       checkDir(path, pm, res);
+                               else if (pm.matches(path)) {
+                                       String fileName = path.getFileName().toString();
+                                       fileName = fileName.substring(0, fileName.lastIndexOf('.'));
+                                       if (fileName.endsWith("-SNAPSHOT"))
+                                               fileName = fileName.substring(0, fileName.lastIndexOf('-')) + ".SNAPSHOT";
+                                       fileName = fileName.substring(fileName.lastIndexOf('-') + 1);
+                                       Version version = new Version(fileName);
+                                       res.put(version, path);
+                               }
+                       }
+               }
+       }
+
+       public void processUrl() {
+               JarInputStream jarIn = null;
+               try {
+                       URL u = new URL(url);
+
+                       // local cache
+                       URI localUri = new URI(localCache + relativeUrl);
+                       Path localPath = Paths.get(localUri);
+                       if (Files.exists(localPath))
+                               u = localUri.toURL();
+                       jarIn = new JarInputStream(u.openStream());
+
+                       // meta data
+                       manifest = jarIn.getManifest();
+                       symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+                       version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+
+                       JarEntry indexEntry;
+                       while ((indexEntry = jarIn.getNextJarEntry()) != null) {
+                               String entryName = indexEntry.getName();
+                               if (entryName.equals(INDEX_FILE_NAME)) {
+                                       break;
+                               }
+                               jarIn.closeEntry();
+                       }
+
+                       // list artifacts
+                       if (indexEntry == null)
+                               throw new IllegalArgumentException("No index " + INDEX_FILE_NAME + " in " + url);
+                       artifacts = listArtifacts(jarIn);
+                       jarIn.closeEntry();
+
+                       // find base URL
+                       // won't work if distribution artifact is not listed
+                       for (int i = 0; i < artifacts.size(); i++) {
+                               OsgiArtifact osgiArtifact = (OsgiArtifact) artifacts.get(i);
+                               if (osgiArtifact.getSymbolicName().equals(symbolicName) && osgiArtifact.getVersion().equals(version)) {
+                                       String relativeUrl = osgiArtifact.getRelativeUrl();
+                                       if (url.endsWith(relativeUrl)) {
+                                               baseUrl = url.substring(0, url.length() - osgiArtifact.getRelativeUrl().length());
+                                               break;
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot list URLs from " + url, e);
+               } finally {
+                       if (jarIn != null)
+                               try {
+                                       jarIn.close();
+                               } catch (IOException e) {
+                                       // silent
+                               }
+               }
+       }
+
+       protected List<OsgiArtifact> listArtifacts(InputStream in) {
+               List<OsgiArtifact> osgiArtifacts = new ArrayList<OsgiArtifact>();
+               BufferedReader reader = null;
+               try {
+                       reader = new BufferedReader(new InputStreamReader(in));
+                       String line = null;
+                       lines: while ((line = reader.readLine()) != null) {
+                               StringTokenizer st = new StringTokenizer(line, separator);
+                               String moduleName = st.nextToken();
+                               String moduleVersion = st.nextToken();
+                               String relativeUrl = st.nextToken();
+                               if (relativeUrl.endsWith(".pom"))
+                                       continue lines;
+                               osgiArtifacts.add(new OsgiArtifact(moduleName, moduleVersion, relativeUrl));
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot list artifacts", e);
+               }
+               return osgiArtifacts;
+       }
+
+       /** Convenience method */
+       public static DistributionBundle processUrl(String baseUrl, String relativeUrl, String localCache) {
+               DistributionBundle distributionBundle = new DistributionBundle(baseUrl, relativeUrl, localCache);
+               distributionBundle.processUrl();
+               return distributionBundle;
+       }
+
+       /**
+        * List full URLs of the bundles, based on base URL, usable directly for
+        * download.
+        */
+       public List<String> listUrls() {
+               if (baseUrl == null)
+                       throw new IllegalArgumentException("Base URL is not set");
+
+               if (artifacts == null)
+                       throw new IllegalStateException("Artifact list not initialized");
+
+               List<String> urls = new ArrayList<String>();
+               for (int i = 0; i < artifacts.size(); i++) {
+                       OsgiArtifact osgiArtifact = (OsgiArtifact) artifacts.get(i);
+                       // local cache
+                       URI localUri;
+                       try {
+                               localUri = new URI(localCache + relativeUrl);
+                       } catch (URISyntaxException e) {
+                               OsgiBootUtils.warn(e.getMessage());
+                               localUri = null;
+                       }
+                       Version version = new Version(osgiArtifact.getVersion());
+                       if (localUri != null && Files.exists(Paths.get(localUri)) && version.getQualifier() != null
+                                       && version.getQualifier().startsWith("SNAPSHOT")) {
+                               urls.add(localCache + osgiArtifact.getRelativeUrl());
+                       } else {
+                               urls.add(baseUrl + osgiArtifact.getRelativeUrl());
+                       }
+               }
+               return urls;
+       }
+
+       public void setBaseUrl(String baseUrl) {
+               this.baseUrl = baseUrl;
+       }
+
+       /** Separator used to parse the tabular file */
+       public void setSeparator(String modulesUrlSeparator) {
+               this.separator = modulesUrlSeparator;
+       }
+
+       public String getRelativeUrl() {
+               return relativeUrl;
+       }
+
+       /** One of the listed artifact */
+       protected static class OsgiArtifact {
+               private final String symbolicName;
+               private final String version;
+               private final String relativeUrl;
+
+               public OsgiArtifact(String symbolicName, String version, String relativeUrl) {
+                       super();
+                       this.symbolicName = symbolicName;
+                       this.version = version;
+                       this.relativeUrl = relativeUrl;
+               }
+
+               public String getSymbolicName() {
+                       return symbolicName;
+               }
+
+               public String getVersion() {
+                       return version;
+               }
+
+               public String getRelativeUrl() {
+                       return relativeUrl;
+               }
+
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/Launcher.java b/org.argeo.init/src/org/argeo/init/osgi/Launcher.java
new file mode 100644 (file)
index 0000000..778c08a
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.init.osgi;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.osgi.framework.BundleContext;
+
+/** An OSGi launcher executing first another class in the system class path. */
+public class Launcher {
+
+       public static void main(String[] args) {
+               // Try to load system properties
+               String systemPropertiesFilePath = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE);
+               if (systemPropertiesFilePath != null) {
+                       FileInputStream in;
+                       try {
+                               in = new FileInputStream(systemPropertiesFilePath);
+                               System.getProperties().load(in);
+                       } catch (IOException e1) {
+                               throw new RuntimeException("Cannot load system properties from " + systemPropertiesFilePath, e1);
+                       }
+                       if (in != null) {
+                               try {
+                                       in.close();
+                               } catch (Exception e) {
+                                       // silent
+                               }
+                       }
+               }
+
+               // Start main class
+               startMainClass();
+
+               // Start Equinox
+               BundleContext bundleContext = null;
+               try {
+                       bundleContext = OsgiBootUtils.launch(OsgiBootUtils.equinoxArgsToConfiguration(args)).getBundleContext();
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot start Equinox.", e);
+               }
+
+               // OSGi bootstrap
+               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+               osgiBoot.bootstrap();
+       }
+
+       protected static void startMainClass() {
+               String className = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
+               if (className == null)
+                       return;
+
+               String line = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPARGS, "");
+
+               String[] uiArgs = readArgumentsFromLine(line);
+
+               try {
+                       // Launch main method using reflection
+                       Class<?> clss = Class.forName(className);
+                       Class<?>[] mainArgsClasses = new Class[] { uiArgs.getClass() };
+                       Object[] mainArgs = { uiArgs };
+                       Method mainMethod = clss.getMethod("main", mainArgsClasses);
+                       mainMethod.invoke(null, mainArgs);
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot start main class.", e);
+               }
+
+       }
+
+       /**
+        * Transform a line into an array of arguments, taking "" as single arguments.
+        * (nested \" are not supported)
+        */
+       private static String[] readArgumentsFromLine(String lineOrig) {
+               String line = lineOrig.trim();// remove trailing spaces
+               List<String> args = new ArrayList<String>();
+               StringBuffer curr = new StringBuffer("");
+               boolean inQuote = false;
+               char[] arr = line.toCharArray();
+               for (int i = 0; i < arr.length; i++) {
+                       char c = arr[i];
+                       switch (c) {
+                       case '\"':
+                               inQuote = !inQuote;
+                               break;
+                       case ' ':
+                               if (!inQuote) {// otherwise, no break: goes to default
+                                       if (curr.length() > 0) {
+                                               args.add(curr.toString());
+                                               curr = new StringBuffer("");
+                                       }
+                                       break;
+                               }
+                       default:
+                               curr.append(c);
+                               break;
+                       }
+               }
+
+               // Add last arg
+               if (curr.length() > 0) {
+                       args.add(curr.toString());
+                       curr = null;
+               }
+
+               String[] res = new String[args.size()];
+               for (int i = 0; i < args.size(); i++) {
+                       res[i] = args.get(i).toString();
+               }
+               return res;
+       }
+
+       public static String getProperty(String name, String defaultValue) {
+               final String value;
+               if (defaultValue != null)
+                       value = System.getProperty(name, defaultValue);
+               else
+                       value = System.getProperty(name);
+
+               if (value == null || value.equals(""))
+                       return null;
+               else
+                       return value;
+       }
+
+       public static String getProperty(String name) {
+               return getProperty(name, null);
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/Main.java b/org.argeo.init/src/org/argeo/init/osgi/Main.java
new file mode 100644 (file)
index 0000000..ce83329
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.init.osgi;
+
+import java.lang.management.ManagementFactory;
+
+public class Main {
+
+       public static void main(String[] args) {
+               String mainClass = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
+               if (mainClass == null) {
+                       throw new IllegalArgumentException(
+                                       "System property " + OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS + " must be specified");
+               }
+
+               OsgiBuilder osgi = new OsgiBuilder();
+               String distributionUrl = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_DISTRIBUTION_URL);
+               if (distributionUrl != null)
+                       osgi.install(distributionUrl);
+               // osgi.conf("argeo.node.useradmin.uris", "os:///");
+               // osgi.conf("osgi.clean", "true");
+               // osgi.conf("osgi.console", "true");
+               osgi.launch();
+
+               if (OsgiBootUtils.isDebug()) {
+                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+                       String jvmUptimeStr = (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
+                       OsgiBootUtils.debug("Ready to launch " + mainClass + " in " + jvmUptimeStr);
+               }
+
+               osgi.main(mainClass, args);
+
+               osgi.shutdown();
+
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java b/org.argeo.init/src/org/argeo/init/osgi/NodeRunner.java
new file mode 100644 (file)
index 0000000..3369650
--- /dev/null
@@ -0,0 +1,235 @@
+package org.argeo.init.osgi;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ServiceLoader;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+/** Launch an OSGi framework and deploy a CMS Node into it. */
+public class NodeRunner {
+       private Long timeout = 30 * 1000l;
+       private final Path baseDir;
+       private final Path confDir;
+       private final Path dataDir;
+
+       private String baseUrl = "http://forge.argeo.org/data/java/argeo-2.1/";
+       private String distributionUrl = null;
+
+       private Framework framework = null;
+
+       public NodeRunner(String distributionUrl, Path baseDir) {
+               this.distributionUrl = distributionUrl;
+               Path mavenBase = Paths.get(System.getProperty("user.home") + "/.m2/repository");
+               Path osgiBase = Paths.get("/user/share/osgi");
+               if (Files.exists(mavenBase)) {
+                       Path mavenPath = mavenBase.resolve(distributionUrl);
+                       if (Files.exists(mavenPath))
+                               baseUrl = mavenBase.toUri().toString();
+               } else if (Files.exists(osgiBase)) {
+                       Path osgiPath = osgiBase.resolve(distributionUrl);
+                       if (Files.exists(osgiPath))
+                               baseUrl = osgiBase.toUri().toString();
+               }
+
+               this.baseDir = baseDir;
+               this.confDir = this.baseDir.resolve("state");
+               this.dataDir = this.baseDir.resolve("data");
+
+       }
+
+       public void start() {
+               long begin = System.currentTimeMillis();
+               // log4j
+               Path log4jFile = confDir.resolve("log4j.properties");
+               if (!Files.exists(log4jFile))
+                       copyResource("/org/argeo/osgi/boot/log4j.properties", log4jFile);
+               System.setProperty("log4j.configuration", "file://" + log4jFile.toAbsolutePath());
+
+               // Start Equinox
+               try {
+                       ServiceLoader<FrameworkFactory> ff = ServiceLoader.load(FrameworkFactory.class);
+                       FrameworkFactory frameworkFactory = ff.iterator().next();
+                       Map<String, String> configuration = new HashMap<String, String>();
+                       configuration.put("osgi.configuration.area", confDir.toAbsolutePath().toString());
+                       configuration.put("osgi.instance.area", dataDir.toAbsolutePath().toString());
+                       defaultConfiguration(configuration);
+
+                       framework = frameworkFactory.newFramework(configuration);
+                       framework.start();
+                       info("## Date : " + new Date());
+                       info("## Data : " + dataDir.toAbsolutePath());
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start OSGi framework", e);
+               }
+               BundleContext bundleContext = framework.getBundleContext();
+               try {
+
+                       // Spring configs currently require System properties
+                       // System.getProperties().putAll(configuration);
+
+                       // expected by JAAS as System.property FIXME
+                       System.setProperty("osgi.instance.area", bundleContext.getProperty("osgi.instance.area"));
+
+                       // OSGi bootstrap
+                       OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
+
+                       osgiBoot.installUrls(osgiBoot.getDistributionUrls(distributionUrl, baseUrl));
+
+                       // Start runtime
+                       Properties startProperties = new Properties();
+                       // TODO make it possible to override it
+                       startProperties.put("argeo.osgi.start.2.node",
+                                       "org.eclipse.equinox.http.servlet,org.eclipse.equinox.http.jetty,"
+                                                       + "org.eclipse.equinox.metatype,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi");
+                       startProperties.put("argeo.osgi.start.3.node", "org.argeo.cms");
+                       startProperties.put("argeo.osgi.start.4.node",
+                                       "org.eclipse.gemini.blueprint.extender,org.eclipse.equinox.http.registry");
+                       osgiBoot.startBundles(startProperties);
+
+                       // Find node repository
+                       ServiceReference<?> sr = null;
+                       while (sr == null) {
+                               sr = bundleContext.getServiceReference("javax.jcr.Repository");
+                               if (System.currentTimeMillis() - begin > timeout)
+                                       throw new RuntimeException("Could find node after " + timeout + "ms");
+                               Thread.sleep(100);
+                       }
+                       Object nodeDeployment = bundleContext.getService(sr);
+                       info("Node Deployment " + nodeDeployment);
+
+                       // Initialization completed
+                       long duration = System.currentTimeMillis() - begin;
+                       info("## CMS Launcher initialized in " + (duration / 1000) + "s " + (duration % 1000) + "ms");
+               } catch (Exception e) {
+                       shutdown();
+                       throw new RuntimeException("Cannot start CMS", e);
+               } finally {
+
+               }
+       }
+
+       private void defaultConfiguration(Map<String, String> configuration) {
+               // all permissions to OSGi security manager
+               Path policyFile = confDir.resolve("node.policy");
+               if (!Files.exists(policyFile))
+                       copyResource("/org/argeo/osgi/boot/node.policy", policyFile);
+               configuration.put("java.security.policy", "file://" + policyFile.toAbsolutePath());
+
+               configuration.put("org.eclipse.rap.workbenchAutostart", "false");
+               configuration.put("org.eclipse.equinox.http.jetty.autostart", "false");
+               configuration.put("org.osgi.framework.bootdelegation",
+                               "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,"
+                                               + "com.sun.nio.file,com.sun.nio.sctp");
+
+               // Do clean
+               // configuration.put("osgi.clean", "true");
+               // if (args.length == 0) {
+               // configuration.put("osgi.console", "");
+               // }
+       }
+
+       public void shutdown() {
+               try {
+                       framework.stop();
+                       framework.waitForStop(15 * 1000);
+               } catch (Exception silent) {
+               }
+       }
+
+       public Path getConfDir() {
+               return confDir;
+       }
+
+       public Path getDataDir() {
+               return dataDir;
+       }
+
+       public Framework getFramework() {
+               return framework;
+       }
+
+       public static void main(String[] args) {
+               try {
+                       String distributionUrl;
+                       Path executionDir;
+                       if (args.length == 2) {
+                               distributionUrl = args[0];
+                               executionDir = Paths.get(args[1]);
+                       } else if (args.length == 1) {
+                               executionDir = Paths.get(System.getProperty("user.dir"));
+                               distributionUrl = args[0];
+                       } else if (args.length == 0) {
+                               executionDir = Paths.get(System.getProperty("user.dir"));
+                               distributionUrl = "org/argeo/commons/org.argeo.dep.cms.sdk/2.1.70/org.argeo.dep.cms.sdk-2.1.70.jar";
+                       }else{
+                               printUsage();
+                               System.exit(1);
+                               return;
+                       }
+
+                       NodeRunner nodeRunner = new NodeRunner(distributionUrl, executionDir);
+                       nodeRunner.start();
+//                     if (args.length != 0)
+//                             System.exit(0);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+       protected static void info(Object msg) {
+               System.out.println(msg);
+       }
+
+       protected static void err(Object msg) {
+               System.err.println(msg);
+       }
+
+       protected static void debug(Object msg) {
+               System.out.println(msg);
+       }
+
+       protected static void copyResource(String resource, Path targetFile) {
+               InputStream input = null;
+               OutputStream output = null;
+               try {
+                       input = NodeRunner.class.getResourceAsStream(resource);
+                       Files.createDirectories(targetFile.getParent());
+                       output = Files.newOutputStream(targetFile);
+                       byte[] buf = new byte[8192];
+                       while (true) {
+                               int length = input.read(buf);
+                               if (length < 0)
+                                       break;
+                               output.write(buf, 0, length);
+                       }
+               } catch (Exception e) {
+                       throw new RuntimeException("Cannot write " + resource + " file to " + targetFile, e);
+               } finally {
+                       try {
+                               input.close();
+                       } catch (Exception ignore) {
+                       }
+                       try {
+                               output.close();
+                       } catch (Exception ignore) {
+                       }
+               }
+
+       }
+
+       static void printUsage(){
+               err("Usage: <distribution url> <base dir>");
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBoot.java
new file mode 100644 (file)
index 0000000..8fbe4b2
--- /dev/null
@@ -0,0 +1,752 @@
+package org.argeo.init.osgi;
+
+import static org.argeo.init.osgi.OsgiBootUtils.debug;
+import static org.argeo.init.osgi.OsgiBootUtils.warn;
+
+import java.io.File;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+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 java.util.Properties;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+
+import org.argeo.init.a2.A2Source;
+import org.argeo.init.a2.ProvisioningManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+/**
+ * Basic provisioning of an OSGi runtime via file path patterns and system
+ * properties. The approach is to generate list of URLs based on various
+ * methods, configured via properties.
+ */
+public class OsgiBoot implements OsgiBootConstants {
+       public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
+       public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
+
+       public final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
+       public final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
+       public final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
+       public final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
+
+       // booleans
+       public final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
+       // public final static String PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN =
+       // "argeo.osgi.boot.excludeSvn";
+
+       public final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
+       public final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
+       public final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
+
+       public final static String DEFAULT_BASE_URL = "reference:file:";
+       // public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**";
+
+       // OSGi system properties
+       final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
+       final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
+       final static String INSTANCE_AREA_PROP = "osgi.instance.area";
+       final static String CONFIGURATION_AREA_PROP = "osgi.configuration.area";
+
+       // Symbolic names
+       public final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.osgi.boot";
+       public final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
+
+       /** Exclude svn metadata implicitely(a bit costly) */
+       // private boolean excludeSvn =
+       // Boolean.valueOf(System.getProperty(PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN,
+       // "false"))
+       // .booleanValue();
+
+       /** Default is 10s */
+       @Deprecated
+       private long defaultTimeout = 10000l;
+
+       private final BundleContext bundleContext;
+       private final String localCache;
+
+       private final ProvisioningManager provisioningManager;
+
+       /*
+        * INITIALIZATION
+        */
+       /** Constructor */
+       public OsgiBoot(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+               Path homePath = Paths.get(System.getProperty("user.home")).toAbsolutePath();
+               String homeUri = homePath.toUri().toString();
+               localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/");
+
+               provisioningManager = new ProvisioningManager(bundleContext);
+               String sources = getProperty(PROP_ARGEO_OSGI_SOURCES);
+               if (sources == null) {
+                       provisioningManager.registerDefaultSource();
+               } else {
+                       for (String source : sources.split(",")) {
+                               if (source.trim().equals(A2Source.DEFAULT_A2_URI)) {
+                                       if (Files.exists(homePath))
+                                               provisioningManager.registerSource(
+                                                               A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/a2");
+                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/a2");
+                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/a2");
+                               } else {
+                                       provisioningManager.registerSource(source);
+                               }
+                       }
+               }
+       }
+
+       ProvisioningManager getProvisioningManager() {
+               return provisioningManager;
+       }
+
+       /*
+        * HIGH-LEVEL METHODS
+        */
+       /** Bootstraps the OSGi runtime */
+       public void bootstrap() {
+               try {
+                       long begin = System.currentTimeMillis();
+                       System.out.println();
+                       String osgiInstancePath = bundleContext.getProperty(INSTANCE_AREA_PROP);
+                       OsgiBootUtils
+                                       .info("OSGi bootstrap starting" + (osgiInstancePath != null ? " (" + osgiInstancePath + ")" : ""));
+                       installUrls(getBundlesUrls());
+                       installUrls(getDistributionUrls());
+                       provisioningManager.install(null);
+                       startBundles();
+                       long duration = System.currentTimeMillis() - begin;
+                       OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
+                                       + duration + "ms), " + bundleContext.getBundles().length + " bundles");
+               } catch (RuntimeException e) {
+                       OsgiBootUtils.error("OSGi bootstrap FAILED", e);
+                       throw e;
+               }
+
+               // diagnostics
+               if (OsgiBootUtils.debug) {
+                       OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
+                       diagnostics.checkUnresolved();
+                       Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
+                       if (duplicatePackages.size() > 0) {
+                               OsgiBootUtils.info("Packages exported twice:");
+                               Iterator<String> it = duplicatePackages.keySet().iterator();
+                               while (it.hasNext()) {
+                                       String pkgName = it.next();
+                                       OsgiBootUtils.info(pkgName);
+                                       Set<String> bdles = duplicatePackages.get(pkgName);
+                                       Iterator<String> bdlesIt = bdles.iterator();
+                                       while (bdlesIt.hasNext())
+                                               OsgiBootUtils.info("  " + bdlesIt.next());
+                               }
+                       }
+               }
+               System.out.println();
+       }
+
+       public void update() {
+               provisioningManager.update();
+       }
+
+       /*
+        * INSTALLATION
+        */
+       /** Install a single url. Convenience method. */
+       public Bundle installUrl(String url) {
+               List<String> urls = new ArrayList<String>();
+               urls.add(url);
+               installUrls(urls);
+               return (Bundle) getBundlesByLocation().get(url);
+       }
+
+       /** Install the bundles at this URL list. */
+       public void installUrls(List<String> urls) {
+               Map<String, Bundle> installedBundles = getBundlesByLocation();
+               for (int i = 0; i < urls.size(); i++) {
+                       String url = (String) urls.get(i);
+                       installUrl(url, installedBundles);
+               }
+               refreshFramework();
+       }
+
+       /** Actually install the provided URL */
+       protected void installUrl(String url, Map<String, Bundle> installedBundles) {
+               try {
+                       if (installedBundles.containsKey(url)) {
+                               Bundle bundle = (Bundle) installedBundles.get(url);
+                               if (OsgiBootUtils.debug)
+                                       debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url);
+                       } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/")
+                                       || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) {
+                               if (OsgiBootUtils.debug)
+                                       warn("Skip " + url);
+                               return;
+                       } else {
+                               Bundle bundle = bundleContext.installBundle(url);
+                               if (url.startsWith("http"))
+                                       OsgiBootUtils
+                                                       .info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
+                               else if (OsgiBootUtils.debug)
+                                       OsgiBootUtils.debug(
+                                                       "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
+                               assert bundle.getSymbolicName() != null;
+                               // uninstall previous versions
+                               bundles: for (Bundle b : bundleContext.getBundles()) {
+                                       if (b.getSymbolicName() == null)
+                                               continue bundles;
+                                       if (bundle.getSymbolicName().equals(b.getSymbolicName())) {
+                                               Version bundleV = bundle.getVersion();
+                                               Version bV = b.getVersion();
+                                               if (bV == null)
+                                                       continue bundles;
+                                               if (bundleV.getMajor() == bV.getMajor() && bundleV.getMinor() == bV.getMinor()) {
+                                                       if (bundleV.getMicro() > bV.getMicro()) {
+                                                               // uninstall older bundles
+                                                               b.uninstall();
+                                                               OsgiBootUtils.debug("Uninstalled " + b);
+                                                       } else if (bundleV.getMicro() < bV.getMicro()) {
+                                                               // uninstall just installed bundle if newer
+                                                               bundle.uninstall();
+                                                               OsgiBootUtils.debug("Uninstalled " + bundle);
+                                                               break bundles;
+                                                       } else {
+                                                               // uninstall any other with same major/minor
+                                                               if (!bundleV.getQualifier().equals(bV.getQualifier())) {
+                                                                       b.uninstall();
+                                                                       OsgiBootUtils.debug("Uninstalled " + b);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+               } catch (BundleException e) {
+                       final String ALREADY_INSTALLED = "is already installed";
+                       String message = e.getMessage();
+                       if ((message.contains("Bundle \"" + SYMBOLIC_NAME_OSGI_BOOT + "\"")
+                                       || message.contains("Bundle \"" + SYMBOLIC_NAME_EQUINOX + "\""))
+                                       && message.contains(ALREADY_INSTALLED)) {
+                               // silent, in order to avoid warnings: we know that both
+                               // have already been installed...
+                       } else {
+                               if (message.contains(ALREADY_INSTALLED)) {
+                                       if (OsgiBootUtils.isDebug())
+                                               OsgiBootUtils.warn("Duplicate install from " + url + ": " + message);
+                               } else
+                                       OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message);
+                       }
+                       if (OsgiBootUtils.debug && !message.contains(ALREADY_INSTALLED))
+                               e.printStackTrace();
+               }
+       }
+
+       /*
+        * START
+        */
+       public void startBundles() {
+               startBundles(System.getProperties());
+       }
+
+       public void startBundles(Properties properties) {
+               FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
+
+               // default and active start levels from System properties
+               Integer defaultStartLevel = Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4"));
+               Integer activeStartLevel = Integer.parseInt(getProperty(PROP_OSGI_STARTLEVEL, "6"));
+
+               SortedMap<Integer, List<String>> startLevels = new TreeMap<Integer, List<String>>();
+               computeStartLevels(startLevels, properties, defaultStartLevel);
+               // inverts the map for the time being, TODO optimise
+               Map<String, Integer> bundleStartLevels = new HashMap<>();
+               for (Integer level : startLevels.keySet()) {
+                       for (String bsn : startLevels.get(level))
+                               bundleStartLevels.put(bsn, level);
+               }
+               for (Bundle bundle : bundleContext.getBundles()) {
+                       String bsn = bundle.getSymbolicName();
+                       if (bundleStartLevels.containsKey(bsn)) {
+                               BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class);
+                               Integer level = bundleStartLevels.get(bsn);
+                               if (bundleStartLevel.getStartLevel() != level || !bundleStartLevel.isPersistentlyStarted()) {
+                                       bundleStartLevel.setStartLevel(level);
+                                       try {
+                                               bundle.start();
+                                       } catch (BundleException e) {
+                                               OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
+                                       }
+                                       if (getDebug())
+                                               OsgiBootUtils.debug(bsn + " starts at level " + level);
+                               }
+                       }
+               }
+               frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
+                       if (getDebug())
+                               OsgiBootUtils.debug("Framework event: " + event);
+                       int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
+                       int startLevel = frameworkStartLevel.getStartLevel();
+                       OsgiBootUtils.debug("Framework start level: " + startLevel + " (initial: " + initialStartLevel + ")");
+               });
+       }
+
+       private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Properties properties,
+                       Integer defaultStartLevel) {
+
+               // default (and previously, only behaviour)
+               appendToStartLevels(startLevels, defaultStartLevel, properties.getProperty(PROP_ARGEO_OSGI_START, ""));
+
+               // list argeo.osgi.start.* system properties
+               Iterator<Object> keys = properties.keySet().iterator();
+               final String prefix = PROP_ARGEO_OSGI_START + ".";
+               while (keys.hasNext()) {
+                       String key = keys.next().toString();
+                       if (key.startsWith(prefix)) {
+                               Integer startLevel;
+                               String suffix = key.substring(prefix.length());
+                               String[] tokens = suffix.split("\\.");
+                               if (tokens.length > 0 && !tokens[0].trim().equals(""))
+                                       try {
+                                               // first token is start level
+                                               startLevel = Integer.parseInt(tokens[0]);
+                                       } catch (NumberFormatException e) {
+                                               startLevel = defaultStartLevel;
+                                       }
+                               else
+                                       startLevel = defaultStartLevel;
+
+                               // append bundle names
+                               String bundleNames = properties.getProperty(key);
+                               appendToStartLevels(startLevels, startLevel, bundleNames);
+                       }
+               }
+       }
+
+       /** Append a comma-separated list of bundles to the start levels. */
+       private static void appendToStartLevels(SortedMap<Integer, List<String>> startLevels, Integer startLevel,
+                       String str) {
+               if (str == null || str.trim().equals(""))
+                       return;
+
+               if (!startLevels.containsKey(startLevel))
+                       startLevels.put(startLevel, new ArrayList<String>());
+               String[] bundleNames = str.split(",");
+               for (int i = 0; i < bundleNames.length; i++) {
+                       if (bundleNames[i] != null && !bundleNames[i].trim().equals(""))
+                               (startLevels.get(startLevel)).add(bundleNames[i]);
+               }
+       }
+
+       /**
+        * Start the provided list of bundles
+        *
+        * @return whether all bundles are now in active state
+        * @deprecated
+        */
+       @Deprecated
+       public boolean startBundles(List<String> bundlesToStart) {
+               if (bundlesToStart.size() == 0)
+                       return true;
+
+               // used to monitor ACTIVE states
+               List<Bundle> startedBundles = new ArrayList<Bundle>();
+               // used to log the bundles not found
+               List<String> notFoundBundles = new ArrayList<String>(bundlesToStart);
+
+               Bundle[] bundles = bundleContext.getBundles();
+               long startBegin = System.currentTimeMillis();
+               for (int i = 0; i < bundles.length; i++) {
+                       Bundle bundle = bundles[i];
+                       String symbolicName = bundle.getSymbolicName();
+                       if (bundlesToStart.contains(symbolicName))
+                               try {
+                                       try {
+                                               bundle.start();
+                                               if (OsgiBootUtils.debug)
+                                                       debug("Bundle " + symbolicName + " started");
+                                       } catch (Exception e) {
+                                               OsgiBootUtils.warn("Start of bundle " + symbolicName + " failed because of " + e
+                                                               + ", maybe bundle is not yet resolved," + " waiting and trying again.");
+                                               waitForBundleResolvedOrActive(startBegin, bundle);
+                                               bundle.start();
+                                               startedBundles.add(bundle);
+                                       }
+                                       notFoundBundles.remove(symbolicName);
+                               } catch (Exception e) {
+                                       OsgiBootUtils.warn("Bundle " + symbolicName + " cannot be started: " + e.getMessage());
+                                       if (OsgiBootUtils.debug)
+                                               e.printStackTrace();
+                                       // was found even if start failed
+                                       notFoundBundles.remove(symbolicName);
+                               }
+               }
+
+               for (int i = 0; i < notFoundBundles.size(); i++)
+                       OsgiBootUtils.warn("Bundle '" + notFoundBundles.get(i) + "' not started because it was not found.");
+
+               // monitors that all bundles are started
+               long beginMonitor = System.currentTimeMillis();
+               boolean allStarted = !(startedBundles.size() > 0);
+               List<String> notStarted = new ArrayList<String>();
+               while (!allStarted && (System.currentTimeMillis() - beginMonitor) < defaultTimeout) {
+                       notStarted = new ArrayList<String>();
+                       allStarted = true;
+                       for (int i = 0; i < startedBundles.size(); i++) {
+                               Bundle bundle = (Bundle) startedBundles.get(i);
+                               // TODO check behaviour of lazs bundles
+                               if (bundle.getState() != Bundle.ACTIVE) {
+                                       allStarted = false;
+                                       notStarted.add(bundle.getSymbolicName());
+                               }
+                       }
+                       try {
+                               Thread.sleep(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+               }
+               long duration = System.currentTimeMillis() - beginMonitor;
+
+               if (!allStarted)
+                       for (int i = 0; i < notStarted.size(); i++)
+                               OsgiBootUtils.warn("Bundle '" + notStarted.get(i) + "' not ACTIVE after " + (duration / 1000) + "s");
+
+               return allStarted;
+       }
+
+       /** Waits for a bundle to become active or resolved */
+       @Deprecated
+       private void waitForBundleResolvedOrActive(long startBegin, Bundle bundle) throws Exception {
+               int originalState = bundle.getState();
+               if ((originalState == Bundle.RESOLVED) || (originalState == Bundle.ACTIVE))
+                       return;
+
+               String originalStateStr = OsgiBootUtils.stateAsString(originalState);
+
+               int currentState = bundle.getState();
+               while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) {
+                       long now = System.currentTimeMillis();
+                       if ((now - startBegin) > defaultTimeout * 10)
+                               throw new Exception("Bundle " + bundle.getSymbolicName() + " was not RESOLVED or ACTIVE after "
+                                               + (now - startBegin) + "ms (originalState=" + originalStateStr + ", currentState="
+                                               + OsgiBootUtils.stateAsString(currentState) + ")");
+
+                       try {
+                               Thread.sleep(100l);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       currentState = bundle.getState();
+               }
+       }
+
+       /*
+        * BUNDLE PATTERNS INSTALLATION
+        */
+       /**
+        * Computes a list of URLs based on Ant-like include/exclude patterns defined by
+        * ${argeo.osgi.bundles} with the following format:<br>
+        * <code>/base/directory;in=*.jar;in=**;ex=org.eclipse.osgi_*;jar</code><br>
+        * WARNING: <code>/base/directory;in=*.jar,\</code> at the end of a file,
+        * without a new line causes a '.' to be appended with unexpected side effects.
+        */
+       public List<String> getBundlesUrls() {
+               String bundlePatterns = getProperty(PROP_ARGEO_OSGI_BUNDLES);
+               return getBundlesUrls(bundlePatterns);
+       }
+
+       /**
+        * Compute a list of URLs to install based on the provided patterns, with
+        * default base url
+        */
+       public List<String> getBundlesUrls(String bundlePatterns) {
+               String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL, DEFAULT_BASE_URL);
+               return getBundlesUrls(baseUrl, bundlePatterns);
+       }
+
+       /** Implements the path matching logic */
+       public List<String> getBundlesUrls(String baseUrl, String bundlePatterns) {
+               List<String> urls = new ArrayList<String>();
+               if (bundlePatterns == null)
+                       return urls;
+
+//             bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
+               if (OsgiBootUtils.debug)
+                       debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
+
+               StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
+               List<BundlesSet> bundlesSets = new ArrayList<BundlesSet>();
+               while (st.hasMoreTokens()) {
+                       String token = st.nextToken();
+                       if (new File(token).exists()) {
+                               String url = locationToUrl(baseUrl, token);
+                               urls.add(url);
+                       } else
+                               bundlesSets.add(new BundlesSet(token));
+               }
+
+               // find included
+               List<String> included = new ArrayList<String>();
+//             PathMatcher matcher = new AntPathMatcher();
+               for (int i = 0; i < bundlesSets.size(); i++) {
+                       BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
+                       for (int j = 0; j < bundlesSet.getIncludes().size(); j++) {
+                               String pattern = (String) bundlesSet.getIncludes().get(j);
+                               match(included, bundlesSet.getDir(), null, pattern);
+                       }
+               }
+
+               // find excluded
+               List<String> excluded = new ArrayList<String>();
+               for (int i = 0; i < bundlesSets.size(); i++) {
+                       BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
+                       for (int j = 0; j < bundlesSet.getExcludes().size(); j++) {
+                               String pattern = (String) bundlesSet.getExcludes().get(j);
+                               match(excluded, bundlesSet.getDir(), null, pattern);
+                       }
+               }
+
+               // construct list
+               for (int i = 0; i < included.size(); i++) {
+                       String fullPath = (String) included.get(i);
+                       if (!excluded.contains(fullPath))
+                               urls.add(locationToUrl(baseUrl, fullPath));
+               }
+
+               return urls;
+       }
+
+       /*
+        * DISTRIBUTION JAR INSTALLATION
+        */
+       public List<String> getDistributionUrls() {
+               String distributionUrl = getProperty(PROP_ARGEO_OSGI_DISTRIBUTION_URL);
+               String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL);
+               return getDistributionUrls(distributionUrl, baseUrl);
+       }
+
+       public List<String> getDistributionUrls(String distributionUrl, String baseUrl) {
+               List<String> urls = new ArrayList<String>();
+               if (distributionUrl == null)
+                       return urls;
+
+               DistributionBundle distributionBundle;
+               if (distributionUrl.startsWith("http") || distributionUrl.startsWith("file")) {
+                       distributionBundle = new DistributionBundle(distributionUrl);
+                       if (baseUrl != null)
+                               distributionBundle.setBaseUrl(baseUrl);
+               } else {
+                       // relative url
+                       if (baseUrl == null) {
+                               baseUrl = localCache;
+                       }
+
+                       if (distributionUrl.contains(":")) {
+                               // TODO make it safer
+                               String[] parts = distributionUrl.trim().split(":");
+                               String[] categoryParts = parts[0].split("\\.");
+                               String artifactId = parts[1];
+                               String version = parts[2];
+                               StringBuilder sb = new StringBuilder();
+                               for (String categoryPart : categoryParts) {
+                                       sb.append(categoryPart).append('/');
+                               }
+                               sb.append(artifactId).append('/');
+                               sb.append(version).append('/');
+                               sb.append(artifactId).append('-').append(version).append(".jar");
+                               distributionUrl = sb.toString();
+                       }
+
+                       distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache);
+               }
+               // if (baseUrl != null && !(distributionUrl.startsWith("http") ||
+               // distributionUrl.startsWith("file"))) {
+               // // relative url
+               // distributionBundle = new DistributionBundle(baseUrl, distributionUrl,
+               // localCache);
+               // } else {
+               // distributionBundle = new DistributionBundle(distributionUrl);
+               // if (baseUrl != null)
+               // distributionBundle.setBaseUrl(baseUrl);
+               // }
+               distributionBundle.processUrl();
+               return distributionBundle.listUrls();
+       }
+
+       /*
+        * HIGH LEVEL UTILITIES
+        */
+       /** Actually performs the matching logic. */
+       protected void match(List<String> matched, String base, String currentPath, String pattern) {
+               if (currentPath == null) {
+                       // Init
+                       File baseDir = new File(base.replace('/', File.separatorChar));
+                       File[] files = baseDir.listFiles();
+
+                       if (files == null) {
+                               if (OsgiBootUtils.debug)
+                                       OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
+                                                       + ", isDirectory=" + baseDir.isDirectory());
+                               return;
+                       }
+
+                       for (int i = 0; i < files.length; i++)
+                               match(matched, base, files[i].getName(), pattern);
+               } else {
+                       PathMatcher matcher = FileSystems.getDefault().getPathMatcher(pattern);
+                       String fullPath = base + '/' + currentPath;
+                       if (matched.contains(fullPath))
+                               return;// don't try deeper if already matched
+
+                       boolean ok = matcher.matches(Paths.get(currentPath));
+                       // if (debug)
+                       // debug(currentPath + " " + (ok ? "" : " not ")
+                       // + " matched with " + pattern);
+                       if (ok) {
+                               matched.add(fullPath);
+                               return;
+                       } else {
+                               String newFullPath = relativeToFullPath(base, currentPath);
+                               File newFile = new File(newFullPath);
+                               File[] files = newFile.listFiles();
+                               if (files != null) {
+                                       for (int i = 0; i < files.length; i++) {
+                                               String newCurrentPath = currentPath + '/' + files[i].getName();
+                                               if (files[i].isDirectory()) {
+//                                                     if (matcher.matchStart(pattern, newCurrentPath)) {
+                                                       // FIXME recurse only if start matches ?
+                                                       match(matched, base, newCurrentPath, pattern);
+//                                                     } else {
+//                                                             if (OsgiBootUtils.debug)
+//                                                                     debug(newCurrentPath + " does not start match with " + pattern);
+//
+//                                                     }
+                                               } else {
+                                                       boolean nonDirectoryOk = matcher.matches(Paths.get(newCurrentPath));
+                                                       if (OsgiBootUtils.debug)
+                                                               debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
+                                                       if (nonDirectoryOk)
+                                                               matched.add(relativeToFullPath(base, newCurrentPath));
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       protected void matchFile() {
+
+       }
+
+       /*
+        * LOW LEVEL UTILITIES
+        */
+       /**
+        * The bundles already installed. Key is location (String) , value is a
+        * {@link Bundle}
+        */
+       public Map<String, Bundle> getBundlesByLocation() {
+               Map<String, Bundle> installedBundles = new HashMap<String, Bundle>();
+               Bundle[] bundles = bundleContext.getBundles();
+               for (int i = 0; i < bundles.length; i++) {
+                       installedBundles.put(bundles[i].getLocation(), bundles[i]);
+               }
+               return installedBundles;
+       }
+
+       /**
+        * The bundles already installed. Key is symbolic name (String) , value is a
+        * {@link Bundle}
+        */
+       public Map<String, Bundle> getBundlesBySymbolicName() {
+               Map<String, Bundle> namedBundles = new HashMap<String, Bundle>();
+               Bundle[] bundles = bundleContext.getBundles();
+               for (int i = 0; i < bundles.length; i++) {
+                       namedBundles.put(bundles[i].getSymbolicName(), bundles[i]);
+               }
+               return namedBundles;
+       }
+
+       /** Creates an URL from a location */
+       protected String locationToUrl(String baseUrl, String location) {
+               return baseUrl + location;
+       }
+
+       /** Transforms a relative path in a full system path. */
+       protected String relativeToFullPath(String basePath, String relativePath) {
+               return (basePath + '/' + relativePath).replace('/', File.separatorChar);
+       }
+
+       private void refreshFramework() {
+               Bundle systemBundle = bundleContext.getBundle(0);
+               FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
+               frameworkWiring.refreshBundles(null);
+       }
+
+       /**
+        * Gets a property value
+        * 
+        * @return null when defaultValue is ""
+        */
+       public String getProperty(String name, String defaultValue) {
+               String value = bundleContext.getProperty(name);
+               if (value == null)
+                       return defaultValue; // may be null
+               else
+                       return value;
+       }
+
+       public String getProperty(String name) {
+               return getProperty(name, null);
+       }
+
+       /*
+        * BEAN METHODS
+        */
+
+       public boolean getDebug() {
+               return OsgiBootUtils.debug;
+       }
+
+       // public void setDebug(boolean debug) {
+       // this.debug = debug;
+       // }
+
+       public BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       public String getLocalCache() {
+               return localCache;
+       }
+
+       // public void setDefaultTimeout(long defaultTimeout) {
+       // this.defaultTimeout = defaultTimeout;
+       // }
+
+       // public boolean isExcludeSvn() {
+       // return excludeSvn;
+       // }
+       //
+       // public void setExcludeSvn(boolean excludeSvn) {
+       // this.excludeSvn = excludeSvn;
+       // }
+
+       /*
+        * INTERNAL CLASSES
+        */
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootConstants.java
new file mode 100644 (file)
index 0000000..e45f826
--- /dev/null
@@ -0,0 +1,5 @@
+package org.argeo.init.osgi;
+
+public interface OsgiBootConstants {
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootDiagnostics.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootDiagnostics.java
new file mode 100644 (file)
index 0000000..72d9a3e
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.init.osgi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+@SuppressWarnings("deprecation")
+class OsgiBootDiagnostics {
+       private final BundleContext bundleContext;
+
+       public OsgiBootDiagnostics(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+       }
+       /*
+        * DIAGNOSTICS
+        */
+       /** Check unresolved bundles */
+       protected void checkUnresolved() {
+               // Refresh
+               ServiceReference<PackageAdmin> packageAdminRef = bundleContext.getServiceReference(PackageAdmin.class);
+               PackageAdmin packageAdmin = (PackageAdmin) bundleContext.getService(packageAdminRef);
+               packageAdmin.resolveBundles(null);
+
+               Bundle[] bundles = bundleContext.getBundles();
+               List<Bundle> unresolvedBundles = new ArrayList<Bundle>();
+               for (int i = 0; i < bundles.length; i++) {
+                       int bundleState = bundles[i].getState();
+                       if (!(bundleState == Bundle.ACTIVE || bundleState == Bundle.RESOLVED || bundleState == Bundle.STARTING))
+                               unresolvedBundles.add(bundles[i]);
+               }
+
+               if (unresolvedBundles.size() != 0) {
+                       OsgiBootUtils.warn("Unresolved bundles " + unresolvedBundles);
+               }
+       }
+
+       /** List packages exported twice. */
+       public Map<String, Set<String>> findPackagesExportedTwice() {
+               ServiceReference<PackageAdmin> paSr = bundleContext.getServiceReference(PackageAdmin.class);
+               PackageAdmin packageAdmin = (PackageAdmin) bundleContext.getService(paSr);
+
+               // find packages exported twice
+               Bundle[] bundles = bundleContext.getBundles();
+               Map<String, Set<String>> exportedPackages = new TreeMap<String, Set<String>>();
+               for (int i = 0; i < bundles.length; i++) {
+                       Bundle bundle = bundles[i];
+                       ExportedPackage[] pkgs = packageAdmin.getExportedPackages(bundle);
+                       if (pkgs != null)
+                               for (int j = 0; j < pkgs.length; j++) {
+                                       String pkgName = pkgs[j].getName();
+                                       if (!exportedPackages.containsKey(pkgName)) {
+                                               exportedPackages.put(pkgName, new TreeSet<String>());
+                                       }
+                                       (exportedPackages.get(pkgName)).add(bundle.getSymbolicName() + "_" + bundle.getVersion());
+                               }
+               }
+               Map<String, Set<String>> duplicatePackages = new TreeMap<String, Set<String>>();
+               Iterator<String> it = exportedPackages.keySet().iterator();
+               while (it.hasNext()) {
+                       String pkgName = it.next().toString();
+                       Set<String> bdles = exportedPackages.get(pkgName);
+                       if (bdles.size() > 1)
+                               duplicatePackages.put(pkgName, bdles);
+               }
+               return duplicatePackages;
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBootUtils.java
new file mode 100644 (file)
index 0000000..d8efe83
--- /dev/null
@@ -0,0 +1,145 @@
+package org.argeo.init.osgi;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+/** Utilities, mostly related to logging. */
+public class OsgiBootUtils {
+       /** ISO8601 (as per log4j) and difference to UTC */
+       private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z");
+
+       static boolean debug = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG) == null ? false
+                       : !System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG).trim().equals("false");
+
+       public static void info(Object obj) {
+               System.out.println("# OSGiBOOT      # " + dateFormat.format(new Date()) + " # " + obj);
+       }
+
+       public static void debug(Object obj) {
+               if (debug)
+                       System.out.println("# OSGiBOOT DBG  # " + dateFormat.format(new Date()) + " # " + obj);
+       }
+
+       public static void warn(Object obj) {
+               System.out.println("# OSGiBOOT WARN # " + dateFormat.format(new Date()) + " # " + obj);
+       }
+
+       public static void error(Object obj, Throwable e) {
+               System.err.println("# OSGiBOOT ERR  # " + dateFormat.format(new Date()) + " # " + obj);
+               if (e != null)
+                       e.printStackTrace();
+       }
+
+       public static boolean isDebug() {
+               return debug;
+       }
+
+       public static String stateAsString(int state) {
+               switch (state) {
+               case Bundle.UNINSTALLED:
+                       return "UNINSTALLED";
+               case Bundle.INSTALLED:
+                       return "INSTALLED";
+               case Bundle.RESOLVED:
+                       return "RESOLVED";
+               case Bundle.STARTING:
+                       return "STARTING";
+               case Bundle.ACTIVE:
+                       return "ACTIVE";
+               case Bundle.STOPPING:
+                       return "STOPPING";
+               default:
+                       return Integer.toString(state);
+               }
+       }
+
+       /**
+        * @return ==0: versions are identical, &lt;0: tested version is newer, &gt;0:
+        *         currentVersion is newer.
+        */
+       public static int compareVersions(String currentVersion, String testedVersion) {
+               List<String> cToks = new ArrayList<String>();
+               StringTokenizer cSt = new StringTokenizer(currentVersion, ".");
+               while (cSt.hasMoreTokens())
+                       cToks.add(cSt.nextToken());
+               List<String> tToks = new ArrayList<String>();
+               StringTokenizer tSt = new StringTokenizer(currentVersion, ".");
+               while (tSt.hasMoreTokens())
+                       tToks.add(tSt.nextToken());
+
+               int comp = 0;
+               comp: for (int i = 0; i < cToks.size(); i++) {
+                       if (tToks.size() <= i) {
+                               // equals until then, tested shorter
+                               comp = 1;
+                               break comp;
+                       }
+
+                       String c = (String) cToks.get(i);
+                       String t = (String) tToks.get(i);
+
+                       try {
+                               int cInt = Integer.parseInt(c);
+                               int tInt = Integer.parseInt(t);
+                               if (cInt == tInt)
+                                       continue comp;
+                               else {
+                                       comp = (cInt - tInt);
+                                       break comp;
+                               }
+                       } catch (NumberFormatException e) {
+                               if (c.equals(t))
+                                       continue comp;
+                               else {
+                                       comp = c.compareTo(t);
+                                       break comp;
+                               }
+                       }
+               }
+
+               if (comp == 0 && tToks.size() > cToks.size()) {
+                       // equals until then, current shorter
+                       comp = -1;
+               }
+
+               return comp;
+       }
+
+       public static Framework launch(Map<String, String> configuration) {
+               Optional<FrameworkFactory> frameworkFactory = ServiceLoader.load(FrameworkFactory.class).findFirst();
+               if (frameworkFactory.isEmpty())
+                       throw new IllegalStateException("No framework factory found");
+               return launch(frameworkFactory.get(), configuration);
+       }
+
+       /** Launch an OSGi framework. */
+       public static Framework launch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {
+               // start OSGi
+               Framework framework = frameworkFactory.newFramework(configuration);
+               try {
+                       framework.start();
+               } catch (BundleException e) {
+                       throw new IllegalStateException("Cannot start OSGi framework", e);
+               }
+               return framework;
+       }
+
+       public static Map<String, String> equinoxArgsToConfiguration(String[] args) {
+               // FIXME implement it
+               return new HashMap<>();
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiBuilder.java
new file mode 100644 (file)
index 0000000..39c42cb
--- /dev/null
@@ -0,0 +1,319 @@
+package org.argeo.init.osgi;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.BundleTracker;
+import org.osgi.util.tracker.ServiceTracker;
+
+/** OSGi builder, focusing on ease of use for scripting. */
+public class OsgiBuilder {
+       private final static String PROP_HTTP_PORT = "org.osgi.service.http.port";
+       private final static String PROP_HTTPS_PORT = "org.osgi.service.https.port";
+       private final static String PROP_OSGI_CLEAN = "osgi.clean";
+
+       private Map<Integer, StartLevel> startLevels = new TreeMap<>();
+       private List<String> distributionBundles = new ArrayList<>();
+
+       private Map<String, String> configuration = new HashMap<String, String>();
+       private Framework framework;
+       private String baseUrl = null;
+
+       public OsgiBuilder() {
+               // configuration.put("osgi.clean", "true");
+               configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
+               configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
+               configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
+       }
+
+       public Framework launch() {
+               // start OSGi
+               framework = OsgiBootUtils.launch(configuration);
+
+               BundleContext bc = framework.getBundleContext();
+               String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
+               // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
+               String osgiConf = framework.getDataFile("").getAbsolutePath();
+               if (OsgiBootUtils.isDebug())
+                       OsgiBootUtils.debug("OSGi starting - data: " + osgiData + " conf: " + osgiConf);
+
+               OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
+               if (distributionBundles.isEmpty()) {
+                       osgiBoot.getProvisioningManager().install(null);
+               } else {
+                       // install bundles
+                       for (String distributionBundle : distributionBundles) {
+                               List<String> bundleUrls = osgiBoot.getDistributionUrls(distributionBundle, baseUrl);
+                               osgiBoot.installUrls(bundleUrls);
+                       }
+               }
+               // start bundles
+               osgiBoot.startBundles(startLevelsToProperties());
+
+               // if (OsgiBootUtils.isDebug())
+               // for (Bundle bundle : bc.getBundles()) {
+               // OsgiBootUtils.debug(bundle.getLocation());
+               // }
+               return framework;
+       }
+
+       public OsgiBuilder conf(String key, String value) {
+               checkNotLaunched();
+               configuration.put(key, value);
+               return this;
+       }
+
+       public OsgiBuilder install(String uri) {
+               // TODO dynamic install
+               checkNotLaunched();
+               if (!distributionBundles.contains(uri))
+                       distributionBundles.add(uri);
+               return this;
+       }
+
+       public OsgiBuilder start(int startLevel, String bundle) {
+               // TODO dynamic start
+               checkNotLaunched();
+               StartLevel sl;
+               if (!startLevels.containsKey(startLevel))
+                       startLevels.put(startLevel, new StartLevel());
+               sl = startLevels.get(startLevel);
+               sl.add(bundle);
+               return this;
+       }
+
+       public OsgiBuilder waitForServlet(String base) {
+               service("(&(objectClass=javax.servlet.Servlet)(osgi.http.whiteboard.servlet.pattern=" + base + "))");
+               return this;
+       }
+
+       public OsgiBuilder waitForBundle(String bundles) {
+               List<String> lst = new ArrayList<>();
+               Collections.addAll(lst, bundles.split(","));
+               BundleTracker<Object> bt = new BundleTracker<Object>(getBc(), Bundle.ACTIVE, null) {
+
+                       @Override
+                       public Object addingBundle(Bundle bundle, BundleEvent event) {
+                               if (lst.contains(bundle.getSymbolicName())) {
+                                       return bundle.getSymbolicName();
+                               } else {
+                                       return null;
+                               }
+                       }
+               };
+               bt.open();
+               while (bt.getTrackingCount() != lst.size()) {
+                       try {
+                               Thread.sleep(500l);
+                       } catch (InterruptedException e) {
+                               break;
+                       }
+               }
+               bt.close();
+               return this;
+
+       }
+
+       public OsgiBuilder main(String clssUri, String[] args) {
+
+               // waitForBundle(bundleSymbolicName);
+               try {
+                       URI uri = new URI(clssUri);
+                       if (!"bundleclass".equals(uri.getScheme()))
+                               throw new IllegalArgumentException("Unsupported scheme for " + clssUri);
+                       String bundleSymbolicName = uri.getHost();
+                       String clss = uri.getPath().substring(1);
+                       Bundle bundle = null;
+                       for (Bundle b : getBc().getBundles()) {
+                               if (bundleSymbolicName.equals(b.getSymbolicName())) {
+                                       bundle = b;
+                                       break;
+                               }
+                       }
+                       if (bundle == null)
+                               throw new IllegalStateException("Bundle " + bundleSymbolicName + " not found");
+                       Class<?> c = bundle.loadClass(clss);
+                       Object[] mainArgs = { args };
+                       Method mainMethod = c.getMethod("main", String[].class);
+                       mainMethod.invoke(null, mainArgs);
+               } catch (Throwable e) {
+                       throw new RuntimeException("Cannot execute " + clssUri, e);
+               }
+               return this;
+       }
+
+       public Object service(String service) {
+               return service(service, 0);
+       }
+
+       public Object service(String service, long timeout) {
+               ServiceTracker<Object, Object> st;
+               if (service.contains("(")) {
+                       try {
+                               st = new ServiceTracker<>(getBc(), FrameworkUtil.createFilter(service), null);
+                       } catch (InvalidSyntaxException e) {
+                               throw new IllegalArgumentException("Badly formatted filter", e);
+                       }
+               } else {
+                       st = new ServiceTracker<>(getBc(), service, null);
+               }
+               st.open();
+               try {
+                       return st.waitForService(timeout);
+               } catch (InterruptedException e) {
+                       OsgiBootUtils.error("Interrupted", e);
+                       return null;
+               } finally {
+                       st.close();
+               }
+
+       }
+
+       public void shutdown() {
+               checkLaunched();
+               try {
+                       framework.stop();
+               } catch (BundleException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+               try {
+                       framework.waitForStop(10 * 60 * 1000);
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+               System.exit(0);
+       }
+
+       public void setHttpPort(Integer port) {
+               checkNotLaunched();
+               configuration.put(PROP_HTTP_PORT, Integer.toString(port));
+       }
+
+       public void setHttpsPort(Integer port) {
+               checkNotLaunched();
+               configuration.put(PROP_HTTPS_PORT, Integer.toString(port));
+       }
+
+       public void setClean(boolean clean) {
+               checkNotLaunched();
+               configuration.put(PROP_OSGI_CLEAN, Boolean.toString(clean));
+       }
+
+       public Integer getHttpPort() {
+               if (!isLaunched()) {
+                       if (configuration.containsKey(PROP_HTTP_PORT))
+                               return Integer.parseInt(configuration.get(PROP_HTTP_PORT));
+                       else
+                               return -1;
+               } else {
+                       // TODO wait for service?
+                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
+                       if (sr == null)
+                               return -1;
+                       Object port = sr.getProperty("http.port");
+                       if (port == null)
+                               return -1;
+                       return Integer.parseInt(port.toString());
+               }
+       }
+
+       public Integer getHttpsPort() {
+               if (!isLaunched()) {
+                       if (configuration.containsKey(PROP_HTTPS_PORT))
+                               return Integer.parseInt(configuration.get(PROP_HTTPS_PORT));
+                       else
+                               return -1;
+               } else {
+                       // TODO wait for service?
+                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
+                       if (sr == null)
+                               return -1;
+                       Object port = sr.getProperty("https.port");
+                       if (port == null)
+                               return -1;
+                       return Integer.parseInt(port.toString());
+               }
+       }
+
+       public Object spring(String bundle) {
+               return service("(&(Bundle-SymbolicName=" + bundle + ")"
+                               + "(objectClass=org.springframework.context.ApplicationContext))");
+       }
+
+       //
+       // BEAN
+       //
+
+       public BundleContext getBc() {
+               checkLaunched();
+               return framework.getBundleContext();
+       }
+
+       public void setBaseUrl(String baseUrl) {
+               this.baseUrl = baseUrl;
+       }
+
+       //
+       // UTILITIES
+       //
+       private Properties startLevelsToProperties() {
+               Properties properties = new Properties();
+               for (Integer startLevel : startLevels.keySet()) {
+                       String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel;
+                       StringBuilder value = new StringBuilder();
+                       for (String bundle : startLevels.get(startLevel).getBundles()) {
+                               value.append(bundle);
+                               value.append(',');
+                       }
+                       // TODO remove trailing comma
+                       properties.put(property, value.toString());
+               }
+               return properties;
+       }
+
+       private void checkLaunched() {
+               if (!isLaunched())
+                       throw new IllegalStateException("OSGi runtime is not launched");
+       }
+
+       private void checkNotLaunched() {
+               if (isLaunched())
+                       throw new IllegalStateException("OSGi runtime already launched");
+       }
+
+       private boolean isLaunched() {
+               return framework != null;
+       }
+
+       private static class StartLevel {
+               private Set<String> bundles = new HashSet<>();
+
+               public void add(String bundle) {
+                       String[] b = bundle.split(",");
+                       Collections.addAll(bundles, b);
+               }
+
+               public Set<String> getBundles() {
+                       return bundles;
+               }
+       }
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java b/org.argeo.init/src/org/argeo/init/osgi/OsgiRuntimeContext.java
new file mode 100644 (file)
index 0000000..6c3e40c
--- /dev/null
@@ -0,0 +1,104 @@
+package org.argeo.init.osgi;
+
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.concurrent.Flow;
+import java.util.function.Consumer;
+
+import org.argeo.init.RuntimeContext;
+import org.argeo.init.logging.ThinLoggerFinder;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+/** An OSGi runtime context. */
+public class OsgiRuntimeContext implements RuntimeContext {
+       private Map<String, String> config;
+       private Framework framework;
+       private OsgiBoot osgiBoot;
+
+       @SuppressWarnings("rawtypes")
+       private ServiceRegistration<Consumer> loggingConfigurationSr;
+       @SuppressWarnings("rawtypes")
+       private ServiceRegistration<Flow.Publisher> logEntryPublisherSr;
+
+       public OsgiRuntimeContext(Map<String, String> config) {
+               this.config = config;
+       }
+
+       public OsgiRuntimeContext(BundleContext bundleContext) {
+               start(bundleContext);
+       }
+
+       @Override
+       public void run() {
+               ServiceLoader<FrameworkFactory> sl = ServiceLoader.load(FrameworkFactory.class);
+               Optional<FrameworkFactory> opt = sl.findFirst();
+               if (opt.isEmpty())
+                       throw new IllegalStateException("Cannot find OSGi framework");
+               framework = opt.get().newFramework(config);
+               try {
+                       framework.start();
+                       BundleContext bundleContext = framework.getBundleContext();
+                       start(bundleContext);
+               } catch (BundleException e) {
+                       throw new IllegalStateException("Cannot start OSGi framework", e);
+               }
+       }
+
+       public void start(BundleContext bundleContext) {
+               // logging
+               loggingConfigurationSr = bundleContext.registerService(Consumer.class,
+                               ThinLoggerFinder.getConfigurationConsumer(),
+                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.configuration")));
+               logEntryPublisherSr = bundleContext.registerService(Flow.Publisher.class,
+                               ThinLoggerFinder.getLogEntryPublisher(),
+                               new Hashtable<>(Collections.singletonMap(Constants.SERVICE_PID, "argeo.logging.publisher")));
+
+               osgiBoot = new OsgiBoot(bundleContext);
+               osgiBoot.bootstrap();
+
+       }
+
+       public void update() {
+               Objects.requireNonNull(osgiBoot);
+               osgiBoot.update();
+       }
+
+       public void stop(BundleContext bundleContext) {
+//             if (loggingConfigurationSr != null)
+//                     try {
+//                             loggingConfigurationSr.unregister();
+//                     } catch (Exception e) {
+//                             // silent
+//                     }
+//             if (logEntryPublisherSr != null)
+//                     try {
+//                             logEntryPublisherSr.unregister();
+//                     } catch (Exception e) {
+//                             // silent
+//                     }
+       }
+
+       @Override
+       public void waitForStop(long timeout) throws InterruptedException {
+               if (framework == null)
+                       throw new IllegalStateException("Framework is not initialised");
+               framework.waitForStop(timeout);
+       }
+
+       @Override
+       public void close() throws Exception {
+               stop(framework.getBundleContext());
+               if (framework != null)
+                       framework.stop();
+       }
+
+}
diff --git a/org.argeo.init/src/org/argeo/init/osgi/log4j.properties b/org.argeo.init/src/org/argeo/init/osgi/log4j.properties
new file mode 100644 (file)
index 0000000..1fcf25e
--- /dev/null
@@ -0,0 +1,12 @@
+log4j.rootLogger=WARN, console
+
+log4j.logger.org.argeo=INFO
+
+## Appenders
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
+
+log4j.appender.development=org.apache.log4j.ConsoleAppender
+log4j.appender.development.layout=org.apache.log4j.PatternLayout
+log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
diff --git a/org.argeo.init/src/org/argeo/init/osgi/node.policy b/org.argeo.init/src/org/argeo/init/osgi/node.policy
new file mode 100644 (file)
index 0000000..facb613
--- /dev/null
@@ -0,0 +1,3 @@
+grant {
+  permission java.security.AllPermission;
+};
\ No newline at end of file
diff --git a/org.argeo.init/src/org/argeo/init/osgi/package-info.java b/org.argeo.init/src/org/argeo/init/osgi/package-info.java
new file mode 100644 (file)
index 0000000..993e246
--- /dev/null
@@ -0,0 +1,2 @@
+/** Simple OSGi initialisation. */
+package org.argeo.init.osgi;
\ No newline at end of file
diff --git a/org.argeo.jcr/.classpath b/org.argeo.jcr/.classpath
deleted file mode 100644 (file)
index d499d30..0000000
+++ /dev/null
@@ -1,7 +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.jcr/.project b/org.argeo.jcr/.project
deleted file mode 100644 (file)
index e432547..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.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>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.jcr/META-INF/.gitignore b/org.argeo.jcr/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.jcr/bnd.bnd b/org.argeo.jcr/bnd.bnd
deleted file mode 100644 (file)
index 4df5017..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Provide-Capability:\
-cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true
-
-Import-Package:\
-*
diff --git a/org.argeo.jcr/build.properties b/org.argeo.jcr/build.properties
deleted file mode 100644 (file)
index acb5245..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-source.. = src/,\
-           ext/test/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.jcr/pom.xml b/org.argeo.jcr/pom.xml
deleted file mode 100644 (file)
index 99becb5..0000000
+++ /dev/null
@@ -1,11 +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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.jcr</artifactId>
-       <name>Commons JCR</name>
-</project>
\ No newline at end of file
diff --git a/org.argeo.jcr/src/org/argeo/jcr/Bin.java b/org.argeo.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.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.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.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.jcr/src/org/argeo/jcr/DefaultJcrListener.java
deleted file mode 100644 (file)
index fc68888..0000000
+++ /dev/null
@@ -1,78 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/** To be overridden */
-public class DefaultJcrListener implements EventListener {
-       private final static Log log = LogFactory.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.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java
deleted file mode 100644 (file)
index 72e325d..0000000
+++ /dev/null
@@ -1,975 +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);
-               }
-       }
-
-       /**
-        * 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.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrCallback.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrException.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrMonitor.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrxApi.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrxName.java b/org.argeo.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.jcr/src/org/argeo/jcr/JcrxType.java b/org.argeo.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.jcr/src/org/argeo/jcr/PropertyDiff.java b/org.argeo.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.jcr/src/org/argeo/jcr/SimplePrincipal.java b/org.argeo.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.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
deleted file mode 100644 (file)
index 1e23338..0000000
+++ /dev/null
@@ -1,280 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/** Proxy JCR sessions and attach them to calling threads. */
-@Deprecated
-public abstract class ThreadBoundJcrSessionFactory {
-       private final static Log log = LogFactory.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.jcr/src/org/argeo/jcr/VersionDiff.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java
deleted file mode 100644 (file)
index 3d538e8..0000000
+++ /dev/null
@@ -1,250 +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.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-
-public class JcrFileSystem extends FileSystem {
-       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.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/JcrPath.java b/org.argeo.jcr/src/org/argeo/jcr/fs/JcrPath.java
deleted file mode 100644 (file)
index 1a4d747..0000000
+++ /dev/null
@@ -1,393 +0,0 @@
-package org.argeo.jcr.fs;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystem;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.WatchEvent.Kind;
-import java.nio.file.WatchEvent.Modifier;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-/** A {@link Path} which contains a reference to a JCR {@link Node}. */
-public class JcrPath implements Path {
-       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) {
-               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) {
-               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);
-       }
-
-       @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();
-       }
-
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
-               // TODO Auto-generated method stub
-               return null;
-       }
-
-       @Override
-       public int compareTo(Path other) {
-               return toString().compareTo(other.toString());
-       }
-
-       public Node getNode() throws RepositoryException {
-               if (!isAbsolute())// TODO default dir
-                       throw new JcrFsException("Cannot get a JCR node from a relative path");
-               assert fileStore != null;
-               return fileStore.toNode(path);
-//             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.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/Text.java b/org.argeo.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.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/org.argeo.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java
deleted file mode 100644 (file)
index 6d9d05c..0000000
+++ /dev/null
@@ -1,191 +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.jcr.JcrUtils;
-
-/** A {@link FileStore} implementation based on JCR {@link Workspace}. */
-public class WorkspaceFileStore extends FileStore {
-       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.jcr/src/org/argeo/jcr/fs/package-info.java b/org.argeo.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.jcr/src/org/argeo/jcr/jcrx.cnd b/org.argeo.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.jcr/src/org/argeo/jcr/package-info.java b/org.argeo.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.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/org.argeo.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.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/org.argeo.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.maintenance/.classpath b/org.argeo.maintenance/.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.maintenance/.project b/org.argeo.maintenance/.project
deleted file mode 100644 (file)
index d1c87c7..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.maintenance</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.maintenance/.settings/org.eclipse.jdt.core.prefs b/org.argeo.maintenance/.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.maintenance/META-INF/.gitignore b/org.argeo.maintenance/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.maintenance/bnd.bnd b/org.argeo.maintenance/bnd.bnd
deleted file mode 100644 (file)
index f0c70aa..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Bundle-Activator: org.argeo.maintenance.internal.Activator
diff --git a/org.argeo.maintenance/build.properties b/org.argeo.maintenance/build.properties
deleted file mode 100644 (file)
index ad08d9e..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
-additional.bundles = org.slf4j.log4j12,\
-                     org.slf4j.api
diff --git a/org.argeo.maintenance/pom.xml b/org.argeo.maintenance/pom.xml
deleted file mode 100644 (file)
index b061f87..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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.maintenance</artifactId>
-       <name>Maintenance</name>
-       <packaging>jar</packaging>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.jcr</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.enterprise</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.core</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-               <dependency>
-                       <groupId>org.argeo.commons</groupId>
-                       <artifactId>org.argeo.api</artifactId>
-                       <version>2.1-SNAPSHOT</version>
-               </dependency>
-       </dependencies>
-</project>
\ No newline at end of file
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java b/org.argeo.maintenance/src/org/argeo/maintenance/AbstractMaintenanceService.java
deleted file mode 100644 (file)
index 6003d63..0000000
+++ /dev/null
@@ -1,221 +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 javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeUtils;
-import org.argeo.jcr.Jcr;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.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 Log log = LogFactory.getLog(AbstractMaintenanceService.class);
-
-       private Repository repository;
-//     private UserAdminService userAdminService;
-       private UserAdmin userAdmin;
-       private UserTransaction 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 = NodeUtils.openDataAdminSession(repository, workspaceName);
-               } catch (RuntimeException e1) {
-                       if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) {
-                               Session defaultAdminSession = NodeUtils.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 = NodeUtils.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 UserTransaction getUserTransaction() {
-               return userTransaction;
-       }
-
-       protected UserAdmin getUserAdmin() {
-               return userAdmin;
-       }
-
-       public void setUserAdmin(UserAdmin userAdmin) {
-               this.userAdmin = userAdmin;
-       }
-
-       public void setUserTransaction(UserTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-}
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/SimpleRoleRegistration.java b/org.argeo.maintenance/src/org/argeo/maintenance/SimpleRoleRegistration.java
deleted file mode 100644 (file)
index a30fe97..0000000
+++ /dev/null
@@ -1,87 +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 javax.transaction.UserTransaction;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-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 Log log = LogFactory.getLog(SimpleRoleRegistration.class);
-
-       private String role;
-       private List<String> roles = new ArrayList<String>();
-       private UserAdmin userAdmin;
-       private UserTransaction 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(UserTransaction userTransaction) {
-               this.userTransaction = userTransaction;
-       }
-
-}
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java b/org.argeo.maintenance/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.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java
deleted file mode 100644 (file)
index 60e8f8e..0000000
+++ /dev/null
@@ -1,449 +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.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.JackrabbitValue;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-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 Log log = LogFactory.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 NodeUtils.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, NodeConstants.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.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java
deleted file mode 100644 (file)
index a12bb41..0000000
+++ /dev/null
@@ -1,86 +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.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.api.NodeConstants;
-import org.argeo.api.NodeUtils;
-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 Log log = LogFactory.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.maintenance/src/org/argeo/maintenance/backup/package-info.java b/org.argeo.maintenance/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.maintenance/src/org/argeo/maintenance/internal/Activator.java b/org.argeo.maintenance/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.maintenance/src/org/argeo/maintenance/package-info.java b/org.argeo.maintenance/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.osgi.boot/.classpath b/org.argeo.osgi.boot/.classpath
deleted file mode 100644 (file)
index 190db03..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="src" path="ext/test"/>
-       <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.osgi.boot/.project b/org.argeo.osgi.boot/.project
deleted file mode 100644 (file)
index e145e96..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.osgi.boot</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>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-               <nature>org.eclipse.pde.PluginNature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.osgi.boot/META-INF/.gitignore b/org.argeo.osgi.boot/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
diff --git a/org.argeo.osgi.boot/bnd.bnd b/org.argeo.osgi.boot/bnd.bnd
deleted file mode 100644 (file)
index fd2b18c..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-Main-Class: org.argeo.osgi.boot.Main
-Class-Path: org.eclipse.osgi.jar
-
-Bundle-Activator: org.argeo.osgi.boot.Activator
-Import-Package: org.eclipse.*;resolution:=optional,\
-org.eclipse.osgi.launch.*;resolution:=optional,\
-org.osgi.*;version=0.0.0,\
-*
diff --git a/org.argeo.osgi.boot/build.properties b/org.argeo.osgi.boot/build.properties
deleted file mode 100644 (file)
index d88c54b..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/,\
-           ext/test/
-additional.bundles = org.junit,\
-                     org.hamcrest
diff --git a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/a2/ClasspathSourceTest.java
deleted file mode 100644 (file)
index d087e5a..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.IOException;
-
-import org.argeo.osgi.a2.ClasspathSource;
-import org.argeo.osgi.a2.ProvisioningManager;
-import org.argeo.osgi.boot.equinox.EquinoxUtils;
-import org.junit.Test;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.launch.Framework;
-
-public class ClasspathSourceTest {
-       @Test
-       public void testProvisioning() throws IOException {
-               Framework framework = EquinoxUtils.launch(null);
-               ProvisioningManager provisioningManager = new ProvisioningManager(framework.getBundleContext());
-               ClasspathSource classpathSource = new ClasspathSource();
-               classpathSource.load();
-               provisioningManager.addSource(classpathSource);
-               provisioningManager.install(null);
-               for (Bundle bundle : framework.getBundleContext().getBundles()) {
-                       System.out.println(bundle.getSymbolicName() + ":" + bundle.getVersion());
-               }
-               try {
-                       framework.stop();
-               } catch (BundleException e) {
-                       // silent
-               }
-       }
-}
diff --git a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootNoRuntimeTest.java b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootNoRuntimeTest.java
deleted file mode 100644 (file)
index 6870a26..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.util.List;
-
-import junit.framework.TestCase;
-
-/** Tests which do not require a runtime. */
-@SuppressWarnings("rawtypes")
-public class OsgiBootNoRuntimeTest extends TestCase {
-       public final static String BUNDLES = "src/test/bundles/some;in=*;ex=excluded,"
-                       + "src/test/bundles/others;in=**/org.argeo.*";
-
-       /** Tests that location lists are properly parsed. */
-       // public void testLocations() {
-       // String baseUrl = "file:";
-       // String locations = "/mydir/myfile" + File.pathSeparator
-       // + "/myotherdir/myotherfile";
-       //
-       // OsgiBoot osgiBoot = new OsgiBoot(null);
-       // osgiBoot.setExcludeSvn(true);
-       // List urls = osgiBoot.getLocationsUrls(baseUrl, locations);
-       // assertEquals(2, urls.size());
-       // assertEquals("file:/mydir/myfile", urls.get(0));
-       // assertEquals("file:/myotherdir/myotherfile", urls.get(1));
-       // }
-
-       /** Tests that bundle lists are properly parsed. */
-       public void testBundles() {
-               String baseUrl = "file:";
-               String bundles = BUNDLES;
-               OsgiBoot osgiBoot = new OsgiBoot(null);
-//             osgiBoot.setExcludeSvn(true);
-               List urls = osgiBoot.getBundlesUrls(baseUrl, bundles);
-               for (int i = 0; i < urls.size(); i++)
-                       System.out.println(urls.get(i));
-               assertEquals(3, urls.size());
-
-               List jarUrls = osgiBoot.getBundlesUrls(baseUrl,
-                               "src/test/bundles/jars;in=*.jar");
-               for (int i = 0; i < jarUrls.size(); i++)
-                       System.out.println(jarUrls.get(i));
-               assertEquals(1, jarUrls.size());
-       }
-}
diff --git a/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootRuntimeTest.java b/org.argeo.osgi.boot/ext/test/org/argeo/osgi/boot/OsgiBootRuntimeTest.java
deleted file mode 100644 (file)
index 25404ba..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeMap;
-
-import junit.framework.TestCase;
-
-import org.eclipse.core.runtime.adaptor.EclipseStarter;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-
-/** Starts an Equinox runtime and provision it with OSGi boot. */
-public class OsgiBootRuntimeTest extends TestCase {
-       protected OsgiBoot osgiBoot = null;
-       private boolean osgiRuntimeAlreadyRunning = false;
-
-       public void testInstallAndStart() throws Exception {
-               if (osgiRuntimeAlreadyRunning) {
-                       System.out
-                                       .println("OSGi runtime already running, skipping test...");
-                       return;
-               }
-               osgiBoot.installUrls(osgiBoot.getBundlesUrls(OsgiBoot.DEFAULT_BASE_URL,
-                               OsgiBootNoRuntimeTest.BUNDLES));
-               Map<String, Bundle> map = new TreeMap<String, Bundle>(
-                               osgiBoot.getBundlesBySymbolicName());
-               for (Iterator<String> keys = map.keySet().iterator(); keys.hasNext();) {
-                       String key = keys.next();
-                       Bundle bundle = map.get(key);
-                       System.out.println(key + " : " + bundle.getLocation());
-               }
-               assertEquals(4, map.size());
-               Iterator<String> keys = map.keySet().iterator();
-               assertEquals("org.argeo.osgi.boot.test.bundle1", keys.next());
-               assertEquals("org.argeo.osgi.boot.test.bundle2", keys.next());
-               assertEquals("org.argeo.osgi.boot.test.bundle3", keys.next());
-               assertEquals("org.eclipse.osgi", keys.next());
-
-               // osgiBoot.startBundles("org.argeo.osgi.boot.test.bundle2");
-               long begin = System.currentTimeMillis();
-               while (System.currentTimeMillis() - begin < 10000) {
-                       Map<String, Bundle> mapBundles = osgiBoot
-                                       .getBundlesBySymbolicName();
-                       Bundle bundle = mapBundles.get("org.argeo.osgi.boot.test.bundle2");
-                       if (bundle.getState() == Bundle.ACTIVE) {
-                               System.out.println("Bundle " + bundle + " started.");
-                               return;
-                       }
-               }
-               fail("Bundle not started after timeout limit.");
-       }
-
-       protected BundleContext startRuntime() throws Exception {
-               String[] args = { "-console", "-clean" };
-               BundleContext bundleContext = EclipseStarter.startup(args, null);
-
-//             ServiceLoader<FrameworkFactory> ff = ServiceLoader.load(FrameworkFactory.class);
-//             Map<String,String> config = new HashMap<String,String>();               
-//             Framework fwk = ff.iterator().next().newFramework(config);
-//             fwk.start();
-               return bundleContext;
-       }
-
-       protected void stopRuntime() throws Exception {
-               EclipseStarter.shutdown();
-       }
-
-       public void setUp() throws Exception {
-               osgiRuntimeAlreadyRunning = EclipseStarter.isRunning();
-               if (osgiRuntimeAlreadyRunning)
-                       return;
-               BundleContext bundleContext = startRuntime();
-               osgiBoot = new OsgiBoot(bundleContext);
-       }
-
-       public void tearDown() throws Exception {
-               if (osgiRuntimeAlreadyRunning)
-                       return;
-               osgiBoot = null;
-               stopRuntime();
-       }
-
-}
diff --git a/org.argeo.osgi.boot/pom.xml b/org.argeo.osgi.boot/pom.xml
deleted file mode 100644 (file)
index 1bf9500..0000000
+++ /dev/null
@@ -1,46 +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.1-SNAPSHOT</version>
-               <artifactId>argeo-commons</artifactId>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>org.argeo.osgi.boot</artifactId>
-       <packaging>jar</packaging>
-       <name>Commons OSGi Boot</name>
-       <build>
-               <plugins>
-                       <plugin>
-                               <artifactId>maven-surefire-plugin</artifactId>
-                               <configuration>
-                                       <skipTests>true</skipTests>
-                               </configuration>
-                       </plugin>
-               </plugins>
-       </build>
-       <dependencies>
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp</groupId> -->
-<!--                   <artifactId>argeo-tp</artifactId> -->
-<!--                   <version>${version.argeo-tp}</version> -->
-<!--                   <scope>provided</scope> -->
-<!--           </dependency> -->
-
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp.rap.platform</groupId> -->
-<!--                   <artifactId>org.eclipse.osgi</artifactId> -->
-<!--                   <scope>provided</scope> -->
-<!--           </dependency> -->
-
-               <!-- TEST -->
-<!--           <dependency> -->
-<!--                   <groupId>org.argeo.tp</groupId> -->
-<!--                   <artifactId>junit</artifactId> -->
-<!--                   <scope>test</scope> -->
-<!--           </dependency> -->
-       </dependencies>
-
-
-</project>
\ No newline at end of file
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Branch.java
deleted file mode 100644 (file)
index 070830e..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.util.Collections;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/**
- * A logical linear sequence of versions of a given {@link A2Component}. This is
- * typically a combination of major and minor version, indicating backward
- * compatibility.
- */
-public class A2Branch implements Comparable<A2Branch> {
-       private final A2Component component;
-       private final String id;
-
-       final SortedMap<Version, A2Module> modules = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public A2Branch(A2Component component, String id) {
-               this.component = component;
-               this.id = id;
-               component.branches.put(id, this);
-       }
-
-       A2Module getOrAddModule(Version version, Object locator) {
-               if (modules.containsKey(version)) {
-                       A2Module res = modules.get(version);
-                       if (OsgiBootUtils.isDebug() && !res.getLocator().equals(locator)) {
-                               OsgiBootUtils.debug("Inconsistent locator " + locator + " (registered: " + res.getLocator() + ")");
-                       }
-                       return res;
-               } else
-                       return new A2Module(this, version, locator);
-       }
-
-       A2Module last() {
-               return modules.get(modules.lastKey());
-       }
-
-       A2Module first() {
-               return modules.get(modules.firstKey());
-       }
-
-       A2Component getComponent() {
-               return component;
-       }
-
-       String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Branch o) {
-               return id.compareTo(id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Branch) {
-                       A2Branch o = (A2Branch) obj;
-                       return component.equals(o.component) && id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return getCoordinates();
-       }
-
-       public String getCoordinates() {
-               return component + ":" + id;
-       }
-
-       static String versionToBranchId(Version version) {
-               return version.getMajor() + "." + version.getMinor();
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Component.java
deleted file mode 100644 (file)
index 0b5d13c..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.util.Collections;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.osgi.framework.Version;
-
-/**
- * The logical name of a software package. In OSGi's case this is
- * <code>Bundle-SymbolicName</code>. This is the equivalent of Maven's artifact
- * id.
- */
-public class A2Component implements Comparable<A2Component> {
-       private final A2Contribution contribution;
-       private final String id;
-
-       final SortedMap<String, A2Branch> branches = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public A2Component(A2Contribution contribution, String id) {
-               this.contribution = contribution;
-               this.id = id;
-               contribution.components.put(id, this);
-       }
-
-       A2Branch getOrAddBranch(String branchId) {
-               if (branches.containsKey(branchId))
-                       return branches.get(branchId);
-               else
-                       return new A2Branch(this, branchId);
-       }
-
-       A2Module getOrAddModule(Version version, Object locator) {
-               A2Branch branch = getOrAddBranch(A2Branch.versionToBranchId(version));
-               A2Module module = branch.getOrAddModule(version, locator);
-               return module;
-       }
-
-       A2Branch last() {
-               return branches.get(branches.lastKey());
-       }
-
-       A2Contribution getContribution() {
-               return contribution;
-       }
-
-       String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Component o) {
-               return id.compareTo(o.id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Component) {
-                       A2Component o = (A2Component) obj;
-                       return contribution.equals(o.contribution) && id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return contribution.getId() + ":" + id;
-       }
-
-       void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               A2Branch lastBranch = last();
-               SortedMap<String, A2Branch> displayMap = new TreeMap<>(Collections.reverseOrder());
-               displayMap.putAll(branches);
-               for (String branchId : displayMap.keySet()) {
-                       A2Branch branch = displayMap.get(branchId);
-                       if (!lastBranch.equals(branch)) {
-                               buf.append('\n');
-                               buf.append(prefix);
-                       } else {
-                               buf.append(" -");
-                       }
-                       buf.append(prefix);
-                       buf.append(branchId);
-                       A2Module first = branch.first();
-                       A2Module last = branch.last();
-                       buf.append(" (").append(last.getVersion());
-                       if (!first.equals(last))
-                               buf.append(" ... ").append(first.getVersion());
-                       buf.append(')');
-               }
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Contribution.java
deleted file mode 100644 (file)
index 35d5f9b..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * A category grouping a set of {@link A2Component}, typically based on the
- * provider of these components. This is the equivalent of Maven's group Id.
- */
-public class A2Contribution implements Comparable<A2Contribution> {
-       final static String BOOT = "boot";
-       final static String RUNTIME = "runtime";
-       final static String CLASSPATH = "classpath";
-
-       private final ProvisioningSource source;
-       private final String id;
-
-       final Map<String, A2Component> components = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       /**
-        * The contribution must be added to the source. Rather use
-        * {@link AbstractProvisioningSource#getOrAddContribution(String)} than this
-        * contructor directly.
-        */
-       public A2Contribution(ProvisioningSource context, String id) {
-               this.source = context;
-               this.id = id;
-//             if (context != null)
-//                     context.contributions.put(id, this);
-       }
-
-       A2Component getOrAddComponent(String componentId) {
-               if (components.containsKey(componentId))
-                       return components.get(componentId);
-               else
-                       return new A2Component(this, componentId);
-       }
-
-       public ProvisioningSource getSource() {
-               return source;
-       }
-
-       public String getId() {
-               return id;
-       }
-
-       @Override
-       public int compareTo(A2Contribution o) {
-               return id.compareTo(o.id);
-       }
-
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Contribution) {
-                       A2Contribution o = (A2Contribution) obj;
-                       return id.equals(o.id);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return id;
-       }
-
-       void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               for (String componentId : components.keySet()) {
-                       buf.append(prefix);
-                       buf.append(componentId);
-                       A2Component component = components.get(componentId);
-                       component.asTree(prefix, buf);
-                       buf.append('\n');
-               }
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Exception.java
deleted file mode 100644 (file)
index 83c743a..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.argeo.osgi.a2;
-
-/** Unchecked A2 provisioning exception. */
-public class A2Exception extends RuntimeException {
-       private static final long serialVersionUID = 1927603558545397360L;
-
-       public A2Exception(String message, Throwable e) {
-               super(message, e);
-       }
-
-       public A2Exception(String message) {
-               super(message);
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Module.java
deleted file mode 100644 (file)
index 77f81d1..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.argeo.osgi.a2;
-
-import org.osgi.framework.Version;
-
-/**
- * An identified software package. In OSGi's case this is the combination of
- * <code>Bundle-SymbolicName</code> and <code>Bundle-version</code>. This is the
- * equivalent of the full coordinates of a Maven artifact version.
- */
-class A2Module implements Comparable<A2Module> {
-       private final A2Branch branch;
-       private final Version version;
-       private final Object locator;
-
-       public A2Module(A2Branch branch, Version version, Object locator) {
-               this.branch = branch;
-               this.version = version;
-               this.locator = locator;
-               branch.modules.put(version, this);
-       }
-
-       A2Branch getBranch() {
-               return branch;
-       }
-
-       Version getVersion() {
-               return version;
-       }
-
-       Object getLocator() {
-               return locator;
-       }
-
-       @Override
-       public int compareTo(A2Module o) {
-               return version.compareTo(o.version);
-       }
-
-       @Override
-       public int hashCode() {
-               return version.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof A2Module) {
-                       A2Module o = (A2Module) obj;
-                       return branch.equals(o.branch) && version.equals(o.version);
-               } else
-                       return false;
-       }
-
-       @Override
-       public String toString() {
-               return getCoordinates();
-       }
-
-       public String getCoordinates() {
-               return branch.getComponent() + ":" + version;
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/A2Source.java
deleted file mode 100644 (file)
index 2c88a38..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.argeo.osgi.a2;
-
-/** A provisioning source in A2 format. */
-public interface A2Source extends ProvisioningSource {
-       final static String SCHEME_A2 = "a2";
-       final static String DEFAULT_A2_URI = SCHEME_A2 + ":///";
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/AbstractProvisioningSource.java
deleted file mode 100644 (file)
index 32c175d..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Collections;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.jar.JarInputStream;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-import java.util.zip.ZipEntry;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-
-/** Where components are retrieved from. */
-public abstract class AbstractProvisioningSource implements ProvisioningSource {
-       protected final Map<String, A2Contribution> contributions = Collections.synchronizedSortedMap(new TreeMap<>());
-
-       public Iterable<A2Contribution> listContributions(Object filter) {
-               return contributions.values();
-       }
-
-       @Override
-       public Bundle install(BundleContext bc, A2Module module) {
-               try {
-                       Path tempJar = null;
-                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
-                               tempJar = toTempJar((Path) module.getLocator());
-                       Bundle bundle;
-                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
-                               bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
-                       }
-                       if (tempJar != null)
-                               Files.deleteIfExists(tempJar);
-                       return bundle;
-               } catch (BundleException | IOException e) {
-                       throw new A2Exception("Cannot install module " + module, e);
-               }
-       }
-
-       @Override
-       public void update(Bundle bundle, A2Module module) {
-               try {
-                       Path tempJar = null;
-                       if (module.getLocator() instanceof Path && Files.isDirectory((Path) module.getLocator()))
-                               tempJar = toTempJar((Path) module.getLocator());
-                       try (InputStream in = newInputStream(tempJar != null ? tempJar : module.getLocator())) {
-                               bundle.update(in);
-                       }
-                       if (tempJar != null)
-                               Files.deleteIfExists(tempJar);
-               } catch (BundleException | IOException e) {
-                       throw new A2Exception("Cannot update module " + module, e);
-               }
-       }
-
-       @Override
-       public A2Branch findBranch(String componentId, Version version) {
-               A2Component component = findComponent(componentId);
-               if (component == null)
-                       return null;
-               String branchId = version.getMajor() + "." + version.getMinor();
-               if (!component.branches.containsKey(branchId))
-                       return null;
-               return component.branches.get(branchId);
-       }
-
-       protected A2Contribution getOrAddContribution(String contributionId) {
-               if (contributions.containsKey(contributionId))
-                       return contributions.get(contributionId);
-               else {
-                       A2Contribution contribution = new A2Contribution(this, contributionId);
-                       contributions.put(contributionId, contribution);
-                       return contribution;
-               }
-       }
-
-       protected void asTree(String prefix, StringBuffer buf) {
-               if (prefix == null)
-                       prefix = "";
-               for (String contributionId : contributions.keySet()) {
-                       buf.append(prefix);
-                       buf.append(contributionId);
-                       buf.append('\n');
-                       A2Contribution contribution = contributions.get(contributionId);
-                       contribution.asTree(prefix + " ", buf);
-               }
-       }
-
-       protected void asTree() {
-               StringBuffer buf = new StringBuffer();
-               asTree("", buf);
-               System.out.println(buf);
-       }
-
-       protected A2Component findComponent(String componentId) {
-               SortedMap<A2Contribution, A2Component> res = new TreeMap<>();
-               for (A2Contribution contribution : contributions.values()) {
-                       components: for (String componentIdKey : contribution.components.keySet()) {
-                               if (componentId.equals(componentIdKey)) {
-                                       res.put(contribution, contribution.components.get(componentIdKey));
-                                       break components;
-                               }
-                       }
-               }
-               if (res.size() == 0)
-                       return null;
-               // TODO explicit contribution priorities
-               return res.get(res.lastKey());
-
-       }
-
-       protected String readVersionFromModule(Path modulePath) {
-               Manifest manifest;
-               if (Files.isDirectory(modulePath)) {
-                       manifest = findManifest(modulePath);
-               } else {
-                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
-                               manifest = in.getManifest();
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
-                       }
-               }
-               String versionStr = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-               return versionStr;
-       }
-
-       protected String readSymbolicNameFromModule(Path modulePath) {
-               Manifest manifest;
-               if (Files.isDirectory(modulePath)) {
-                       manifest = findManifest(modulePath);
-               } else {
-                       try (JarInputStream in = new JarInputStream(newInputStream(modulePath))) {
-                               manifest = in.getManifest();
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + modulePath, e);
-                       }
-               }
-               String symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-               int semiColIndex = symbolicName.indexOf(';');
-               if (semiColIndex >= 0)
-                       symbolicName = symbolicName.substring(0, semiColIndex);
-               return symbolicName;
-       }
-
-       private static Manifest findManifest(Path currentPath) {
-               Path metaInfPath = currentPath.resolve("META-INF");
-               if (Files.exists(metaInfPath) && Files.isDirectory(metaInfPath)) {
-                       Path manifestPath = metaInfPath.resolve("MANIFEST.MF");
-                       try {
-                               try (InputStream in = Files.newInputStream(manifestPath)) {
-                                       Manifest manifest = new Manifest(in);
-                                       return manifest;
-                               }
-                       } catch (IOException e) {
-                               throw new A2Exception("Cannot read manifest from " + manifestPath, e);
-                       }
-               } else {
-                       Path parentPath = currentPath.getParent();
-                       if (parentPath == null)
-                               throw new A2Exception("MANIFEST.MF file not found.");
-                       return findManifest(currentPath.getParent());
-               }
-       }
-
-       private static Path toTempJar(Path dir) {
-               try {
-                       Manifest manifest = findManifest(dir);
-                       Path jarPath = Files.createTempFile("a2Source", ".jar");
-                       try (JarOutputStream zos = new JarOutputStream(new FileOutputStream(jarPath.toFile()), manifest)) {
-                               Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
-                                       public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                                               Path relPath = dir.relativize(file);
-                                               // skip MANIFEST from folder
-                                               if (relPath.toString().contentEquals("META-INF/MANIFEST.MF"))
-                                                       return FileVisitResult.CONTINUE;
-                                               zos.putNextEntry(new ZipEntry(relPath.toString()));
-                                               Files.copy(file, zos);
-                                               zos.closeEntry();
-                                               return FileVisitResult.CONTINUE;
-                                       }
-                               });
-                       }
-                       return jarPath;
-               } catch (IOException e) {
-                       throw new A2Exception("Cannot install OSGi bundle from " + dir, e);
-               }
-
-       }
-
-       private InputStream newInputStream(Object locator) throws IOException {
-               if (locator instanceof Path) {
-                       return Files.newInputStream((Path) locator);
-               } else if (locator instanceof URL) {
-                       return ((URL) locator).openStream();
-               } else {
-                       throw new IllegalArgumentException("Unsupported module locator type " + locator.getClass());
-               }
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ClasspathSource.java
deleted file mode 100644 (file)
index ea34666..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.List;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/**
- * A provisioning source based on the linear classpath with which the JCM has
- * been started.
- */
-public class ClasspathSource extends AbstractProvisioningSource {
-       void load() throws IOException {
-               A2Contribution classpathContribution = getOrAddContribution( A2Contribution.CLASSPATH);
-               List<String> classpath = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
-               parts: for (String part : classpath) {
-                       Path file = Paths.get(part);
-                       Version version;
-                       try {
-                               version = new Version(readVersionFromModule(file));
-                       } catch (Exception e) {
-                               // ignore non OSGi
-                               continue parts;
-                       }
-                       String moduleName = readSymbolicNameFromModule(file);
-                       A2Component component = classpathContribution.getOrAddComponent(moduleName);
-                       A2Module module = component.getOrAddModule(version, file);
-                       if (OsgiBootUtils.isDebug())
-                               OsgiBootUtils.debug("Registered " + module);
-               }
-
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsA2Source.java
deleted file mode 100644 (file)
index eea59de..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.IOException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/** A file system {@link AbstractProvisioningSource} in A2 format. */
-public class FsA2Source extends AbstractProvisioningSource implements A2Source {
-       private final Path base;
-
-       public FsA2Source(Path base) {
-               super();
-               this.base = base;
-       }
-
-       void load() throws IOException {
-               DirectoryStream<Path> contributionPaths = Files.newDirectoryStream(base);
-               SortedSet<A2Contribution> contributions = new TreeSet<>();
-               contributions: for (Path contributionPath : contributionPaths) {
-                       if (Files.isDirectory(contributionPath)) {
-                               String contributionId = contributionPath.getFileName().toString();
-                               if (A2Contribution.BOOT.equals(contributionId))// skip boot
-                                       continue contributions;
-                               A2Contribution contribution = getOrAddContribution(contributionId);
-                               contributions.add(contribution);
-                       }
-               }
-
-               for (A2Contribution contribution : contributions) {
-                       DirectoryStream<Path> modulePaths = Files.newDirectoryStream(base.resolve(contribution.getId()));
-                       modules: for (Path modulePath : modulePaths) {
-                               if (!Files.isDirectory(modulePath)) {
-                                       // OsgiBootUtils.debug("Registering " + modulePath);
-                                       String moduleFileName = modulePath.getFileName().toString();
-                                       int lastDot = moduleFileName.lastIndexOf('.');
-                                       String ext = moduleFileName.substring(lastDot + 1);
-                                       if (!"jar".equals(ext))
-                                               continue modules;
-                                       String moduleName = moduleFileName.substring(0, lastDot);
-                                       if (moduleName.endsWith("-SNAPSHOT"))
-                                               moduleName = moduleName.substring(0, moduleName.length() - "-SNAPSHOT".length());
-                                       int lastDash = moduleName.lastIndexOf('-');
-                                       String versionStr = moduleName.substring(lastDash + 1);
-                                       String componentName = moduleName.substring(0, lastDash);
-                                       // if(versionStr.endsWith("-SNAPSHOT")) {
-                                       // versionStr = readVersionFromModule(modulePath);
-                                       // }
-                                       Version version;
-                                       try {
-                                               version = new Version(versionStr);
-                                       } catch (Exception e) {
-                                               versionStr = readVersionFromModule(modulePath);
-                                               if (versionStr != null) {
-                                                       version = new Version(versionStr);
-                                               } else {
-                                                       OsgiBootUtils.debug("Ignore " + modulePath + " (" + e.getMessage() + ")");
-                                                       continue modules;
-                                               }
-                                       }
-                                       A2Component component = contribution.getOrAddComponent(componentName);
-                                       A2Module module = component.getOrAddModule(version, modulePath);
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.debug("Registered " + module);
-                               }
-                       }
-               }
-
-       }
-
-       public static void main(String[] args) {
-               try {
-                       FsA2Source context = new FsA2Source(Paths.get(
-                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.77-SNAPSHOT/share/osgi"));
-                       context.load();
-                       context.asTree();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/FsM2Source.java
deleted file mode 100644 (file)
index a4c3ed5..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.osgi.framework.Version;
-
-/** A file system {@link AbstractProvisioningSource} in Maven 2 format. */
-public class FsM2Source extends AbstractProvisioningSource {
-       private final Path base;
-
-       public FsM2Source(Path base) {
-               super();
-               this.base = base;
-       }
-
-       void load() throws IOException {
-               Files.walkFileTree(base, new ArtifactFileVisitor());
-       }
-
-       class ArtifactFileVisitor extends SimpleFileVisitor<Path> {
-
-               @Override
-               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                       // OsgiBootUtils.debug("Processing " + file);
-                       if (file.toString().endsWith(".jar")) {
-                               Version version;
-                               try {
-                                       version = new Version(readVersionFromModule(file));
-                               } catch (Exception e) {
-                                       // ignore non OSGi
-                                       return FileVisitResult.CONTINUE;
-                               }
-                               String moduleName = readSymbolicNameFromModule(file);
-                               Path groupPath = file.getParent().getParent().getParent();
-                               Path relGroupPath = base.relativize(groupPath);
-                               String contributionName = relGroupPath.toString().replace(File.separatorChar, '.');
-                               A2Contribution contribution = getOrAddContribution(contributionName);
-                               A2Component component = contribution.getOrAddComponent(moduleName);
-                               A2Module module = component.getOrAddModule(version, file);
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug("Registered " + module);
-                       }
-                       return super.visitFile(file, attrs);
-               }
-
-       }
-
-       public static void main(String[] args) {
-               try {
-                       FsM2Source context = new FsM2Source(Paths.get("/home/mbaudier/.m2/repository"));
-                       context.load();
-                       context.asTree();
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/OsgiContext.java
deleted file mode 100644 (file)
index 8630dc2..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.argeo.osgi.a2;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
-
-/** A running OSGi bundle context seen as a {@link AbstractProvisioningSource}. */
-class OsgiContext extends AbstractProvisioningSource {
-       private final BundleContext bc;
-
-       public OsgiContext(BundleContext bc) {
-               super();
-               this.bc = bc;
-       }
-
-       public OsgiContext() {
-               Bundle bundle = FrameworkUtil.getBundle(OsgiContext.class);
-               if (bundle == null)
-                       throw new IllegalArgumentException(
-                                       "OSGi Boot bundle must be started or a bundle context must be specified");
-               this.bc = bundle.getBundleContext();
-       }
-
-       void load() {
-               A2Contribution runtimeContribution = getOrAddContribution( A2Contribution.RUNTIME);
-               for (Bundle bundle : bc.getBundles()) {
-                       // OsgiBootUtils.debug(bundle.getDataFile("/"));
-                       String componentId = bundle.getSymbolicName();
-                       Version version = bundle.getVersion();
-                       A2Component component = runtimeContribution.getOrAddComponent(componentId);
-                       A2Module module = component.getOrAddModule(version, bundle);
-                       if (OsgiBootUtils.isDebug())
-                               OsgiBootUtils.debug("Registered " + module + " (location id: " + bundle.getLocation() + ")");
-               }
-
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningManager.java
deleted file mode 100644 (file)
index d8246f1..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-package org.argeo.osgi.a2;
-
-import java.io.File;
-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.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.eclipse.osgi.launch.EquinoxFactory;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.wiring.FrameworkWiring;
-
-/** Loads provisioning sources into an OSGi context. */
-public class ProvisioningManager {
-       BundleContext bc;
-       OsgiContext osgiContext;
-       List<ProvisioningSource> sources = Collections.synchronizedList(new ArrayList<>());
-
-       public ProvisioningManager(BundleContext bc) {
-               this.bc = bc;
-               osgiContext = new OsgiContext(bc);
-               osgiContext.load();
-       }
-
-       protected void addSource(ProvisioningSource source) {
-               sources.add(source);
-       }
-
-       void installWholeSource(ProvisioningSource source) {
-               Set<Bundle> updatedBundles = new HashSet<>();
-               for (A2Contribution contribution : source.listContributions(null)) {
-                       for (A2Component component : contribution.components.values()) {
-                               A2Module module = component.last().last();
-                               Bundle bundle = installOrUpdate(module);
-                               if (bundle != null)
-                                       updatedBundles.add(bundle);
-                       }
-               }
-               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
-               frameworkWiring.refreshBundles(updatedBundles);
-       }
-
-       public void registerSource(String uri) {
-               try {
-                       URI u = new URI(uri);
-                       if (A2Source.SCHEME_A2.equals(u.getScheme())) {
-                               if (u.getHost() == null || "".equals(u.getHost())) {
-                                       String baseStr = u.getPath();
-                                       if (File.separatorChar == '\\') {// MS Windows
-                                               baseStr = baseStr.substring(1).replace('/', File.separatorChar);
-                                       }
-                                       Path base = Paths.get(baseStr);
-                                       if (Files.exists(base)) {
-                                               FsA2Source source = new FsA2Source(base);
-                                               source.load();
-                                               addSource(source);
-                                               OsgiBootUtils.info("Registered " + uri + " as source");
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new A2Exception("Cannot add source " + uri, e);
-               }
-       }
-
-       public boolean registerDefaultSource() {
-               String frameworkLocation = bc.getProperty("osgi.framework");
-               try {
-                       URI frameworkLocationUri = new URI(frameworkLocation);
-                       if ("file".equals(frameworkLocationUri.getScheme())) {
-                               Path frameworkPath = Paths.get(frameworkLocationUri);
-                               if (frameworkPath.getParent().getFileName().toString().equals(A2Contribution.BOOT)) {
-                                       Path base = frameworkPath.getParent().getParent();
-                                       String baseStr = base.toString();
-                                       if (File.separatorChar == '\\')// MS Windows
-                                               baseStr = '/' + baseStr.replace(File.separatorChar, '/');
-                                       URI baseUri = new URI(A2Source.SCHEME_A2, null, null, 0, baseStr, null, null);
-                                       registerSource(baseUri.toString());
-                                       OsgiBootUtils.debug("Default source from framework location " + frameworkLocation);
-                                       return true;
-                               }
-                       }
-               } catch (Exception e) {
-                       OsgiBootUtils.error("Cannot register default source based on framework location " + frameworkLocation, e);
-               }
-               return false;
-       }
-
-       public void install(String spec) {
-               if (spec == null) {
-                       for (ProvisioningSource source : sources) {
-                               installWholeSource(source);
-                       }
-               }
-       }
-
-       /** @return the new/updated bundle, or null if nothing was done. */
-       protected Bundle installOrUpdate(A2Module module) {
-               try {
-                       ProvisioningSource moduleSource = module.getBranch().getComponent().getContribution().getSource();
-                       Version moduleVersion = module.getVersion();
-                       A2Branch osgiBranch = osgiContext.findBranch(module.getBranch().getComponent().getId(), moduleVersion);
-                       if (osgiBranch == null) {
-//                             Bundle bundle = bc.installBundle(module.getBranch().getCoordinates(),
-//                                             moduleSource.newInputStream(module.getLocator()));
-                               Bundle bundle = moduleSource.install(bc, module);
-                               if (OsgiBootUtils.isDebug())
-                                       OsgiBootUtils.debug("Installed bundle " + bundle.getLocation() + " with version " + moduleVersion);
-                               return bundle;
-                       } else {
-                               A2Module lastOsgiModule = osgiBranch.last();
-                               int compare = moduleVersion.compareTo(lastOsgiModule.getVersion());
-                               if (compare > 0) {// update
-                                       Bundle bundle = (Bundle) lastOsgiModule.getLocator();
-//                                     bundle.update(moduleSource.newInputStream(module.getLocator()));
-                                       moduleSource.update(bundle, module);
-                                       OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
-                                       return bundle;
-                               }
-                       }
-               } catch (Exception e) {
-                       OsgiBootUtils.error("Could not install module " + module + ": " + e.getMessage(), e);
-               }
-               return null;
-       }
-
-       public Collection<Bundle> update() {
-               boolean fragmentsUpdated = false;
-               Set<Bundle> updatedBundles = new HashSet<>();
-               bundles: for (Bundle bundle : bc.getBundles()) {
-                       for (ProvisioningSource source : sources) {
-                               String componentId = bundle.getSymbolicName();
-                               Version version = bundle.getVersion();
-                               A2Branch branch = source.findBranch(componentId, version);
-                               if (branch == null)
-                                       continue bundles;
-                               A2Module module = branch.last();
-                               Version moduleVersion = module.getVersion();
-                               int compare = moduleVersion.compareTo(version);
-                               if (compare > 0) {// update
-                                       try {
-                                               source.update(bundle, module);
-//                                             bundle.update(in);
-                                               String fragmentHost = bundle.getHeaders().get(Constants.FRAGMENT_HOST);
-                                               if (fragmentHost != null)
-                                                       fragmentsUpdated = true;
-                                               OsgiBootUtils.info("Updated bundle " + bundle.getLocation() + " to version " + moduleVersion);
-                                               updatedBundles.add(bundle);
-                                       } catch (Exception e) {
-                                               OsgiBootUtils.error("Cannot update with module " + module, e);
-                                       }
-                               }
-                       }
-               }
-               FrameworkWiring frameworkWiring = bc.getBundle(0).adapt(FrameworkWiring.class);
-               if (fragmentsUpdated)// refresh all
-                       frameworkWiring.refreshBundles(null);
-               else
-                       frameworkWiring.refreshBundles(updatedBundles);
-               return updatedBundles;
-       }
-
-       public static void main(String[] args) {
-               Map<String, String> configuration = new HashMap<>();
-               configuration.put("osgi.console", "2323");
-               Framework framework = OsgiBootUtils.launch(new EquinoxFactory(), configuration);
-               try {
-                       ProvisioningManager pm = new ProvisioningManager(framework.getBundleContext());
-                       FsA2Source context = new FsA2Source(Paths.get(
-                                       "/home/mbaudier/dev/git/apache2/argeo-commons/dist/argeo-node/target/argeo-node-2.1.74-SNAPSHOT/argeo-node/share/osgi"));
-                       context.load();
-                       if (framework.getBundleContext().getBundles().length == 1) {// initial
-                               pm.install(null);
-                       } else {
-                               pm.update();
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-               } finally {
-                       try {
-                               // framework.stop();
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                       }
-               }
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/ProvisioningSource.java
deleted file mode 100644 (file)
index 7d6fadf..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.argeo.osgi.a2;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Version;
-
-/** Where components are retrieved from. */
-public interface ProvisioningSource {
-       /** List all contributions of this source. */
-       Iterable<A2Contribution> listContributions(Object filter);
-
-       /** Install a module in the OSGi runtime. */
-       Bundle install(BundleContext bc, A2Module module);
-
-       /** Update a module in the OSGi runtime. */
-       void update(Bundle bundle, A2Module module);
-
-       /** Finds the {@link A2Branch} related to this component and version. */
-       A2Branch findBranch(String componentId, Version version);
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/a2/package-info.java b/org.argeo.osgi.boot/src/org/argeo/osgi/a2/package-info.java
deleted file mode 100644 (file)
index b381410..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** A2 OSGi repository format. */
-package org.argeo.osgi.a2;
\ No newline at end of file
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Activator.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Activator.java
deleted file mode 100644 (file)
index 516ab90..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.argeo.osgi.boot;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-/**
- * An OSGi configurator. See
- * <a href="http://wiki.eclipse.org/Configurator">http:
- * //wiki.eclipse.org/Configurator</a>
- */
-public class Activator implements BundleActivator {
-       private Long checkpoint = null;
-
-       public void start(final BundleContext bundleContext) throws Exception {
-               // admin thread
-               Thread adminThread = new AdminThread(bundleContext);
-               adminThread.start();
-
-               // bootstrap
-               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-               if (checkpoint == null) {
-                       osgiBoot.bootstrap();
-                       checkpoint = System.currentTimeMillis();
-               } else {
-                       osgiBoot.update();
-                       checkpoint = System.currentTimeMillis();
-               }
-       }
-
-       public void stop(BundleContext context) throws Exception {
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/AdminThread.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/AdminThread.java
deleted file mode 100644 (file)
index b0144a9..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.io.File;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.launch.Framework;
-
-/** Monitors the runtime and can shut it down. */
-public class AdminThread extends Thread {
-       public final static String PROP_ARGEO_OSGI_SHUTDOWN_FILE = "argeo.osgi.shutdownFile";
-       private File shutdownFile;
-       private final BundleContext bundleContext;
-
-       public AdminThread(BundleContext bundleContext) {
-               super("OSGi Boot Admin");
-               this.bundleContext = bundleContext;
-               if (System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE) != null) {
-                       shutdownFile = new File(
-                                       System.getProperty(PROP_ARGEO_OSGI_SHUTDOWN_FILE));
-                       if (!shutdownFile.exists()) {
-                               shutdownFile = null;
-                               OsgiBootUtils.warn("Shutdown file " + shutdownFile
-                                               + " not found, feature deactivated");
-                       }
-               }
-       }
-
-       public void run() {
-               if (shutdownFile != null) {
-                       // wait for file to be removed
-                       while (shutdownFile.exists()) {
-                               try {
-                                       Thread.sleep(1000);
-                               } catch (InterruptedException e) {
-                                       e.printStackTrace();
-                               }
-                       }
-
-                       Framework framework = (Framework) bundleContext.getBundle(0);
-                       try {
-                               // shutdown framework
-                               framework.stop();
-                               // wait 10 mins for shutdown
-                               framework.waitForStop(10 * 60 * 1000);
-                               // close VM
-                               System.exit(0);
-                       } catch (Exception e) {
-                               e.printStackTrace();
-                               System.exit(1);
-                       }
-               }
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/BundlesSet.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/BundlesSet.java
deleted file mode 100644 (file)
index 4a342fd..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/** Intermediary structure used by path matching */
-class BundlesSet {
-       private String baseUrl = "reference:file";// not used yet
-       private final String dir;
-       private List<String> includes = new ArrayList<String>();
-       private List<String> excludes = new ArrayList<String>();
-
-       public BundlesSet(String def) {
-               StringTokenizer st = new StringTokenizer(def, ";");
-
-               if (!st.hasMoreTokens())
-                       throw new RuntimeException("Base dir not defined.");
-               try {
-                       String dirPath = st.nextToken();
-
-                       if (dirPath.startsWith("file:"))
-                               dirPath = dirPath.substring("file:".length());
-
-                       dir = new File(dirPath.replace('/', File.separatorChar)).getCanonicalPath();
-                       if (OsgiBootUtils.debug)
-                               OsgiBootUtils.debug("Base dir: " + dir);
-               } catch (IOException e) {
-                       throw new RuntimeException("Cannot convert to absolute path", e);
-               }
-
-               while (st.hasMoreTokens()) {
-                       String tk = st.nextToken();
-                       StringTokenizer stEq = new StringTokenizer(tk, "=");
-                       String type = stEq.nextToken();
-                       String pattern = stEq.nextToken();
-                       if ("in".equals(type) || "include".equals(type)) {
-                               includes.add(pattern);
-                       } else if ("ex".equals(type) || "exclude".equals(type)) {
-                               excludes.add(pattern);
-                       } else if ("baseUrl".equals(type)) {
-                               baseUrl = pattern;
-                       } else {
-                               System.err.println("Unkown bundles pattern type " + type);
-                       }
-               }
-
-               // if (excludeSvn && !excludes.contains(EXCLUDES_SVN_PATTERN)) {
-               // excludes.add(EXCLUDES_SVN_PATTERN);
-               // }
-       }
-
-       public String getDir() {
-               return dir;
-       }
-
-       public List<String> getIncludes() {
-               return includes;
-       }
-
-       public List<String> getExcludes() {
-               return excludes;
-       }
-
-       public String getBaseUrl() {
-               return baseUrl;
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/DistributionBundle.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/DistributionBundle.java
deleted file mode 100644 (file)
index a336b64..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.StringTokenizer;
-import java.util.TreeMap;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.jar.Manifest;
-
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-
-/**
- * A distribution bundle is a bundle within a maven-like distribution
- * groupId:Bundle-SymbolicName:Bundle-Version which references others OSGi
- * bundle. It is not required to be OSGi complete also it will generally be
- * expected that it is. The root of the repository is computed based on the file
- * name of the URL and of the content of the index.
- */
-public class DistributionBundle {
-       private final static String INDEX_FILE_NAME = "modularDistribution.csv";
-
-       private final String url;
-
-       private Manifest manifest;
-       private String symbolicName;
-       private String version;
-
-       /** can be null */
-       private String baseUrl;
-       /** can be null */
-       private String relativeUrl;
-       private String localCache;
-
-       private List<OsgiArtifact> artifacts;
-
-       private String separator = ",";
-
-       public DistributionBundle(String url) {
-               this.url = url;
-       }
-
-       public DistributionBundle(String baseUrl, String relativeUrl, String localCache) {
-               if (baseUrl == null || !baseUrl.endsWith("/"))
-                       throw new OsgiBootException("Base url " + baseUrl + " badly formatted");
-               if (relativeUrl.startsWith("http") || relativeUrl.startsWith("file:"))
-                       throw new OsgiBootException("Relative URL " + relativeUrl + " badly formatted");
-               this.url = constructUrl(baseUrl, relativeUrl);
-               this.baseUrl = baseUrl;
-               this.relativeUrl = relativeUrl;
-               this.localCache = localCache;
-       }
-
-       protected String constructUrl(String baseUrl, String relativeUrl) {
-               try {
-                       if (relativeUrl.indexOf('*') >= 0) {
-                               if (!baseUrl.startsWith("file:"))
-                                       throw new IllegalArgumentException(
-                                                       "Wildcard support only for file:, badly formatted " + baseUrl + " and " + relativeUrl);
-                               Path basePath = Paths.get(new URI(baseUrl));
-                               // Path basePath = Paths.get(new URI(baseUrl));
-                               // int li = relativeUrl.lastIndexOf('/');
-                               // String relativeDir = relativeUrl.substring(0, li);
-                               // String relativeFile = relativeUrl.substring(li,
-                               // relativeUrl.length());
-                               String pattern = "glob:" + basePath + '/' + relativeUrl;
-                               PathMatcher pm = basePath.getFileSystem().getPathMatcher(pattern);
-                               SortedMap<Version, Path> res = new TreeMap<>();
-                               checkDir(basePath, pm, res);
-                               if (res.size() == 0)
-                                       throw new OsgiBootException("No file matching " + relativeUrl + " found in " + baseUrl);
-                               return res.get(res.firstKey()).toUri().toString();
-                       } else {
-                               return baseUrl + relativeUrl;
-                       }
-               } catch (Exception e) {
-                       throw new OsgiBootException("Cannot build URL from " + baseUrl + " and " + relativeUrl, e);
-               }
-       }
-
-       private void checkDir(Path dir, PathMatcher pm, SortedMap<Version, Path> res) throws IOException {
-               try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
-                       for (Path path : ds) {
-                               if (Files.isDirectory(path))
-                                       checkDir(path, pm, res);
-                               else if (pm.matches(path)) {
-                                       String fileName = path.getFileName().toString();
-                                       fileName = fileName.substring(0, fileName.lastIndexOf('.'));
-                                       if (fileName.endsWith("-SNAPSHOT"))
-                                               fileName = fileName.substring(0, fileName.lastIndexOf('-')) + ".SNAPSHOT";
-                                       fileName = fileName.substring(fileName.lastIndexOf('-') + 1);
-                                       Version version = new Version(fileName);
-                                       res.put(version, path);
-                               }
-                       }
-               }
-       }
-
-       public void processUrl() {
-               JarInputStream jarIn = null;
-               try {
-                       URL u = new URL(url);
-
-                       // local cache
-                       URI localUri = new URI(localCache + relativeUrl);
-                       Path localPath = Paths.get(localUri);
-                       if (Files.exists(localPath))
-                               u = localUri.toURL();
-                       jarIn = new JarInputStream(u.openStream());
-
-                       // meta data
-                       manifest = jarIn.getManifest();
-                       symbolicName = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
-                       version = manifest.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
-
-                       JarEntry indexEntry;
-                       while ((indexEntry = jarIn.getNextJarEntry()) != null) {
-                               String entryName = indexEntry.getName();
-                               if (entryName.equals(INDEX_FILE_NAME)) {
-                                       break;
-                               }
-                               jarIn.closeEntry();
-                       }
-
-                       // list artifacts
-                       if (indexEntry == null)
-                               throw new OsgiBootException("No index " + INDEX_FILE_NAME + " in " + url);
-                       artifacts = listArtifacts(jarIn);
-                       jarIn.closeEntry();
-
-                       // find base URL
-                       // won't work if distribution artifact is not listed
-                       for (int i = 0; i < artifacts.size(); i++) {
-                               OsgiArtifact osgiArtifact = (OsgiArtifact) artifacts.get(i);
-                               if (osgiArtifact.getSymbolicName().equals(symbolicName) && osgiArtifact.getVersion().equals(version)) {
-                                       String relativeUrl = osgiArtifact.getRelativeUrl();
-                                       if (url.endsWith(relativeUrl)) {
-                                               baseUrl = url.substring(0, url.length() - osgiArtifact.getRelativeUrl().length());
-                                               break;
-                                       }
-                               }
-                       }
-               } catch (Exception e) {
-                       throw new OsgiBootException("Cannot list URLs from " + url, e);
-               } finally {
-                       if (jarIn != null)
-                               try {
-                                       jarIn.close();
-                               } catch (IOException e) {
-                                       // silent
-                               }
-               }
-       }
-
-       protected List<OsgiArtifact> listArtifacts(InputStream in) {
-               List<OsgiArtifact> osgiArtifacts = new ArrayList<OsgiArtifact>();
-               BufferedReader reader = null;
-               try {
-                       reader = new BufferedReader(new InputStreamReader(in));
-                       String line = null;
-                       lines: while ((line = reader.readLine()) != null) {
-                               StringTokenizer st = new StringTokenizer(line, separator);
-                               String moduleName = st.nextToken();
-                               String moduleVersion = st.nextToken();
-                               String relativeUrl = st.nextToken();
-                               if (relativeUrl.endsWith(".pom"))
-                                       continue lines;
-                               osgiArtifacts.add(new OsgiArtifact(moduleName, moduleVersion, relativeUrl));
-                       }
-               } catch (Exception e) {
-                       throw new OsgiBootException("Cannot list artifacts", e);
-               }
-               return osgiArtifacts;
-       }
-
-       /** Convenience method */
-       public static DistributionBundle processUrl(String baseUrl, String relativeUrl, String localCache) {
-               DistributionBundle distributionBundle = new DistributionBundle(baseUrl, relativeUrl, localCache);
-               distributionBundle.processUrl();
-               return distributionBundle;
-       }
-
-       /**
-        * List full URLs of the bundles, based on base URL, usable directly for
-        * download.
-        */
-       public List<String> listUrls() {
-               if (baseUrl == null)
-                       throw new OsgiBootException("Base URL is not set");
-
-               if (artifacts == null)
-                       throw new OsgiBootException("Artifact list not initialized");
-
-               List<String> urls = new ArrayList<String>();
-               for (int i = 0; i < artifacts.size(); i++) {
-                       OsgiArtifact osgiArtifact = (OsgiArtifact) artifacts.get(i);
-                       // local cache
-                       URI localUri;
-                       try {
-                               localUri = new URI(localCache + relativeUrl);
-                       } catch (URISyntaxException e) {
-                               OsgiBootUtils.warn(e.getMessage());
-                               localUri = null;
-                       }
-                       Version version = new Version(osgiArtifact.getVersion());
-                       if (localUri != null && Files.exists(Paths.get(localUri)) && version.getQualifier() != null
-                                       && version.getQualifier().startsWith("SNAPSHOT")) {
-                               urls.add(localCache + osgiArtifact.getRelativeUrl());
-                       } else {
-                               urls.add(baseUrl + osgiArtifact.getRelativeUrl());
-                       }
-               }
-               return urls;
-       }
-
-       public void setBaseUrl(String baseUrl) {
-               this.baseUrl = baseUrl;
-       }
-
-       /** Separator used to parse the tabular file */
-       public void setSeparator(String modulesUrlSeparator) {
-               this.separator = modulesUrlSeparator;
-       }
-
-       public String getRelativeUrl() {
-               return relativeUrl;
-       }
-
-       /** One of the listed artifact */
-       protected static class OsgiArtifact {
-               private final String symbolicName;
-               private final String version;
-               private final String relativeUrl;
-
-               public OsgiArtifact(String symbolicName, String version, String relativeUrl) {
-                       super();
-                       this.symbolicName = symbolicName;
-                       this.version = version;
-                       this.relativeUrl = relativeUrl;
-               }
-
-               public String getSymbolicName() {
-                       return symbolicName;
-               }
-
-               public String getVersion() {
-                       return version;
-               }
-
-               public String getRelativeUrl() {
-                       return relativeUrl;
-               }
-
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Launcher.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Launcher.java
deleted file mode 100644 (file)
index f67f45b..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.core.runtime.adaptor.EclipseStarter;
-import org.osgi.framework.BundleContext;
-
-/** Command line interface. */
-public class Launcher {
-
-       public static void main(String[] args) {
-               // Try to load system properties
-               String systemPropertiesFilePath = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE);
-               if (systemPropertiesFilePath != null) {
-                       FileInputStream in;
-                       try {
-                               in = new FileInputStream(systemPropertiesFilePath);
-                               System.getProperties().load(in);
-                       } catch (IOException e1) {
-                               throw new RuntimeException("Cannot load system properties from " + systemPropertiesFilePath, e1);
-                       }
-                       if (in != null) {
-                               try {
-                                       in.close();
-                               } catch (Exception e) {
-                                       // silent
-                               }
-                       }
-               }
-
-               // Start main class
-               startMainClass();
-
-               // Start Equinox
-               BundleContext bundleContext = null;
-               try {
-                       bundleContext = EclipseStarter.startup(args, null);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot start Equinox.", e);
-               }
-
-               // OSGi bootstrap
-               OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-               osgiBoot.bootstrap();
-       }
-
-       protected static void startMainClass() {
-               String className = getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
-               if (className == null)
-                       return;
-
-               String line = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPARGS, "");
-
-               String[] uiArgs = readArgumentsFromLine(line);
-
-               try {
-                       // Launch main method using reflection
-                       Class<?> clss = Class.forName(className);
-                       Class<?>[] mainArgsClasses = new Class[] { uiArgs.getClass() };
-                       Object[] mainArgs = { uiArgs };
-                       Method mainMethod = clss.getMethod("main", mainArgsClasses);
-                       mainMethod.invoke(null, mainArgs);
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot start main class.", e);
-               }
-
-       }
-
-       /**
-        * Transform a line into an array of arguments, taking "" as single
-        * arguments. (nested \" are not supported)
-        */
-       private static String[] readArgumentsFromLine(String lineOrig) {
-               String line = lineOrig.trim();// remove trailing spaces
-               List<String> args = new ArrayList<String>();
-               StringBuffer curr = new StringBuffer("");
-               boolean inQuote = false;
-               char[] arr = line.toCharArray();
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       switch (c) {
-                       case '\"':
-                               inQuote = !inQuote;
-                               break;
-                       case ' ':
-                               if (!inQuote) {// otherwise, no break: goes to default
-                                       if (curr.length() > 0) {
-                                               args.add(curr.toString());
-                                               curr = new StringBuffer("");
-                                       }
-                                       break;
-                               }
-                       default:
-                               curr.append(c);
-                               break;
-                       }
-               }
-
-               // Add last arg
-               if (curr.length() > 0) {
-                       args.add(curr.toString());
-                       curr = null;
-               }
-
-               String[] res = new String[args.size()];
-               for (int i = 0; i < args.size(); i++) {
-                       res[i] = args.get(i).toString();
-               }
-               return res;
-       }
-
-       public static String getProperty(String name, String defaultValue) {
-               final String value;
-               if (defaultValue != null)
-                       value = System.getProperty(name, defaultValue);
-               else
-                       value = System.getProperty(name);
-
-               if (value == null || value.equals(""))
-                       return null;
-               else
-                       return value;
-       }
-
-       public static String getProperty(String name) {
-               return getProperty(name, null);
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Main.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/Main.java
deleted file mode 100644 (file)
index 45094a7..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.lang.management.ManagementFactory;
-
-public class Main {
-
-       public static void main(String[] args) {
-               String mainClass = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS);
-               if (mainClass == null) {
-                       throw new IllegalArgumentException(
-                                       "System property " + OsgiBoot.PROP_ARGEO_OSGI_BOOT_APPCLASS + " must be specified");
-               }
-
-               OsgiBuilder osgi = new OsgiBuilder();
-               String distributionUrl = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_DISTRIBUTION_URL);
-               if (distributionUrl != null)
-                       osgi.install(distributionUrl);
-               // osgi.conf("argeo.node.useradmin.uris", "os:///");
-               // osgi.conf("osgi.clean", "true");
-               // osgi.conf("osgi.console", "true");
-               osgi.launch();
-
-               if (OsgiBootUtils.isDebug()) {
-                       long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
-                       String jvmUptimeStr = (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s";
-                       OsgiBootUtils.debug("Ready to launch " + mainClass + " in " + jvmUptimeStr);
-               }
-
-               osgi.main(mainClass, args);
-
-               osgi.shutdown();
-
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/NodeRunner.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/NodeRunner.java
deleted file mode 100644 (file)
index 263a4cd..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.ServiceLoader;
-
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
-
-/** Launch an OSGi framework and deploy a CMS Node into it. */
-public class NodeRunner {
-       private Long timeout = 30 * 1000l;
-       private final Path baseDir;
-       private final Path confDir;
-       private final Path dataDir;
-
-       private String baseUrl = "http://forge.argeo.org/data/java/argeo-2.1/";
-       private String distributionUrl = null;
-
-       private Framework framework = null;
-
-       public NodeRunner(String distributionUrl, Path baseDir) {
-               this.distributionUrl = distributionUrl;
-               Path mavenBase = Paths.get(System.getProperty("user.home") + "/.m2/repository");
-               Path osgiBase = Paths.get("/user/share/osgi");
-               if (Files.exists(mavenBase)) {
-                       Path mavenPath = mavenBase.resolve(distributionUrl);
-                       if (Files.exists(mavenPath))
-                               baseUrl = mavenBase.toUri().toString();
-               } else if (Files.exists(osgiBase)) {
-                       Path osgiPath = osgiBase.resolve(distributionUrl);
-                       if (Files.exists(osgiPath))
-                               baseUrl = osgiBase.toUri().toString();
-               }
-
-               this.baseDir = baseDir;
-               this.confDir = this.baseDir.resolve("state");
-               this.dataDir = this.baseDir.resolve("data");
-
-       }
-
-       public void start() {
-               long begin = System.currentTimeMillis();
-               // log4j
-               Path log4jFile = confDir.resolve("log4j.properties");
-               if (!Files.exists(log4jFile))
-                       copyResource("/org/argeo/osgi/boot/log4j.properties", log4jFile);
-               System.setProperty("log4j.configuration", "file://" + log4jFile.toAbsolutePath());
-
-               // Start Equinox
-               try {
-                       ServiceLoader<FrameworkFactory> ff = ServiceLoader.load(FrameworkFactory.class);
-                       FrameworkFactory frameworkFactory = ff.iterator().next();
-                       Map<String, String> configuration = new HashMap<String, String>();
-                       configuration.put("osgi.configuration.area", confDir.toAbsolutePath().toString());
-                       configuration.put("osgi.instance.area", dataDir.toAbsolutePath().toString());
-                       defaultConfiguration(configuration);
-
-                       framework = frameworkFactory.newFramework(configuration);
-                       framework.start();
-                       info("## Date : " + new Date());
-                       info("## Data : " + dataDir.toAbsolutePath());
-               } catch (Exception e) {
-                       throw new IllegalStateException("Cannot start OSGi framework", e);
-               }
-               BundleContext bundleContext = framework.getBundleContext();
-               try {
-
-                       // Spring configs currently require System properties
-                       // System.getProperties().putAll(configuration);
-
-                       // expected by JAAS as System.property FIXME
-                       System.setProperty("osgi.instance.area", bundleContext.getProperty("osgi.instance.area"));
-
-                       // OSGi bootstrap
-                       OsgiBoot osgiBoot = new OsgiBoot(bundleContext);
-
-                       osgiBoot.installUrls(osgiBoot.getDistributionUrls(distributionUrl, baseUrl));
-
-                       // Start runtime
-                       Properties startProperties = new Properties();
-                       // TODO make it possible to override it
-                       startProperties.put("argeo.osgi.start.2.node",
-                                       "org.eclipse.equinox.http.servlet,org.eclipse.equinox.http.jetty,"
-                                                       + "org.eclipse.equinox.metatype,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi");
-                       startProperties.put("argeo.osgi.start.3.node", "org.argeo.cms");
-                       startProperties.put("argeo.osgi.start.4.node",
-                                       "org.eclipse.gemini.blueprint.extender,org.eclipse.equinox.http.registry");
-                       osgiBoot.startBundles(startProperties);
-
-                       // Find node repository
-                       ServiceReference<?> sr = null;
-                       while (sr == null) {
-                               sr = bundleContext.getServiceReference("javax.jcr.Repository");
-                               if (System.currentTimeMillis() - begin > timeout)
-                                       throw new RuntimeException("Could find node after " + timeout + "ms");
-                               Thread.sleep(100);
-                       }
-                       Object nodeDeployment = bundleContext.getService(sr);
-                       info("Node Deployment " + nodeDeployment);
-
-                       // Initialization completed
-                       long duration = System.currentTimeMillis() - begin;
-                       info("## CMS Launcher initialized in " + (duration / 1000) + "s " + (duration % 1000) + "ms");
-               } catch (Exception e) {
-                       shutdown();
-                       throw new RuntimeException("Cannot start CMS", e);
-               } finally {
-
-               }
-       }
-
-       private void defaultConfiguration(Map<String, String> configuration) {
-               // all permissions to OSGi security manager
-               Path policyFile = confDir.resolve("node.policy");
-               if (!Files.exists(policyFile))
-                       copyResource("/org/argeo/osgi/boot/node.policy", policyFile);
-               configuration.put("java.security.policy", "file://" + policyFile.toAbsolutePath());
-
-               configuration.put("org.eclipse.rap.workbenchAutostart", "false");
-               configuration.put("org.eclipse.equinox.http.jetty.autostart", "false");
-               configuration.put("org.osgi.framework.bootdelegation",
-                               "com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,"
-                                               + "com.sun.nio.file,com.sun.nio.sctp");
-
-               // Do clean
-               // configuration.put("osgi.clean", "true");
-               // if (args.length == 0) {
-               // configuration.put("osgi.console", "");
-               // }
-       }
-
-       public void shutdown() {
-               try {
-                       framework.stop();
-                       framework.waitForStop(15 * 1000);
-               } catch (Exception silent) {
-               }
-       }
-
-       public Path getConfDir() {
-               return confDir;
-       }
-
-       public Path getDataDir() {
-               return dataDir;
-       }
-
-       public Framework getFramework() {
-               return framework;
-       }
-
-       public static void main(String[] args) {
-               try {
-                       String distributionUrl;
-                       Path executionDir;
-                       if (args.length == 2) {
-                               distributionUrl = args[0];
-                               executionDir = Paths.get(args[1]);
-                       } else if (args.length == 1) {
-                               executionDir = Paths.get(System.getProperty("user.dir"));
-                               distributionUrl = args[0];
-                       } else if (args.length == 0) {
-                               executionDir = Paths.get(System.getProperty("user.dir"));
-                               distributionUrl = "org/argeo/commons/org.argeo.dep.cms.sdk/2.1.70/org.argeo.dep.cms.sdk-2.1.70.jar";
-                       }else{
-                               printUsage();
-                               System.exit(1);
-                               return;
-                       }
-
-                       NodeRunner nodeRunner = new NodeRunner(distributionUrl, executionDir);
-                       nodeRunner.start();
-//                     if (args.length != 0)
-//                             System.exit(0);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-
-       protected static void info(Object msg) {
-               System.out.println(msg);
-       }
-
-       protected static void err(Object msg) {
-               System.err.println(msg);
-       }
-
-       protected static void debug(Object msg) {
-               System.out.println(msg);
-       }
-
-       protected static void copyResource(String resource, Path targetFile) {
-               InputStream input = null;
-               OutputStream output = null;
-               try {
-                       input = NodeRunner.class.getResourceAsStream(resource);
-                       Files.createDirectories(targetFile.getParent());
-                       output = Files.newOutputStream(targetFile);
-                       byte[] buf = new byte[8192];
-                       while (true) {
-                               int length = input.read(buf);
-                               if (length < 0)
-                                       break;
-                               output.write(buf, 0, length);
-                       }
-               } catch (Exception e) {
-                       throw new RuntimeException("Cannot write " + resource + " file to " + targetFile, e);
-               } finally {
-                       try {
-                               input.close();
-                       } catch (Exception ignore) {
-                       }
-                       try {
-                               output.close();
-                       } catch (Exception ignore) {
-                       }
-               }
-
-       }
-
-       static void printUsage(){
-               err("Usage: <distribution url> <base dir>");
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBoot.java
deleted file mode 100644 (file)
index b49e720..0000000
+++ /dev/null
@@ -1,753 +0,0 @@
-package org.argeo.osgi.boot;
-
-import static org.argeo.osgi.boot.OsgiBootUtils.debug;
-import static org.argeo.osgi.boot.OsgiBootUtils.warn;
-
-import java.io.File;
-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 java.util.Properties;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.StringTokenizer;
-import java.util.TreeMap;
-
-import org.argeo.osgi.a2.A2Source;
-import org.argeo.osgi.a2.ProvisioningManager;
-import org.argeo.osgi.boot.internal.springutil.AntPathMatcher;
-import org.argeo.osgi.boot.internal.springutil.PathMatcher;
-import org.argeo.osgi.boot.internal.springutil.SystemPropertyUtils;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkEvent;
-import org.osgi.framework.Version;
-import org.osgi.framework.startlevel.BundleStartLevel;
-import org.osgi.framework.startlevel.FrameworkStartLevel;
-import org.osgi.framework.wiring.FrameworkWiring;
-
-/**
- * Basic provisioning of an OSGi runtime via file path patterns and system
- * properties. The approach is to generate list of URLs based on various
- * methods, configured via properties.
- */
-public class OsgiBoot implements OsgiBootConstants {
-       public final static String PROP_ARGEO_OSGI_START = "argeo.osgi.start";
-       public final static String PROP_ARGEO_OSGI_SOURCES = "argeo.osgi.sources";
-
-       public final static String PROP_ARGEO_OSGI_BUNDLES = "argeo.osgi.bundles";
-       public final static String PROP_ARGEO_OSGI_BASE_URL = "argeo.osgi.baseUrl";
-       public final static String PROP_ARGEO_OSGI_LOCAL_CACHE = "argeo.osgi.localCache";
-       public final static String PROP_ARGEO_OSGI_DISTRIBUTION_URL = "argeo.osgi.distributionUrl";
-
-       // booleans
-       public final static String PROP_ARGEO_OSGI_BOOT_DEBUG = "argeo.osgi.boot.debug";
-       // public final static String PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN =
-       // "argeo.osgi.boot.excludeSvn";
-
-       public final static String PROP_ARGEO_OSGI_BOOT_SYSTEM_PROPERTIES_FILE = "argeo.osgi.boot.systemPropertiesFile";
-       public final static String PROP_ARGEO_OSGI_BOOT_APPCLASS = "argeo.osgi.boot.appclass";
-       public final static String PROP_ARGEO_OSGI_BOOT_APPARGS = "argeo.osgi.boot.appargs";
-
-       public final static String DEFAULT_BASE_URL = "reference:file:";
-       // public final static String EXCLUDES_SVN_PATTERN = "**/.svn/**";
-
-       // OSGi system properties
-       final static String PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL = "osgi.bundles.defaultStartLevel";
-       final static String PROP_OSGI_STARTLEVEL = "osgi.startLevel";
-       final static String INSTANCE_AREA_PROP = "osgi.instance.area";
-       final static String CONFIGURATION_AREA_PROP = "osgi.configuration.area";
-
-       // Symbolic names
-       public final static String SYMBOLIC_NAME_OSGI_BOOT = "org.argeo.osgi.boot";
-       public final static String SYMBOLIC_NAME_EQUINOX = "org.eclipse.osgi";
-
-       /** Exclude svn metadata implicitely(a bit costly) */
-       // private boolean excludeSvn =
-       // Boolean.valueOf(System.getProperty(PROP_ARGEO_OSGI_BOOT_EXCLUDE_SVN,
-       // "false"))
-       // .booleanValue();
-
-       /** Default is 10s */
-       @Deprecated
-       private long defaultTimeout = 10000l;
-
-       private final BundleContext bundleContext;
-       private final String localCache;
-
-       private final ProvisioningManager provisioningManager;
-
-       /*
-        * INITIALIZATION
-        */
-       /** Constructor */
-       public OsgiBoot(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-               Path homePath = Paths.get(System.getProperty("user.home")).toAbsolutePath();
-               String homeUri = homePath.toUri().toString();
-               localCache = getProperty(PROP_ARGEO_OSGI_LOCAL_CACHE, homeUri + ".m2/repository/");
-
-               provisioningManager = new ProvisioningManager(bundleContext);
-               String sources = getProperty(PROP_ARGEO_OSGI_SOURCES);
-               if (sources == null) {
-                       provisioningManager.registerDefaultSource();
-               } else {
-                       for (String source : sources.split(",")) {
-                               if (source.trim().equals(A2Source.DEFAULT_A2_URI)) {
-                                       if (Files.exists(homePath))
-                                               provisioningManager.registerSource(
-                                                               A2Source.SCHEME_A2 + "://" + homePath.toString() + "/.local/share/osgi");
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/local/share/osgi");
-                                       provisioningManager.registerSource(A2Source.SCHEME_A2 + ":///usr/share/osgi");
-                               } else {
-                                       provisioningManager.registerSource(source);
-                               }
-                       }
-               }
-       }
-
-       ProvisioningManager getProvisioningManager() {
-               return provisioningManager;
-       }
-
-       /*
-        * HIGH-LEVEL METHODS
-        */
-       /** Bootstraps the OSGi runtime */
-       public void bootstrap() {
-               try {
-                       long begin = System.currentTimeMillis();
-                       System.out.println();
-                       String osgiInstancePath = bundleContext.getProperty(INSTANCE_AREA_PROP);
-                       OsgiBootUtils
-                                       .info("OSGi bootstrap starting" + (osgiInstancePath != null ? " (" + osgiInstancePath + ")" : ""));
-                       installUrls(getBundlesUrls());
-                       installUrls(getDistributionUrls());
-                       provisioningManager.install(null);
-                       startBundles();
-                       long duration = System.currentTimeMillis() - begin;
-                       OsgiBootUtils.info("OSGi bootstrap completed in " + Math.round(((double) duration) / 1000) + "s ("
-                                       + duration + "ms), " + bundleContext.getBundles().length + " bundles");
-               } catch (RuntimeException e) {
-                       OsgiBootUtils.error("OSGi bootstrap FAILED", e);
-                       throw e;
-               }
-
-               // diagnostics
-               if (OsgiBootUtils.debug) {
-                       OsgiBootDiagnostics diagnostics = new OsgiBootDiagnostics(bundleContext);
-                       diagnostics.checkUnresolved();
-                       Map<String, Set<String>> duplicatePackages = diagnostics.findPackagesExportedTwice();
-                       if (duplicatePackages.size() > 0) {
-                               OsgiBootUtils.info("Packages exported twice:");
-                               Iterator<String> it = duplicatePackages.keySet().iterator();
-                               while (it.hasNext()) {
-                                       String pkgName = it.next();
-                                       OsgiBootUtils.info(pkgName);
-                                       Set<String> bdles = duplicatePackages.get(pkgName);
-                                       Iterator<String> bdlesIt = bdles.iterator();
-                                       while (bdlesIt.hasNext())
-                                               OsgiBootUtils.info("  " + bdlesIt.next());
-                               }
-                       }
-               }
-               System.out.println();
-       }
-
-       public void update() {
-               provisioningManager.update();
-       }
-
-       /*
-        * INSTALLATION
-        */
-       /** Install a single url. Convenience method. */
-       public Bundle installUrl(String url) {
-               List<String> urls = new ArrayList<String>();
-               urls.add(url);
-               installUrls(urls);
-               return (Bundle) getBundlesByLocation().get(url);
-       }
-
-       /** Install the bundles at this URL list. */
-       public void installUrls(List<String> urls) {
-               Map<String, Bundle> installedBundles = getBundlesByLocation();
-               for (int i = 0; i < urls.size(); i++) {
-                       String url = (String) urls.get(i);
-                       installUrl(url, installedBundles);
-               }
-               refreshFramework();
-       }
-
-       /** Actually install the provided URL */
-       protected void installUrl(String url, Map<String, Bundle> installedBundles) {
-               try {
-                       if (installedBundles.containsKey(url)) {
-                               Bundle bundle = (Bundle) installedBundles.get(url);
-                               if (OsgiBootUtils.debug)
-                                       debug("Bundle " + bundle.getSymbolicName() + " already installed from " + url);
-                       } else if (url.contains("/" + SYMBOLIC_NAME_EQUINOX + "/")
-                                       || url.contains("/" + SYMBOLIC_NAME_OSGI_BOOT + "/")) {
-                               if (OsgiBootUtils.debug)
-                                       warn("Skip " + url);
-                               return;
-                       } else {
-                               Bundle bundle = bundleContext.installBundle(url);
-                               if (url.startsWith("http"))
-                                       OsgiBootUtils
-                                                       .info("Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
-                               else if (OsgiBootUtils.debug)
-                                       OsgiBootUtils.debug(
-                                                       "Installed " + bundle.getSymbolicName() + "-" + bundle.getVersion() + " from " + url);
-                               assert bundle.getSymbolicName() != null;
-                               // uninstall previous versions
-                               bundles: for (Bundle b : bundleContext.getBundles()) {
-                                       if (b.getSymbolicName() == null)
-                                               continue bundles;
-                                       if (bundle.getSymbolicName().equals(b.getSymbolicName())) {
-                                               Version bundleV = bundle.getVersion();
-                                               Version bV = b.getVersion();
-                                               if (bV == null)
-                                                       continue bundles;
-                                               if (bundleV.getMajor() == bV.getMajor() && bundleV.getMinor() == bV.getMinor()) {
-                                                       if (bundleV.getMicro() > bV.getMicro()) {
-                                                               // uninstall older bundles
-                                                               b.uninstall();
-                                                               OsgiBootUtils.debug("Uninstalled " + b);
-                                                       } else if (bundleV.getMicro() < bV.getMicro()) {
-                                                               // uninstall just installed bundle if newer
-                                                               bundle.uninstall();
-                                                               OsgiBootUtils.debug("Uninstalled " + bundle);
-                                                               break bundles;
-                                                       } else {
-                                                               // uninstall any other with same major/minor
-                                                               if (!bundleV.getQualifier().equals(bV.getQualifier())) {
-                                                                       b.uninstall();
-                                                                       OsgiBootUtils.debug("Uninstalled " + b);
-                                                               }
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               } catch (BundleException e) {
-                       final String ALREADY_INSTALLED = "is already installed";
-                       String message = e.getMessage();
-                       if ((message.contains("Bundle \"" + SYMBOLIC_NAME_OSGI_BOOT + "\"")
-                                       || message.contains("Bundle \"" + SYMBOLIC_NAME_EQUINOX + "\""))
-                                       && message.contains(ALREADY_INSTALLED)) {
-                               // silent, in order to avoid warnings: we know that both
-                               // have already been installed...
-                       } else {
-                               if (message.contains(ALREADY_INSTALLED)) {
-                                       if (OsgiBootUtils.isDebug())
-                                               OsgiBootUtils.warn("Duplicate install from " + url + ": " + message);
-                               } else
-                                       OsgiBootUtils.warn("Could not install bundle from " + url + ": " + message);
-                       }
-                       if (OsgiBootUtils.debug && !message.contains(ALREADY_INSTALLED))
-                               e.printStackTrace();
-               }
-       }
-
-       /*
-        * START
-        */
-       public void startBundles() {
-               startBundles(System.getProperties());
-       }
-
-       public void startBundles(Properties properties) {
-               FrameworkStartLevel frameworkStartLevel = bundleContext.getBundle(0).adapt(FrameworkStartLevel.class);
-
-               // default and active start levels from System properties
-               Integer defaultStartLevel = new Integer(
-                               Integer.parseInt(getProperty(PROP_OSGI_BUNDLES_DEFAULTSTARTLEVEL, "4")));
-               Integer activeStartLevel = new Integer(getProperty(PROP_OSGI_STARTLEVEL, "6"));
-
-               SortedMap<Integer, List<String>> startLevels = new TreeMap<Integer, List<String>>();
-               computeStartLevels(startLevels, properties, defaultStartLevel);
-               // inverts the map for the time being, TODO optimise
-               Map<String, Integer> bundleStartLevels = new HashMap<>();
-               for (Integer level : startLevels.keySet()) {
-                       for (String bsn : startLevels.get(level))
-                               bundleStartLevels.put(bsn, level);
-               }
-               for (Bundle bundle : bundleContext.getBundles()) {
-                       String bsn = bundle.getSymbolicName();
-                       if (bundleStartLevels.containsKey(bsn)) {
-                               BundleStartLevel bundleStartLevel = bundle.adapt(BundleStartLevel.class);
-                               Integer level = bundleStartLevels.get(bsn);
-                               if (bundleStartLevel.getStartLevel() != level || !bundleStartLevel.isPersistentlyStarted()) {
-                                       bundleStartLevel.setStartLevel(level);
-                                       try {
-                                               bundle.start();
-                                       } catch (BundleException e) {
-                                               OsgiBootUtils.error("Cannot mark " + bsn + " as started", e);
-                                       }
-                                       if (getDebug())
-                                               OsgiBootUtils.debug(bsn + " starts at level " + level);
-                               }
-                       }
-               }
-               frameworkStartLevel.setStartLevel(activeStartLevel, (FrameworkEvent event) -> {
-                       if (getDebug())
-                               OsgiBootUtils.debug("Framework event: " + event);
-                       int initialStartLevel = frameworkStartLevel.getInitialBundleStartLevel();
-                       int startLevel = frameworkStartLevel.getStartLevel();
-                       OsgiBootUtils.debug("Framework start level: " + startLevel + " (initial: " + initialStartLevel + ")");
-               });
-       }
-
-       private static void computeStartLevels(SortedMap<Integer, List<String>> startLevels, Properties properties,
-                       Integer defaultStartLevel) {
-
-               // default (and previously, only behaviour)
-               appendToStartLevels(startLevels, defaultStartLevel, properties.getProperty(PROP_ARGEO_OSGI_START, ""));
-
-               // list argeo.osgi.start.* system properties
-               Iterator<Object> keys = properties.keySet().iterator();
-               final String prefix = PROP_ARGEO_OSGI_START + ".";
-               while (keys.hasNext()) {
-                       String key = keys.next().toString();
-                       if (key.startsWith(prefix)) {
-                               Integer startLevel;
-                               String suffix = key.substring(prefix.length());
-                               String[] tokens = suffix.split("\\.");
-                               if (tokens.length > 0 && !tokens[0].trim().equals(""))
-                                       try {
-                                               // first token is start level
-                                               startLevel = new Integer(tokens[0]);
-                                       } catch (NumberFormatException e) {
-                                               startLevel = defaultStartLevel;
-                                       }
-                               else
-                                       startLevel = defaultStartLevel;
-
-                               // append bundle names
-                               String bundleNames = properties.getProperty(key);
-                               appendToStartLevels(startLevels, startLevel, bundleNames);
-                       }
-               }
-       }
-
-       /** Append a comma-separated list of bundles to the start levels. */
-       private static void appendToStartLevels(SortedMap<Integer, List<String>> startLevels, Integer startLevel,
-                       String str) {
-               if (str == null || str.trim().equals(""))
-                       return;
-
-               if (!startLevels.containsKey(startLevel))
-                       startLevels.put(startLevel, new ArrayList<String>());
-               String[] bundleNames = str.split(",");
-               for (int i = 0; i < bundleNames.length; i++) {
-                       if (bundleNames[i] != null && !bundleNames[i].trim().equals(""))
-                               (startLevels.get(startLevel)).add(bundleNames[i]);
-               }
-       }
-
-       /**
-        * Start the provided list of bundles
-        *
-        * @return whether all bundles are now in active state
-        * @deprecated
-        */
-       @Deprecated
-       public boolean startBundles(List<String> bundlesToStart) {
-               if (bundlesToStart.size() == 0)
-                       return true;
-
-               // used to monitor ACTIVE states
-               List<Bundle> startedBundles = new ArrayList<Bundle>();
-               // used to log the bundles not found
-               List<String> notFoundBundles = new ArrayList<String>(bundlesToStart);
-
-               Bundle[] bundles = bundleContext.getBundles();
-               long startBegin = System.currentTimeMillis();
-               for (int i = 0; i < bundles.length; i++) {
-                       Bundle bundle = bundles[i];
-                       String symbolicName = bundle.getSymbolicName();
-                       if (bundlesToStart.contains(symbolicName))
-                               try {
-                                       try {
-                                               bundle.start();
-                                               if (OsgiBootUtils.debug)
-                                                       debug("Bundle " + symbolicName + " started");
-                                       } catch (Exception e) {
-                                               OsgiBootUtils.warn("Start of bundle " + symbolicName + " failed because of " + e
-                                                               + ", maybe bundle is not yet resolved," + " waiting and trying again.");
-                                               waitForBundleResolvedOrActive(startBegin, bundle);
-                                               bundle.start();
-                                               startedBundles.add(bundle);
-                                       }
-                                       notFoundBundles.remove(symbolicName);
-                               } catch (Exception e) {
-                                       OsgiBootUtils.warn("Bundle " + symbolicName + " cannot be started: " + e.getMessage());
-                                       if (OsgiBootUtils.debug)
-                                               e.printStackTrace();
-                                       // was found even if start failed
-                                       notFoundBundles.remove(symbolicName);
-                               }
-               }
-
-               for (int i = 0; i < notFoundBundles.size(); i++)
-                       OsgiBootUtils.warn("Bundle '" + notFoundBundles.get(i) + "' not started because it was not found.");
-
-               // monitors that all bundles are started
-               long beginMonitor = System.currentTimeMillis();
-               boolean allStarted = !(startedBundles.size() > 0);
-               List<String> notStarted = new ArrayList<String>();
-               while (!allStarted && (System.currentTimeMillis() - beginMonitor) < defaultTimeout) {
-                       notStarted = new ArrayList<String>();
-                       allStarted = true;
-                       for (int i = 0; i < startedBundles.size(); i++) {
-                               Bundle bundle = (Bundle) startedBundles.get(i);
-                               // TODO check behaviour of lazs bundles
-                               if (bundle.getState() != Bundle.ACTIVE) {
-                                       allStarted = false;
-                                       notStarted.add(bundle.getSymbolicName());
-                               }
-                       }
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-               }
-               long duration = System.currentTimeMillis() - beginMonitor;
-
-               if (!allStarted)
-                       for (int i = 0; i < notStarted.size(); i++)
-                               OsgiBootUtils.warn("Bundle '" + notStarted.get(i) + "' not ACTIVE after " + (duration / 1000) + "s");
-
-               return allStarted;
-       }
-
-       /** Waits for a bundle to become active or resolved */
-       @Deprecated
-       private void waitForBundleResolvedOrActive(long startBegin, Bundle bundle) throws Exception {
-               int originalState = bundle.getState();
-               if ((originalState == Bundle.RESOLVED) || (originalState == Bundle.ACTIVE))
-                       return;
-
-               String originalStateStr = OsgiBootUtils.stateAsString(originalState);
-
-               int currentState = bundle.getState();
-               while (!(currentState == Bundle.RESOLVED || currentState == Bundle.ACTIVE)) {
-                       long now = System.currentTimeMillis();
-                       if ((now - startBegin) > defaultTimeout * 10)
-                               throw new Exception("Bundle " + bundle.getSymbolicName() + " was not RESOLVED or ACTIVE after "
-                                               + (now - startBegin) + "ms (originalState=" + originalStateStr + ", currentState="
-                                               + OsgiBootUtils.stateAsString(currentState) + ")");
-
-                       try {
-                               Thread.sleep(100l);
-                       } catch (InterruptedException e) {
-                               // silent
-                       }
-                       currentState = bundle.getState();
-               }
-       }
-
-       /*
-        * BUNDLE PATTERNS INSTALLATION
-        */
-       /**
-        * Computes a list of URLs based on Ant-like include/exclude patterns defined by
-        * ${argeo.osgi.bundles} with the following format:<br>
-        * <code>/base/directory;in=*.jar;in=**;ex=org.eclipse.osgi_*;jar</code><br>
-        * WARNING: <code>/base/directory;in=*.jar,\</code> at the end of a file,
-        * without a new line causes a '.' to be appended with unexpected side effects.
-        */
-       public List<String> getBundlesUrls() {
-               String bundlePatterns = getProperty(PROP_ARGEO_OSGI_BUNDLES);
-               return getBundlesUrls(bundlePatterns);
-       }
-
-       /**
-        * Compute a list of URLs to install based on the provided patterns, with
-        * default base url
-        */
-       public List<String> getBundlesUrls(String bundlePatterns) {
-               String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL, DEFAULT_BASE_URL);
-               return getBundlesUrls(baseUrl, bundlePatterns);
-       }
-
-       /** Implements the path matching logic */
-       public List<String> getBundlesUrls(String baseUrl, String bundlePatterns) {
-               List<String> urls = new ArrayList<String>();
-               if (bundlePatterns == null)
-                       return urls;
-
-               bundlePatterns = SystemPropertyUtils.resolvePlaceholders(bundlePatterns);
-               if (OsgiBootUtils.debug)
-                       debug(PROP_ARGEO_OSGI_BUNDLES + "=" + bundlePatterns);
-
-               StringTokenizer st = new StringTokenizer(bundlePatterns, ",");
-               List<BundlesSet> bundlesSets = new ArrayList<BundlesSet>();
-               while (st.hasMoreTokens()) {
-                       String token = st.nextToken();
-                       if (new File(token).exists()) {
-                               String url = locationToUrl(baseUrl, token);
-                               urls.add(url);
-                       } else
-                               bundlesSets.add(new BundlesSet(token));
-               }
-
-               // find included
-               List<String> included = new ArrayList<String>();
-               PathMatcher matcher = new AntPathMatcher();
-               for (int i = 0; i < bundlesSets.size(); i++) {
-                       BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
-                       for (int j = 0; j < bundlesSet.getIncludes().size(); j++) {
-                               String pattern = (String) bundlesSet.getIncludes().get(j);
-                               match(matcher, included, bundlesSet.getDir(), null, pattern);
-                       }
-               }
-
-               // find excluded
-               List<String> excluded = new ArrayList<String>();
-               for (int i = 0; i < bundlesSets.size(); i++) {
-                       BundlesSet bundlesSet = (BundlesSet) bundlesSets.get(i);
-                       for (int j = 0; j < bundlesSet.getExcludes().size(); j++) {
-                               String pattern = (String) bundlesSet.getExcludes().get(j);
-                               match(matcher, excluded, bundlesSet.getDir(), null, pattern);
-                       }
-               }
-
-               // construct list
-               for (int i = 0; i < included.size(); i++) {
-                       String fullPath = (String) included.get(i);
-                       if (!excluded.contains(fullPath))
-                               urls.add(locationToUrl(baseUrl, fullPath));
-               }
-
-               return urls;
-       }
-
-       /*
-        * DISTRIBUTION JAR INSTALLATION
-        */
-       public List<String> getDistributionUrls() {
-               String distributionUrl = getProperty(PROP_ARGEO_OSGI_DISTRIBUTION_URL);
-               String baseUrl = getProperty(PROP_ARGEO_OSGI_BASE_URL);
-               return getDistributionUrls(distributionUrl, baseUrl);
-       }
-
-       public List<String> getDistributionUrls(String distributionUrl, String baseUrl) {
-               List<String> urls = new ArrayList<String>();
-               if (distributionUrl == null)
-                       return urls;
-
-               DistributionBundle distributionBundle;
-               if (distributionUrl.startsWith("http") || distributionUrl.startsWith("file")) {
-                       distributionBundle = new DistributionBundle(distributionUrl);
-                       if (baseUrl != null)
-                               distributionBundle.setBaseUrl(baseUrl);
-               } else {
-                       // relative url
-                       if (baseUrl == null) {
-                               baseUrl = localCache;
-                       }
-
-                       if (distributionUrl.contains(":")) {
-                               // TODO make it safer
-                               String[] parts = distributionUrl.trim().split(":");
-                               String[] categoryParts = parts[0].split("\\.");
-                               String artifactId = parts[1];
-                               String version = parts[2];
-                               StringBuilder sb = new StringBuilder();
-                               for (String categoryPart : categoryParts) {
-                                       sb.append(categoryPart).append('/');
-                               }
-                               sb.append(artifactId).append('/');
-                               sb.append(version).append('/');
-                               sb.append(artifactId).append('-').append(version).append(".jar");
-                               distributionUrl = sb.toString();
-                       }
-
-                       distributionBundle = new DistributionBundle(baseUrl, distributionUrl, localCache);
-               }
-               // if (baseUrl != null && !(distributionUrl.startsWith("http") ||
-               // distributionUrl.startsWith("file"))) {
-               // // relative url
-               // distributionBundle = new DistributionBundle(baseUrl, distributionUrl,
-               // localCache);
-               // } else {
-               // distributionBundle = new DistributionBundle(distributionUrl);
-               // if (baseUrl != null)
-               // distributionBundle.setBaseUrl(baseUrl);
-               // }
-               distributionBundle.processUrl();
-               return distributionBundle.listUrls();
-       }
-
-       /*
-        * HIGH LEVEL UTILITIES
-        */
-       /** Actually performs the matching logic. */
-       protected void match(PathMatcher matcher, List<String> matched, String base, String currentPath, String pattern) {
-               if (currentPath == null) {
-                       // Init
-                       File baseDir = new File(base.replace('/', File.separatorChar));
-                       File[] files = baseDir.listFiles();
-
-                       if (files == null) {
-                               if (OsgiBootUtils.debug)
-                                       OsgiBootUtils.warn("Base dir " + baseDir + " has no children, exists=" + baseDir.exists()
-                                                       + ", isDirectory=" + baseDir.isDirectory());
-                               return;
-                       }
-
-                       for (int i = 0; i < files.length; i++)
-                               match(matcher, matched, base, files[i].getName(), pattern);
-               } else {
-                       String fullPath = base + '/' + currentPath;
-                       if (matched.contains(fullPath))
-                               return;// don't try deeper if already matched
-
-                       boolean ok = matcher.match(pattern, currentPath);
-                       // if (debug)
-                       // debug(currentPath + " " + (ok ? "" : " not ")
-                       // + " matched with " + pattern);
-                       if (ok) {
-                               matched.add(fullPath);
-                               return;
-                       } else {
-                               String newFullPath = relativeToFullPath(base, currentPath);
-                               File newFile = new File(newFullPath);
-                               File[] files = newFile.listFiles();
-                               if (files != null) {
-                                       for (int i = 0; i < files.length; i++) {
-                                               String newCurrentPath = currentPath + '/' + files[i].getName();
-                                               if (files[i].isDirectory()) {
-                                                       if (matcher.matchStart(pattern, newCurrentPath)) {
-                                                               // recurse only if start matches
-                                                               match(matcher, matched, base, newCurrentPath, pattern);
-                                                       } else {
-                                                               if (OsgiBootUtils.debug)
-                                                                       debug(newCurrentPath + " does not start match with " + pattern);
-
-                                                       }
-                                               } else {
-                                                       boolean nonDirectoryOk = matcher.match(pattern, newCurrentPath);
-                                                       if (OsgiBootUtils.debug)
-                                                               debug(currentPath + " " + (ok ? "" : " not ") + " matched with " + pattern);
-                                                       if (nonDirectoryOk)
-                                                               matched.add(relativeToFullPath(base, newCurrentPath));
-                                               }
-                                       }
-                               }
-                       }
-               }
-       }
-
-       protected void matchFile() {
-
-       }
-
-       /*
-        * LOW LEVEL UTILITIES
-        */
-       /**
-        * The bundles already installed. Key is location (String) , value is a
-        * {@link Bundle}
-        */
-       public Map<String, Bundle> getBundlesByLocation() {
-               Map<String, Bundle> installedBundles = new HashMap<String, Bundle>();
-               Bundle[] bundles = bundleContext.getBundles();
-               for (int i = 0; i < bundles.length; i++) {
-                       installedBundles.put(bundles[i].getLocation(), bundles[i]);
-               }
-               return installedBundles;
-       }
-
-       /**
-        * The bundles already installed. Key is symbolic name (String) , value is a
-        * {@link Bundle}
-        */
-       public Map<String, Bundle> getBundlesBySymbolicName() {
-               Map<String, Bundle> namedBundles = new HashMap<String, Bundle>();
-               Bundle[] bundles = bundleContext.getBundles();
-               for (int i = 0; i < bundles.length; i++) {
-                       namedBundles.put(bundles[i].getSymbolicName(), bundles[i]);
-               }
-               return namedBundles;
-       }
-
-       /** Creates an URL from a location */
-       protected String locationToUrl(String baseUrl, String location) {
-               return baseUrl + location;
-       }
-
-       /** Transforms a relative path in a full system path. */
-       protected String relativeToFullPath(String basePath, String relativePath) {
-               return (basePath + '/' + relativePath).replace('/', File.separatorChar);
-       }
-
-       private void refreshFramework() {
-               Bundle systemBundle = bundleContext.getBundle(0);
-               FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
-               frameworkWiring.refreshBundles(null);
-       }
-
-       /**
-        * Gets a property value
-        * 
-        * @return null when defaultValue is ""
-        */
-       public String getProperty(String name, String defaultValue) {
-               String value = bundleContext.getProperty(name);
-               if (value == null)
-                       return defaultValue; // may be null
-               else
-                       return value;
-       }
-
-       public String getProperty(String name) {
-               return getProperty(name, null);
-       }
-
-       /*
-        * BEAN METHODS
-        */
-
-       public boolean getDebug() {
-               return OsgiBootUtils.debug;
-       }
-
-       // public void setDebug(boolean debug) {
-       // this.debug = debug;
-       // }
-
-       public BundleContext getBundleContext() {
-               return bundleContext;
-       }
-
-       public String getLocalCache() {
-               return localCache;
-       }
-
-       // public void setDefaultTimeout(long defaultTimeout) {
-       // this.defaultTimeout = defaultTimeout;
-       // }
-
-       // public boolean isExcludeSvn() {
-       // return excludeSvn;
-       // }
-       //
-       // public void setExcludeSvn(boolean excludeSvn) {
-       // this.excludeSvn = excludeSvn;
-       // }
-
-       /*
-        * INTERNAL CLASSES
-        */
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootConstants.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootConstants.java
deleted file mode 100644 (file)
index e2d7719..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.argeo.osgi.boot;
-
-public interface OsgiBootConstants {
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootDiagnostics.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootDiagnostics.java
deleted file mode 100644 (file)
index 24a3317..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.service.packageadmin.ExportedPackage;
-import org.osgi.service.packageadmin.PackageAdmin;
-
-@SuppressWarnings("deprecation")
-class OsgiBootDiagnostics {
-       private final BundleContext bundleContext;
-
-       public OsgiBootDiagnostics(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
-       }
-       /*
-        * DIAGNOSTICS
-        */
-       /** Check unresolved bundles */
-       protected void checkUnresolved() {
-               // Refresh
-               ServiceReference<PackageAdmin> packageAdminRef = bundleContext.getServiceReference(PackageAdmin.class);
-               PackageAdmin packageAdmin = (PackageAdmin) bundleContext.getService(packageAdminRef);
-               packageAdmin.resolveBundles(null);
-
-               Bundle[] bundles = bundleContext.getBundles();
-               List<Bundle> unresolvedBundles = new ArrayList<Bundle>();
-               for (int i = 0; i < bundles.length; i++) {
-                       int bundleState = bundles[i].getState();
-                       if (!(bundleState == Bundle.ACTIVE || bundleState == Bundle.RESOLVED || bundleState == Bundle.STARTING))
-                               unresolvedBundles.add(bundles[i]);
-               }
-
-               if (unresolvedBundles.size() != 0) {
-                       OsgiBootUtils.warn("Unresolved bundles " + unresolvedBundles);
-               }
-       }
-
-       /** List packages exported twice. */
-       public Map<String, Set<String>> findPackagesExportedTwice() {
-               ServiceReference<PackageAdmin> paSr = bundleContext.getServiceReference(PackageAdmin.class);
-               PackageAdmin packageAdmin = (PackageAdmin) bundleContext.getService(paSr);
-
-               // find packages exported twice
-               Bundle[] bundles = bundleContext.getBundles();
-               Map<String, Set<String>> exportedPackages = new TreeMap<String, Set<String>>();
-               for (int i = 0; i < bundles.length; i++) {
-                       Bundle bundle = bundles[i];
-                       ExportedPackage[] pkgs = packageAdmin.getExportedPackages(bundle);
-                       if (pkgs != null)
-                               for (int j = 0; j < pkgs.length; j++) {
-                                       String pkgName = pkgs[j].getName();
-                                       if (!exportedPackages.containsKey(pkgName)) {
-                                               exportedPackages.put(pkgName, new TreeSet<String>());
-                                       }
-                                       (exportedPackages.get(pkgName)).add(bundle.getSymbolicName() + "_" + bundle.getVersion());
-                               }
-               }
-               Map<String, Set<String>> duplicatePackages = new TreeMap<String, Set<String>>();
-               Iterator<String> it = exportedPackages.keySet().iterator();
-               while (it.hasNext()) {
-                       String pkgName = it.next().toString();
-                       Set<String> bdles = exportedPackages.get(pkgName);
-                       if (bdles.size() > 1)
-                               duplicatePackages.put(pkgName, bdles);
-               }
-               return duplicatePackages;
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootException.java
deleted file mode 100644 (file)
index fd6bf65..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.argeo.osgi.boot;
-
-/** OsgiBoot specific exceptions */
-class OsgiBootException extends RuntimeException {
-       private static final long serialVersionUID = 2414011711711425353L;
-
-       public OsgiBootException() {
-       }
-
-       public OsgiBootException(String message) {
-               super(message);
-       }
-
-       public OsgiBootException(String message, Throwable e) {
-               super(message, e);
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBootUtils.java
deleted file mode 100644 (file)
index c152ed7..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
-
-/** Utilities, mostly related to logging. */
-public class OsgiBootUtils {
-       /** ISO8601 (as per log4j) and difference to UTC */
-       private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS Z");
-
-       static boolean debug = System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG) == null ? false
-                       : !System.getProperty(OsgiBoot.PROP_ARGEO_OSGI_BOOT_DEBUG).trim().equals("false");
-
-       public static void info(Object obj) {
-               System.out.println("# OSGiBOOT      # " + dateFormat.format(new Date()) + " # " + obj);
-       }
-
-       public static void debug(Object obj) {
-               if (debug)
-                       System.out.println("# OSGiBOOT DBG  # " + dateFormat.format(new Date()) + " # " + obj);
-       }
-
-       public static void warn(Object obj) {
-               System.out.println("# OSGiBOOT WARN # " + dateFormat.format(new Date()) + " # " + obj);
-       }
-
-       public static void error(Object obj, Throwable e) {
-               System.err.println("# OSGiBOOT ERR  # " + dateFormat.format(new Date()) + " # " + obj);
-               if (e != null)
-                       e.printStackTrace();
-       }
-
-       public static boolean isDebug() {
-               return debug;
-       }
-
-       public static String stateAsString(int state) {
-               switch (state) {
-               case Bundle.UNINSTALLED:
-                       return "UNINSTALLED";
-               case Bundle.INSTALLED:
-                       return "INSTALLED";
-               case Bundle.RESOLVED:
-                       return "RESOLVED";
-               case Bundle.STARTING:
-                       return "STARTING";
-               case Bundle.ACTIVE:
-                       return "ACTIVE";
-               case Bundle.STOPPING:
-                       return "STOPPING";
-               default:
-                       return Integer.toString(state);
-               }
-       }
-
-       /**
-        * @return ==0: versions are identical, &lt;0: tested version is newer, &gt;0:
-        *         currentVersion is newer.
-        */
-       public static int compareVersions(String currentVersion, String testedVersion) {
-               List<String> cToks = new ArrayList<String>();
-               StringTokenizer cSt = new StringTokenizer(currentVersion, ".");
-               while (cSt.hasMoreTokens())
-                       cToks.add(cSt.nextToken());
-               List<String> tToks = new ArrayList<String>();
-               StringTokenizer tSt = new StringTokenizer(currentVersion, ".");
-               while (tSt.hasMoreTokens())
-                       tToks.add(tSt.nextToken());
-
-               int comp = 0;
-               comp: for (int i = 0; i < cToks.size(); i++) {
-                       if (tToks.size() <= i) {
-                               // equals until then, tested shorter
-                               comp = 1;
-                               break comp;
-                       }
-
-                       String c = (String) cToks.get(i);
-                       String t = (String) tToks.get(i);
-
-                       try {
-                               int cInt = Integer.parseInt(c);
-                               int tInt = Integer.parseInt(t);
-                               if (cInt == tInt)
-                                       continue comp;
-                               else {
-                                       comp = (cInt - tInt);
-                                       break comp;
-                               }
-                       } catch (NumberFormatException e) {
-                               if (c.equals(t))
-                                       continue comp;
-                               else {
-                                       comp = c.compareTo(t);
-                                       break comp;
-                               }
-                       }
-               }
-
-               if (comp == 0 && tToks.size() > cToks.size()) {
-                       // equals until then, current shorter
-                       comp = -1;
-               }
-
-               return comp;
-       }
-
-       /** Launch an OSGi framework. */
-       public static Framework launch(FrameworkFactory frameworkFactory, Map<String, String> configuration) {
-               // start OSGi
-               Framework framework = frameworkFactory.newFramework(configuration);
-               try {
-                       framework.start();
-               } catch (BundleException e) {
-                       throw new OsgiBootException("Cannot start OSGi framework", e);
-               }
-               return framework;
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBuilder.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/OsgiBuilder.java
deleted file mode 100644 (file)
index 8c460e1..0000000
+++ /dev/null
@@ -1,327 +0,0 @@
-package org.argeo.osgi.boot;
-
-import java.lang.reflect.Method;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.TreeMap;
-
-import org.eclipse.osgi.launch.EquinoxFactory;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleException;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.launch.Framework;
-import org.osgi.framework.launch.FrameworkFactory;
-import org.osgi.util.tracker.BundleTracker;
-import org.osgi.util.tracker.ServiceTracker;
-
-/** OSGi builder, focusing on ease of use for scripting. */
-public class OsgiBuilder {
-       private final static String PROP_HTTP_PORT = "org.osgi.service.http.port";
-       private final static String PROP_HTTPS_PORT = "org.osgi.service.https.port";
-       private final static String PROP_OSGI_CLEAN = "osgi.clean";
-
-       private Map<Integer, StartLevel> startLevels = new TreeMap<>();
-       private List<String> distributionBundles = new ArrayList<>();
-
-       private Map<String, String> configuration = new HashMap<String, String>();
-       private Framework framework;
-       private String baseUrl = null;
-
-       public OsgiBuilder() {
-               // configuration.put("osgi.clean", "true");
-               configuration.put(OsgiBoot.CONFIGURATION_AREA_PROP, System.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP));
-               configuration.put(OsgiBoot.INSTANCE_AREA_PROP, System.getProperty(OsgiBoot.INSTANCE_AREA_PROP));
-               configuration.put(PROP_OSGI_CLEAN, System.getProperty(PROP_OSGI_CLEAN));
-       }
-
-       public Framework launch() {
-               // start OSGi
-               FrameworkFactory frameworkFactory = new EquinoxFactory();
-               framework = frameworkFactory.newFramework(configuration);
-               try {
-                       framework.start();
-               } catch (BundleException e) {
-                       throw new OsgiBootException("Cannot start OSGi framework", e);
-               }
-
-               BundleContext bc = framework.getBundleContext();
-               String osgiData = bc.getProperty(OsgiBoot.INSTANCE_AREA_PROP);
-               // String osgiConf = bc.getProperty(OsgiBoot.CONFIGURATION_AREA_PROP);
-               String osgiConf = framework.getDataFile("").getAbsolutePath();
-               if (OsgiBootUtils.isDebug())
-                       OsgiBootUtils.debug("OSGi starting - data: " + osgiData + " conf: " + osgiConf);
-
-               OsgiBoot osgiBoot = new OsgiBoot(framework.getBundleContext());
-               if (distributionBundles.isEmpty()) {
-                       osgiBoot.getProvisioningManager().install(null);
-               } else {
-                       // install bundles
-                       for (String distributionBundle : distributionBundles) {
-                               List<String> bundleUrls = osgiBoot.getDistributionUrls(distributionBundle, baseUrl);
-                               osgiBoot.installUrls(bundleUrls);
-                       }
-               }
-               // start bundles
-               osgiBoot.startBundles(startLevelsToProperties());
-
-               // if (OsgiBootUtils.isDebug())
-               // for (Bundle bundle : bc.getBundles()) {
-               // OsgiBootUtils.debug(bundle.getLocation());
-               // }
-               return framework;
-       }
-
-       public OsgiBuilder conf(String key, String value) {
-               checkNotLaunched();
-               configuration.put(key, value);
-               return this;
-       }
-
-       public OsgiBuilder install(String uri) {
-               // TODO dynamic install
-               checkNotLaunched();
-               if (!distributionBundles.contains(uri))
-                       distributionBundles.add(uri);
-               return this;
-       }
-
-       public OsgiBuilder start(int startLevel, String bundle) {
-               // TODO dynamic start
-               checkNotLaunched();
-               StartLevel sl;
-               if (!startLevels.containsKey(startLevel))
-                       startLevels.put(startLevel, new StartLevel());
-               sl = startLevels.get(startLevel);
-               sl.add(bundle);
-               return this;
-       }
-
-       public OsgiBuilder waitForServlet(String base) {
-               service("(&(objectClass=javax.servlet.Servlet)(osgi.http.whiteboard.servlet.pattern=" + base + "))");
-               return this;
-       }
-
-       public OsgiBuilder waitForBundle(String bundles) {
-               List<String> lst = new ArrayList<>();
-               Collections.addAll(lst, bundles.split(","));
-               BundleTracker<Object> bt = new BundleTracker<Object>(getBc(), Bundle.ACTIVE, null) {
-
-                       @Override
-                       public Object addingBundle(Bundle bundle, BundleEvent event) {
-                               if (lst.contains(bundle.getSymbolicName())) {
-                                       return bundle.getSymbolicName();
-                               } else {
-                                       return null;
-                               }
-                       }
-               };
-               bt.open();
-               while (bt.getTrackingCount() != lst.size()) {
-                       try {
-                               Thread.sleep(500l);
-                       } catch (InterruptedException e) {
-                               break;
-                       }
-               }
-               bt.close();
-               return this;
-
-       }
-
-       public OsgiBuilder main(String clssUri, String[] args) {
-
-               // waitForBundle(bundleSymbolicName);
-               try {
-                       URI uri = new URI(clssUri);
-                       if (!"bundleclass".equals(uri.getScheme()))
-                               throw new IllegalArgumentException("Unsupported scheme for " + clssUri);
-                       String bundleSymbolicName = uri.getHost();
-                       String clss = uri.getPath().substring(1);
-                       Bundle bundle = null;
-                       for (Bundle b : getBc().getBundles()) {
-                               if (bundleSymbolicName.equals(b.getSymbolicName())) {
-                                       bundle = b;
-                                       break;
-                               }
-                       }
-                       if (bundle == null)
-                               throw new OsgiBootException("Bundle " + bundleSymbolicName + " not found");
-                       Class<?> c = bundle.loadClass(clss);
-                       Object[] mainArgs = { args };
-                       Method mainMethod = c.getMethod("main", String[].class);
-                       mainMethod.invoke(null, mainArgs);
-               } catch (Throwable e) {
-                       throw new OsgiBootException("Cannot execute " + clssUri, e);
-               }
-               return this;
-       }
-
-       public Object service(String service) {
-               return service(service, 0);
-       }
-
-       public Object service(String service, long timeout) {
-               ServiceTracker<Object, Object> st;
-               if (service.contains("(")) {
-                       try {
-                               st = new ServiceTracker<>(getBc(), FrameworkUtil.createFilter(service), null);
-                       } catch (InvalidSyntaxException e) {
-                               throw new IllegalArgumentException("Badly formatted filter", e);
-                       }
-               } else {
-                       st = new ServiceTracker<>(getBc(), service, null);
-               }
-               st.open();
-               try {
-                       return st.waitForService(timeout);
-               } catch (InterruptedException e) {
-                       OsgiBootUtils.error("Interrupted", e);
-                       return null;
-               } finally {
-                       st.close();
-               }
-
-       }
-
-       public void shutdown() {
-               checkLaunched();
-               try {
-                       framework.stop();
-               } catch (BundleException e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-               try {
-                       framework.waitForStop(10 * 60 * 1000);
-               } catch (InterruptedException e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-               System.exit(0);
-       }
-
-       public void setHttpPort(Integer port) {
-               checkNotLaunched();
-               configuration.put(PROP_HTTP_PORT, Integer.toString(port));
-       }
-
-       public void setHttpsPort(Integer port) {
-               checkNotLaunched();
-               configuration.put(PROP_HTTPS_PORT, Integer.toString(port));
-       }
-
-       public void setClean(boolean clean) {
-               checkNotLaunched();
-               configuration.put(PROP_OSGI_CLEAN, Boolean.toString(clean));
-       }
-
-       public Integer getHttpPort() {
-               if (!isLaunched()) {
-                       if (configuration.containsKey(PROP_HTTP_PORT))
-                               return Integer.parseInt(configuration.get(PROP_HTTP_PORT));
-                       else
-                               return -1;
-               } else {
-                       // TODO wait for service?
-                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
-                       if (sr == null)
-                               return -1;
-                       Object port = sr.getProperty("http.port");
-                       if (port == null)
-                               return -1;
-                       return Integer.parseInt(port.toString());
-               }
-       }
-
-       public Integer getHttpsPort() {
-               if (!isLaunched()) {
-                       if (configuration.containsKey(PROP_HTTPS_PORT))
-                               return Integer.parseInt(configuration.get(PROP_HTTPS_PORT));
-                       else
-                               return -1;
-               } else {
-                       // TODO wait for service?
-                       ServiceReference<?> sr = getBc().getServiceReference("org.osgi.service.http.HttpService");
-                       if (sr == null)
-                               return -1;
-                       Object port = sr.getProperty("https.port");
-                       if (port == null)
-                               return -1;
-                       return Integer.parseInt(port.toString());
-               }
-       }
-
-       public Object spring(String bundle) {
-               return service("(&(Bundle-SymbolicName=" + bundle + ")"
-                               + "(objectClass=org.springframework.context.ApplicationContext))");
-       }
-
-       //
-       // BEAN
-       //
-
-       public BundleContext getBc() {
-               checkLaunched();
-               return framework.getBundleContext();
-       }
-
-       public void setBaseUrl(String baseUrl) {
-               this.baseUrl = baseUrl;
-       }
-
-       //
-       // UTILITIES
-       //
-       private Properties startLevelsToProperties() {
-               Properties properties = new Properties();
-               for (Integer startLevel : startLevels.keySet()) {
-                       String property = OsgiBoot.PROP_ARGEO_OSGI_START + "." + startLevel;
-                       StringBuilder value = new StringBuilder();
-                       for (String bundle : startLevels.get(startLevel).getBundles()) {
-                               value.append(bundle);
-                               value.append(',');
-                       }
-                       // TODO remove trailing comma
-                       properties.put(property, value.toString());
-               }
-               return properties;
-       }
-
-       private void checkLaunched() {
-               if (!isLaunched())
-                       throw new OsgiBootException("OSGi runtime is not launched");
-       }
-
-       private void checkNotLaunched() {
-               if (isLaunched())
-                       throw new OsgiBootException("OSGi runtime already launched");
-       }
-
-       private boolean isLaunched() {
-               return framework != null;
-       }
-
-       private static class StartLevel {
-               private Set<String> bundles = new HashSet<>();
-
-               public void add(String bundle) {
-                       String[] b = bundle.split(",");
-                       Collections.addAll(bundles, b);
-               }
-
-               public Set<String> getBundles() {
-                       return bundles;
-               }
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/EquinoxUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/EquinoxUtils.java
deleted file mode 100644 (file)
index 55cd067..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.argeo.osgi.boot.equinox;
-
-import java.util.Map;
-
-import org.argeo.osgi.boot.OsgiBootUtils;
-import org.eclipse.osgi.launch.EquinoxFactory;
-import org.osgi.framework.launch.Framework;
-
-/**
- * Utilities with a dependency to the Equinox OSGi runtime or its configuration.
- */
-public class EquinoxUtils {
-
-       public static Framework launch(Map<String, String> configuration) {
-               return OsgiBootUtils.launch(new EquinoxFactory(), configuration);
-       }
-
-       /** Singleton. */
-       private EquinoxUtils() {
-
-       }
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/package-info.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/equinox/package-info.java
deleted file mode 100644 (file)
index b503752..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Simple Eclipse Equinox initialisation. */
-package org.argeo.osgi.boot.equinox;
\ No newline at end of file
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/AntPathMatcher.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/AntPathMatcher.java
deleted file mode 100644 (file)
index e3fc6c2..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-/*\r
- * Copyright 2002-2007 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-/**\r
- * PathMatcher implementation for Ant-style path patterns.\r
- * Examples are provided below.\r
- *\r
- * <p>Part of this mapping code has been kindly borrowed from\r
- * <a href="http://ant.apache.org">Apache Ant</a>.\r
- *\r
- * <p>The mapping matches URLs using the following rules:<br>\r
- * <ul>\r
- * <li>? matches one character</li>\r
- * <li>* matches zero or more characters</li>\r
- * <li>** matches zero or more 'directories' in a path</li>\r
- * </ul>\r
- *\r
- * <p>Some examples:<br>\r
- * <ul>\r
- * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also\r
- * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>\r
- * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the\r
- * <code>com</code> directory</li>\r
- * <li><code>com/&#42;&#42;/test.jsp</code> - matches all <code>test.jsp</code>\r
- * files underneath the <code>com</code> path</li>\r
- * <li><code>org/springframework/&#42;&#42;/*.jsp</code> - matches all <code>.jsp</code>\r
- * files underneath the <code>org/springframework</code> path</li>\r
- * <li><code>org/&#42;&#42;/servlet/bla.jsp</code> - matches\r
- * <code>org/springframework/servlet/bla.jsp</code> but also\r
- * <code>org/springframework/testing/servlet/bla.jsp</code> and\r
- * <code>org/servlet/bla.jsp</code></li>\r
- * </ul>\r
- *\r
- * @author Alef Arendsen\r
- * @author Juergen Hoeller\r
- * @author Rob Harrop\r
- * @since 16.07.2003\r
- */\r
-public class AntPathMatcher implements PathMatcher {\r
-\r
-       /** Default path separator: "/" */\r
-       public static final String DEFAULT_PATH_SEPARATOR = "/";\r
-\r
-       private String pathSeparator = DEFAULT_PATH_SEPARATOR;\r
-\r
-\r
-       /**\r
-        * Set the path separator to use for pattern parsing.\r
-        * Default is "/", as in Ant.\r
-        */\r
-       public void setPathSeparator(String pathSeparator) {\r
-               this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);\r
-       }\r
-\r
-\r
-       public boolean isPattern(String path) {\r
-               return (path.indexOf('*') != -1 || path.indexOf('?') != -1);\r
-       }\r
-\r
-       public boolean match(String pattern, String path) {\r
-               return doMatch(pattern, path, true);\r
-       }\r
-\r
-       public boolean matchStart(String pattern, String path) {\r
-               return doMatch(pattern, path, false);\r
-       }\r
-\r
-\r
-       /**\r
-        * Actually match the given <code>path</code> against the given <code>pattern</code>.\r
-        * @param pattern the pattern to match against\r
-        * @param path the path String to test\r
-        * @param fullMatch whether a full pattern match is required\r
-        * (else a pattern match as far as the given base path goes is sufficient)\r
-        * @return <code>true</code> if the supplied <code>path</code> matched,\r
-        * <code>false</code> if it didn't\r
-        */\r
-       protected boolean doMatch(String pattern, String path, boolean fullMatch) {\r
-               if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {\r
-                       return false;\r
-               }\r
-\r
-               String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);\r
-               String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);\r
-\r
-               int pattIdxStart = 0;\r
-               int pattIdxEnd = pattDirs.length - 1;\r
-               int pathIdxStart = 0;\r
-               int pathIdxEnd = pathDirs.length - 1;\r
-\r
-               // Match all elements up to the first **\r
-               while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {\r
-                       String patDir = pattDirs[pattIdxStart];\r
-                       if ("**".equals(patDir)) {\r
-                               break;\r
-                       }\r
-                       if (!matchStrings(patDir, pathDirs[pathIdxStart])) {\r
-                               return false;\r
-                       }\r
-                       pattIdxStart++;\r
-                       pathIdxStart++;\r
-               }\r
-\r
-               if (pathIdxStart > pathIdxEnd) {\r
-                       // Path is exhausted, only match if rest of pattern is * or **'s\r
-                       if (pattIdxStart > pattIdxEnd) {\r
-                               return (pattern.endsWith(this.pathSeparator) ?\r
-                                               path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));\r
-                       }\r
-                       if (!fullMatch) {\r
-                               return true;\r
-                       }\r
-                       if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") &&\r
-                                       path.endsWith(this.pathSeparator)) {\r
-                               return true;\r
-                       }\r
-                       for (int i = pattIdxStart; i <= pattIdxEnd; i++) {\r
-                               if (!pattDirs[i].equals("**")) {\r
-                                       return false;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-               else if (pattIdxStart > pattIdxEnd) {\r
-                       // String not exhausted, but pattern is. Failure.\r
-                       return false;\r
-               }\r
-               else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {\r
-                       // Path start definitely matches due to "**" part in pattern.\r
-                       return true;\r
-               }\r
-\r
-               // up to last '**'\r
-               while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {\r
-                       String patDir = pattDirs[pattIdxEnd];\r
-                       if (patDir.equals("**")) {\r
-                               break;\r
-                       }\r
-                       if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {\r
-                               return false;\r
-                       }\r
-                       pattIdxEnd--;\r
-                       pathIdxEnd--;\r
-               }\r
-               if (pathIdxStart > pathIdxEnd) {\r
-                       // String is exhausted\r
-                       for (int i = pattIdxStart; i <= pattIdxEnd; i++) {\r
-                               if (!pattDirs[i].equals("**")) {\r
-                                       return false;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-\r
-               while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {\r
-                       int patIdxTmp = -1;\r
-                       for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {\r
-                               if (pattDirs[i].equals("**")) {\r
-                                       patIdxTmp = i;\r
-                                       break;\r
-                               }\r
-                       }\r
-                       if (patIdxTmp == pattIdxStart + 1) {\r
-                               // '**/**' situation, so skip one\r
-                               pattIdxStart++;\r
-                               continue;\r
-                       }\r
-                       // Find the pattern between padIdxStart & padIdxTmp in str between\r
-                       // strIdxStart & strIdxEnd\r
-                       int patLength = (patIdxTmp - pattIdxStart - 1);\r
-                       int strLength = (pathIdxEnd - pathIdxStart + 1);\r
-                       int foundIdx = -1;\r
-\r
-                       strLoop:\r
-                           for (int i = 0; i <= strLength - patLength; i++) {\r
-                                   for (int j = 0; j < patLength; j++) {\r
-                                           String subPat = (String) pattDirs[pattIdxStart + j + 1];\r
-                                           String subStr = (String) pathDirs[pathIdxStart + i + j];\r
-                                           if (!matchStrings(subPat, subStr)) {\r
-                                                   continue strLoop;\r
-                                           }\r
-                                   }\r
-                                   foundIdx = pathIdxStart + i;\r
-                                   break;\r
-                           }\r
-\r
-                       if (foundIdx == -1) {\r
-                               return false;\r
-                       }\r
-\r
-                       pattIdxStart = patIdxTmp;\r
-                       pathIdxStart = foundIdx + patLength;\r
-               }\r
-\r
-               for (int i = pattIdxStart; i <= pattIdxEnd; i++) {\r
-                       if (!pattDirs[i].equals("**")) {\r
-                               return false;\r
-                       }\r
-               }\r
-\r
-               return true;\r
-       }\r
-\r
-       /**\r
-        * Tests whether or not a string matches against a pattern.\r
-        * The pattern may contain two special characters:<br>\r
-        * '*' means zero or more characters<br>\r
-        * '?' means one and only one character\r
-        * @param pattern pattern to match against.\r
-        * Must not be <code>null</code>.\r
-        * @param str string which must be matched against the pattern.\r
-        * Must not be <code>null</code>.\r
-        * @return <code>true</code> if the string matches against the\r
-        * pattern, or <code>false</code> otherwise.\r
-        */\r
-       private boolean matchStrings(String pattern, String str) {\r
-               char[] patArr = pattern.toCharArray();\r
-               char[] strArr = str.toCharArray();\r
-               int patIdxStart = 0;\r
-               int patIdxEnd = patArr.length - 1;\r
-               int strIdxStart = 0;\r
-               int strIdxEnd = strArr.length - 1;\r
-               char ch;\r
-\r
-               boolean containsStar = false;\r
-               for (int i = 0; i < patArr.length; i++) {\r
-                       if (patArr[i] == '*') {\r
-                               containsStar = true;\r
-                               break;\r
-                       }\r
-               }\r
-\r
-               if (!containsStar) {\r
-                       // No '*'s, so we make a shortcut\r
-                       if (patIdxEnd != strIdxEnd) {\r
-                               return false; // Pattern and string do not have the same size\r
-                       }\r
-                       for (int i = 0; i <= patIdxEnd; i++) {\r
-                               ch = patArr[i];\r
-                               if (ch != '?') {\r
-                                       if (ch != strArr[i]) {\r
-                                               return false;// Character mismatch\r
-                                       }\r
-                               }\r
-                       }\r
-                       return true; // String matches against pattern\r
-               }\r
-\r
-\r
-               if (patIdxEnd == 0) {\r
-                       return true; // Pattern contains only '*', which matches anything\r
-               }\r
-\r
-               // Process characters before first star\r
-               while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {\r
-                       if (ch != '?') {\r
-                               if (ch != strArr[strIdxStart]) {\r
-                                       return false;// Character mismatch\r
-                               }\r
-                       }\r
-                       patIdxStart++;\r
-                       strIdxStart++;\r
-               }\r
-               if (strIdxStart > strIdxEnd) {\r
-                       // All characters in the string are used. Check if only '*'s are\r
-                       // left in the pattern. If so, we succeeded. Otherwise failure.\r
-                       for (int i = patIdxStart; i <= patIdxEnd; i++) {\r
-                               if (patArr[i] != '*') {\r
-                                       return false;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-\r
-               // Process characters after last star\r
-               while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {\r
-                       if (ch != '?') {\r
-                               if (ch != strArr[strIdxEnd]) {\r
-                                       return false;// Character mismatch\r
-                               }\r
-                       }\r
-                       patIdxEnd--;\r
-                       strIdxEnd--;\r
-               }\r
-               if (strIdxStart > strIdxEnd) {\r
-                       // All characters in the string are used. Check if only '*'s are\r
-                       // left in the pattern. If so, we succeeded. Otherwise failure.\r
-                       for (int i = patIdxStart; i <= patIdxEnd; i++) {\r
-                               if (patArr[i] != '*') {\r
-                                       return false;\r
-                               }\r
-                       }\r
-                       return true;\r
-               }\r
-\r
-               // process pattern between stars. padIdxStart and patIdxEnd point\r
-               // always to a '*'.\r
-               while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {\r
-                       int patIdxTmp = -1;\r
-                       for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {\r
-                               if (patArr[i] == '*') {\r
-                                       patIdxTmp = i;\r
-                                       break;\r
-                               }\r
-                       }\r
-                       if (patIdxTmp == patIdxStart + 1) {\r
-                               // Two stars next to each other, skip the first one.\r
-                               patIdxStart++;\r
-                               continue;\r
-                       }\r
-                       // Find the pattern between padIdxStart & padIdxTmp in str between\r
-                       // strIdxStart & strIdxEnd\r
-                       int patLength = (patIdxTmp - patIdxStart - 1);\r
-                       int strLength = (strIdxEnd - strIdxStart + 1);\r
-                       int foundIdx = -1;\r
-                       strLoop:\r
-                       for (int i = 0; i <= strLength - patLength; i++) {\r
-                               for (int j = 0; j < patLength; j++) {\r
-                                       ch = patArr[patIdxStart + j + 1];\r
-                                       if (ch != '?') {\r
-                                               if (ch != strArr[strIdxStart + i + j]) {\r
-                                                       continue strLoop;\r
-                                               }\r
-                                       }\r
-                               }\r
-\r
-                               foundIdx = strIdxStart + i;\r
-                               break;\r
-                       }\r
-\r
-                       if (foundIdx == -1) {\r
-                               return false;\r
-                       }\r
-\r
-                       patIdxStart = patIdxTmp;\r
-                       strIdxStart = foundIdx + patLength;\r
-               }\r
-\r
-               // All characters in the string are used. Check if only '*'s are left\r
-               // in the pattern. If so, we succeeded. Otherwise failure.\r
-               for (int i = patIdxStart; i <= patIdxEnd; i++) {\r
-                       if (patArr[i] != '*') {\r
-                               return false;\r
-                       }\r
-               }\r
-\r
-               return true;\r
-       }\r
-\r
-       /**\r
-        * Given a pattern and a full path, determine the pattern-mapped part.\r
-        * <p>For example:\r
-        * <ul>\r
-        * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> to ''</li>\r
-        * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> to '<code>cvs/commit</code>'</li>\r
-        * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> to '<code>commit.html</code>'</li>\r
-        * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> to '<code>cvs/commit</code>'</li>\r
-        * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> to '<code>cvs/commit.html</code>'</li>\r
-        * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> to '<code>docs/cvs/commit.html</code>'</li>\r
-        * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> to '<code>/docs/cvs/commit.html</code>'</li>\r
-        * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> to '<code>/docs/cvs/commit.html</code>'</li>\r
-        * </ul>\r
-        * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'\r
-        * and '<code>path</code>', but does <strong>not</strong> enforce this.\r
-        */\r
-       public String extractPathWithinPattern(String pattern, String path) {\r
-               String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);\r
-               String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);\r
-\r
-               StringBuffer buffer = new StringBuffer();\r
-\r
-               // Add any path parts that have a wildcarded pattern part.\r
-               int puts = 0;\r
-               for (int i = 0; i < patternParts.length; i++) {\r
-                       String patternPart = patternParts[i];\r
-                       if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {\r
-                               if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {\r
-                                       buffer.append(this.pathSeparator);\r
-                               }\r
-                               buffer.append(pathParts[i]);\r
-                               puts++;\r
-                       }\r
-               }\r
-\r
-               // Append any trailing path parts.\r
-               for (int i = patternParts.length; i < pathParts.length; i++) {\r
-                       if (puts > 0 || i > 0) {\r
-                               buffer.append(this.pathSeparator);\r
-                       }\r
-                       buffer.append(pathParts[i]);\r
-               }\r
-\r
-               return buffer.toString();\r
-       }\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java
deleted file mode 100644 (file)
index 2ac4562..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-package org.argeo.osgi.boot.internal.springutil;
-
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-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.List;
-import java.util.Map;
-
-public class Bootstrap {
-
-       public static void main(String[] args) {
-               try {
-                       String configurationArea = "file:" + System.getProperty("user.dir") + "/state";
-                       String instanceArea = "file:" + System.getProperty("user.dir") + "/data";
-                       String log4jUrl = "file:" + System.getProperty("user.dir") + "/log4j.properties";
-
-                       System.setProperty("org.osgi.service.http.port", "7070");
-                       System.setProperty("log4j.configuration", log4jUrl);
-
-                       System.setProperty("osgi.console", "2323");
-                       Map<String, String> props = new HashMap<String, String>();
-                       props.put("osgi.clean", "true");
-//                     props.put("osgi.console", "2323");
-                       props.put("osgi.configuration.area", configurationArea);
-                       props.put("osgi.instance.area", instanceArea);
-
-                       System.setProperty("argeo.osgi.start.2.node",
-                                       "org.eclipse.equinox.console,org.eclipse.equinox.http.servlet,org.eclipse.equinox.ds,"
-                                                       + "org.eclipse.equinox.metatype,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi");
-                       System.setProperty("argeo.osgi.start.3.node", "org.argeo.cms");
-
-                       // URL osgiJar =
-                       // Bootstrap.class.getClassLoader().getResource("/usr/share/osgi/boot/org.eclipse.org.jar");
-                       URL osgiJar = new URL(
-                                       "file:///home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/org.eclipse.org.jar");
-                       URL osgiBootJar = new URL(
-                                       "file:///home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/org.argeo.osgi.boot.jar");
-                       URL[] jarUrls = { osgiJar };
-                       try (URLClassLoader urlCl = new URLClassLoader(jarUrls)) {
-
-                               // Class<?> factoryClass =
-                               // urlCl.loadClass("/org/eclipse/osgi/launch/EquinoxFactory");
-                               Class<?> factoryClass = urlCl.loadClass("org.eclipse.osgi.launch.EquinoxFactory");
-                               Class<?> frameworkClass = urlCl.loadClass("org.osgi.framework.launch.Framework");
-                               Class<?> bundleContextClass = urlCl.loadClass("org.osgi.framework.BundleContext");
-                               Class<?> bundleClass = urlCl.loadClass("org.osgi.framework.Bundle");
-
-                               Object factory = factoryClass.getConstructor().newInstance();
-                               Method newFrameworkMethod = factoryClass.getMethod("newFramework", Map.class);
-                               Object framework = newFrameworkMethod.invoke(factory, props);
-                               Method startFramework = frameworkClass.getMethod("start", new Class[] {});
-                               startFramework.invoke(framework);
-                               Method getBundleContext = frameworkClass.getMethod("getBundleContext", new Class[] {});
-                               Object bundleContext = getBundleContext.invoke(framework);
-                               Class<?>[] installArgs = { String.class, InputStream.class };
-                               Method install = bundleContextClass.getMethod("installBundle", installArgs);
-                               Method startBundle = bundleClass.getMethod("start");
-                               Method getSymbolicName = bundleClass.getMethod("getSymbolicName");
-
-                               Path basePath = Paths.get(
-                                               "/home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/");
-                               List<Object> bundles = new ArrayList<>();
-                               for (Path p : Files.newDirectoryStream(basePath)) {
-                                       try (InputStream in = Files.newInputStream(p)) {
-                                               Object bundle = install.invoke(bundleContext, "file:" + p, in);
-                                               bundles.add(bundle);
-                                               System.out.println("Installed " + bundle);
-                                       } catch (Exception e) {
-                                               if (!p.getFileName().toString().startsWith("org.eclipse.osgi")) {
-                                                       System.err.println(p);
-                                                       e.printStackTrace();
-                                               }
-                                       }
-                               }
-
-//                             for (Object bundle : bundles) {
-//                                     try {
-//                                             String symbolicName = getSymbolicName.invoke(bundle).toString();
-//                                             startBundle.invoke(bundle);
-//                                     } catch (Exception e) {
-//                                             // TODO Auto-generated catch block
-//                                             e.printStackTrace();
-//                                     }
-//                             }
-
-                               Object osgiBootBundle = install.invoke(bundleContext, osgiBootJar.toString(), osgiBootJar.openStream());
-                               startBundle.invoke(osgiBootBundle);
-                       }
-               } catch (Exception e) {
-                       e.printStackTrace();
-               }
-
-       }
-
-}
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/CollectionUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/CollectionUtils.java
deleted file mode 100644 (file)
index 18cbe16..0000000
+++ /dev/null
@@ -1,276 +0,0 @@
-/*\r
- * Copyright 2002-2008 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Enumeration;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Properties;\r
-\r
-/**\r
- * Miscellaneous collection utility methods.\r
- * Mainly for internal use within the framework.\r
- *\r
- * @author Juergen Hoeller\r
- * @author Rob Harrop\r
- * @since 1.1.3\r
- */\r
-@SuppressWarnings({ "rawtypes", "unchecked" })\r
-public abstract class CollectionUtils {\r
-\r
-       /**\r
-        * Return <code>true</code> if the supplied Collection is <code>null</code>\r
-        * or empty. Otherwise, return <code>false</code>.\r
-        * @param collection the Collection to check\r
-        * @return whether the given Collection is empty\r
-        */\r
-       public static boolean isEmpty(Collection collection) {\r
-               return (collection == null || collection.isEmpty());\r
-       }\r
-\r
-       /**\r
-        * Return <code>true</code> if the supplied Map is <code>null</code>\r
-        * or empty. Otherwise, return <code>false</code>.\r
-        * @param map the Map to check\r
-        * @return whether the given Map is empty\r
-        */\r
-       public static boolean isEmpty(Map map) {\r
-               return (map == null || map.isEmpty());\r
-       }\r
-\r
-       /**\r
-        * Convert the supplied array into a List. A primitive array gets\r
-        * converted into a List of the appropriate wrapper type.\r
-        * <p>A <code>null</code> source value will be converted to an\r
-        * empty List.\r
-        * @param source the (potentially primitive) array\r
-        * @return the converted List result\r
-        * @see ObjectUtils#toObjectArray(Object)\r
-        */\r
-       public static List arrayToList(Object source) {\r
-               return Arrays.asList(ObjectUtils.toObjectArray(source));\r
-       }\r
-\r
-       /**\r
-        * Merge the given array into the given Collection.\r
-        * @param array the array to merge (may be <code>null</code>)\r
-        * @param collection the target Collection to merge the array into\r
-        */\r
-       public static void mergeArrayIntoCollection(Object array, Collection collection) {\r
-               if (collection == null) {\r
-                       throw new IllegalArgumentException("Collection must not be null");\r
-               }\r
-               Object[] arr = ObjectUtils.toObjectArray(array);\r
-               for (int i = 0; i < arr.length; i++) {\r
-                       collection.add(arr[i]);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Merge the given Properties instance into the given Map,\r
-        * copying all properties (key-value pairs) over.\r
-        * <p>Uses <code>Properties.propertyNames()</code> to even catch\r
-        * default properties linked into the original Properties instance.\r
-        * @param props the Properties instance to merge (may be <code>null</code>)\r
-        * @param map the target Map to merge the properties into\r
-        */\r
-       public static void mergePropertiesIntoMap(Properties props, Map map) {\r
-               if (map == null) {\r
-                       throw new IllegalArgumentException("Map must not be null");\r
-               }\r
-               if (props != null) {\r
-                       for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {\r
-                               String key = (String) en.nextElement();\r
-                               map.put(key, props.getProperty(key));\r
-                       }\r
-               }\r
-       }\r
-\r
-\r
-       /**\r
-        * Check whether the given Iterator contains the given element.\r
-        * @param iterator the Iterator to check\r
-        * @param element the element to look for\r
-        * @return <code>true</code> if found, <code>false</code> else\r
-        */\r
-       public static boolean contains(Iterator iterator, Object element) {\r
-               if (iterator != null) {\r
-                       while (iterator.hasNext()) {\r
-                               Object candidate = iterator.next();\r
-                               if (ObjectUtils.nullSafeEquals(candidate, element)) {\r
-                                       return true;\r
-                               }\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Check whether the given Enumeration contains the given element.\r
-        * @param enumeration the Enumeration to check\r
-        * @param element the element to look for\r
-        * @return <code>true</code> if found, <code>false</code> else\r
-        */\r
-       public static boolean contains(Enumeration enumeration, Object element) {\r
-               if (enumeration != null) {\r
-                       while (enumeration.hasMoreElements()) {\r
-                               Object candidate = enumeration.nextElement();\r
-                               if (ObjectUtils.nullSafeEquals(candidate, element)) {\r
-                                       return true;\r
-                               }\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Check whether the given Collection contains the given element instance.\r
-        * <p>Enforces the given instance to be present, rather than returning\r
-        * <code>true</code> for an equal element as well.\r
-        * @param collection the Collection to check\r
-        * @param element the element to look for\r
-        * @return <code>true</code> if found, <code>false</code> else\r
-        */\r
-       public static boolean containsInstance(Collection collection, Object element) {\r
-               if (collection != null) {\r
-                       for (Iterator it = collection.iterator(); it.hasNext();) {\r
-                               Object candidate = it.next();\r
-                               if (candidate == element) {\r
-                                       return true;\r
-                               }\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Return <code>true</code> if any element in '<code>candidates</code>' is\r
-        * contained in '<code>source</code>'; otherwise returns <code>false</code>.\r
-        * @param source the source Collection\r
-        * @param candidates the candidates to search for\r
-        * @return whether any of the candidates has been found\r
-        */\r
-       public static boolean containsAny(Collection source, Collection candidates) {\r
-               if (isEmpty(source) || isEmpty(candidates)) {\r
-                       return false;\r
-               }\r
-               for (Iterator it = candidates.iterator(); it.hasNext();) {\r
-                       if (source.contains(it.next())) {\r
-                               return true;\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Return the first element in '<code>candidates</code>' that is contained in\r
-        * '<code>source</code>'. If no element in '<code>candidates</code>' is present in\r
-        * '<code>source</code>' returns <code>null</code>. Iteration order is\r
-        * {@link Collection} implementation specific.\r
-        * @param source the source Collection\r
-        * @param candidates the candidates to search for\r
-        * @return the first present object, or <code>null</code> if not found\r
-        */\r
-       public static Object findFirstMatch(Collection source, Collection candidates) {\r
-               if (isEmpty(source) || isEmpty(candidates)) {\r
-                       return null;\r
-               }\r
-               for (Iterator it = candidates.iterator(); it.hasNext();) {\r
-                       Object candidate = it.next();\r
-                       if (source.contains(candidate)) {\r
-                               return candidate;\r
-                       }\r
-               }\r
-               return null;\r
-       }\r
-\r
-       /**\r
-        * Find a single value of the given type in the given Collection.\r
-        * @param collection the Collection to search\r
-        * @param type the type to look for\r
-        * @return a value of the given type found if there is a clear match,\r
-        * or <code>null</code> if none or more than one such value found\r
-        */\r
-       public static Object findValueOfType(Collection collection, Class type) {\r
-               if (isEmpty(collection)) {\r
-                       return null;\r
-               }\r
-               Object value = null;\r
-               for (Iterator it = collection.iterator(); it.hasNext();) {\r
-                       Object obj = it.next();\r
-                       if (type == null || type.isInstance(obj)) {\r
-                               if (value != null) {\r
-                                       // More than one value found... no clear single value.\r
-                                       return null;\r
-                               }\r
-                               value = obj;\r
-                       }\r
-               }\r
-               return value;\r
-       }\r
-\r
-       /**\r
-        * Find a single value of one of the given types in the given Collection:\r
-        * searching the Collection for a value of the first type, then\r
-        * searching for a value of the second type, etc.\r
-        * @param collection the collection to search\r
-        * @param types the types to look for, in prioritized order\r
-        * @return a value of one of the given types found if there is a clear match,\r
-        * or <code>null</code> if none or more than one such value found\r
-        */\r
-       public static Object findValueOfType(Collection collection, Class[] types) {\r
-               if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {\r
-                       return null;\r
-               }\r
-               for (int i = 0; i < types.length; i++) {\r
-                       Object value = findValueOfType(collection, types[i]);\r
-                       if (value != null) {\r
-                               return value;\r
-                       }\r
-               }\r
-               return null;\r
-       }\r
-\r
-       /**\r
-        * Determine whether the given Collection only contains a single unique object.\r
-        * @param collection the Collection to check\r
-        * @return <code>true</code> if the collection contains a single reference or\r
-        * multiple references to the same instance, <code>false</code> else\r
-        */\r
-       public static boolean hasUniqueObject(Collection collection) {\r
-               if (isEmpty(collection)) {\r
-                       return false;\r
-               }\r
-               boolean hasCandidate = false;\r
-               Object candidate = null;\r
-               for (Iterator it = collection.iterator(); it.hasNext();) {\r
-                       Object elem = it.next();\r
-                       if (!hasCandidate) {\r
-                               hasCandidate = true;\r
-                               candidate = elem;\r
-                       }\r
-                       else if (candidate != elem) {\r
-                               return false;\r
-                       }\r
-               }\r
-               return true;\r
-       }\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/ObjectUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/ObjectUtils.java
deleted file mode 100644 (file)
index 2c98b46..0000000
+++ /dev/null
@@ -1,833 +0,0 @@
-/*\r
- * Copyright 2002-2007 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-import java.lang.reflect.Array;\r
-import java.util.Arrays;\r
-\r
-/**\r
- * Miscellaneous object utility methods. Mainly for internal use within the\r
- * framework; consider Jakarta's Commons Lang for a more comprehensive suite\r
- * of object utilities.\r
- *\r
- * @author Juergen Hoeller\r
- * @author Keith Donald\r
- * @author Rod Johnson\r
- * @author Rob Harrop\r
- * @author Alex Ruiz\r
- * @since 19.03.2004\r
- */\r
-@SuppressWarnings({ "rawtypes", "unchecked" })\r
-public abstract class ObjectUtils {\r
-\r
-       private static final int INITIAL_HASH = 7;\r
-       private static final int MULTIPLIER = 31;\r
-\r
-       private static final String EMPTY_STRING = "";\r
-       private static final String NULL_STRING = "null";\r
-       private static final String ARRAY_START = "{";\r
-       private static final String ARRAY_END = "}";\r
-       private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;\r
-       private static final String ARRAY_ELEMENT_SEPARATOR = ", ";\r
-\r
-\r
-       /**\r
-        * Return whether the given throwable is a checked exception:\r
-        * that is, neither a RuntimeException nor an Error.\r
-        * @param ex the throwable to check\r
-        * @return whether the throwable is a checked exception\r
-        * @see java.lang.Exception\r
-        * @see java.lang.RuntimeException\r
-        * @see java.lang.Error\r
-        */\r
-       public static boolean isCheckedException(Throwable ex) {\r
-               return !(ex instanceof RuntimeException || ex instanceof Error);\r
-       }\r
-\r
-       /**\r
-        * Check whether the given exception is compatible with the exceptions\r
-        * declared in a throws clause.\r
-        * @param ex the exception to checked\r
-        * @param declaredExceptions the exceptions declared in the throws clause\r
-        * @return whether the given exception is compatible\r
-        */\r
-       public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) {\r
-               if (!isCheckedException(ex)) {\r
-                       return true;\r
-               }\r
-               if (declaredExceptions != null) {\r
-                       for (int i = 0; i < declaredExceptions.length; i++) {\r
-                               if (declaredExceptions[i].isAssignableFrom(ex.getClass())) {\r
-                                       return true;\r
-                               }\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Return whether the given array is empty: that is, <code>null</code>\r
-        * or of zero length.\r
-        * @param array the array to check\r
-        * @return whether the given array is empty\r
-        */\r
-       public static boolean isEmpty(Object[] array) {\r
-               return (array == null || array.length == 0);\r
-       }\r
-\r
-       /**\r
-        * Check whether the given array contains the given element.\r
-        * @param array the array to check (may be <code>null</code>,\r
-        * in which case the return value will always be <code>false</code>)\r
-        * @param element the element to check for\r
-        * @return whether the element has been found in the given array\r
-        */\r
-       public static boolean containsElement(Object[] array, Object element) {\r
-               if (array == null) {\r
-                       return false;\r
-               }\r
-               for (int i = 0; i < array.length; i++) {\r
-                       if (nullSafeEquals(array[i], element)) {\r
-                               return true;\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Append the given Object to the given array, returning a new array\r
-        * consisting of the input array contents plus the given Object.\r
-        * @param array the array to append to (can be <code>null</code>)\r
-        * @param obj the Object to append\r
-        * @return the new array (of the same component type; never <code>null</code>)\r
-        */\r
-       public static Object[] addObjectToArray(Object[] array, Object obj) {\r
-               Class compType = Object.class;\r
-               if (array != null) {\r
-                       compType = array.getClass().getComponentType();\r
-               }\r
-               else if (obj != null) {\r
-                       compType = obj.getClass();\r
-               }\r
-               int newArrLength = (array != null ? array.length + 1 : 1);\r
-               Object[] newArr = (Object[]) Array.newInstance(compType, newArrLength);\r
-               if (array != null) {\r
-                       System.arraycopy(array, 0, newArr, 0, array.length);\r
-               }\r
-               newArr[newArr.length - 1] = obj;\r
-               return newArr;\r
-       }\r
-\r
-       /**\r
-        * Convert the given array (which may be a primitive array) to an\r
-        * object array (if necessary of primitive wrapper objects).\r
-        * <p>A <code>null</code> source value will be converted to an\r
-        * empty Object array.\r
-        * @param source the (potentially primitive) array\r
-        * @return the corresponding object array (never <code>null</code>)\r
-        * @throws IllegalArgumentException if the parameter is not an array\r
-        */\r
-       public static Object[] toObjectArray(Object source) {\r
-               if (source instanceof Object[]) {\r
-                       return (Object[]) source;\r
-               }\r
-               if (source == null) {\r
-                       return new Object[0];\r
-               }\r
-               if (!source.getClass().isArray()) {\r
-                       throw new IllegalArgumentException("Source is not an array: " + source);\r
-               }\r
-               int length = Array.getLength(source);\r
-               if (length == 0) {\r
-                       return new Object[0];\r
-               }\r
-               Class wrapperType = Array.get(source, 0).getClass();\r
-               Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);\r
-               for (int i = 0; i < length; i++) {\r
-                       newArray[i] = Array.get(source, i);\r
-               }\r
-               return newArray;\r
-       }\r
-\r
-\r
-       //---------------------------------------------------------------------\r
-       // Convenience methods for content-based equality/hash-code handling\r
-       //---------------------------------------------------------------------\r
-\r
-       /**\r
-        * Determine if the given objects are equal, returning <code>true</code>\r
-        * if both are <code>null</code> or <code>false</code> if only one is\r
-        * <code>null</code>.\r
-        * <p>Compares arrays with <code>Arrays.equals</code>, performing an equality\r
-        * check based on the array elements rather than the array reference.\r
-        * @param o1 first Object to compare\r
-        * @param o2 second Object to compare\r
-        * @return whether the given objects are equal\r
-        * @see java.util.Arrays#equals\r
-        */\r
-       public static boolean nullSafeEquals(Object o1, Object o2) {\r
-               if (o1 == o2) {\r
-                       return true;\r
-               }\r
-               if (o1 == null || o2 == null) {\r
-                       return false;\r
-               }\r
-               if (o1.equals(o2)) {\r
-                       return true;\r
-               }\r
-               if (o1.getClass().isArray() && o2.getClass().isArray()) {\r
-                       if (o1 instanceof Object[] && o2 instanceof Object[]) {\r
-                               return Arrays.equals((Object[]) o1, (Object[]) o2);\r
-                       }\r
-                       if (o1 instanceof boolean[] && o2 instanceof boolean[]) {\r
-                               return Arrays.equals((boolean[]) o1, (boolean[]) o2);\r
-                       }\r
-                       if (o1 instanceof byte[] && o2 instanceof byte[]) {\r
-                               return Arrays.equals((byte[]) o1, (byte[]) o2);\r
-                       }\r
-                       if (o1 instanceof char[] && o2 instanceof char[]) {\r
-                               return Arrays.equals((char[]) o1, (char[]) o2);\r
-                       }\r
-                       if (o1 instanceof double[] && o2 instanceof double[]) {\r
-                               return Arrays.equals((double[]) o1, (double[]) o2);\r
-                       }\r
-                       if (o1 instanceof float[] && o2 instanceof float[]) {\r
-                               return Arrays.equals((float[]) o1, (float[]) o2);\r
-                       }\r
-                       if (o1 instanceof int[] && o2 instanceof int[]) {\r
-                               return Arrays.equals((int[]) o1, (int[]) o2);\r
-                       }\r
-                       if (o1 instanceof long[] && o2 instanceof long[]) {\r
-                               return Arrays.equals((long[]) o1, (long[]) o2);\r
-                       }\r
-                       if (o1 instanceof short[] && o2 instanceof short[]) {\r
-                               return Arrays.equals((short[]) o1, (short[]) o2);\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Return as hash code for the given object; typically the value of\r
-        * <code>{@link Object#hashCode()}</code>. If the object is an array,\r
-        * this method will delegate to any of the <code>nullSafeHashCode</code>\r
-        * methods for arrays in this class. If the object is <code>null</code>,\r
-        * this method returns 0.\r
-        * @see #nullSafeHashCode(Object[])\r
-        * @see #nullSafeHashCode(boolean[])\r
-        * @see #nullSafeHashCode(byte[])\r
-        * @see #nullSafeHashCode(char[])\r
-        * @see #nullSafeHashCode(double[])\r
-        * @see #nullSafeHashCode(float[])\r
-        * @see #nullSafeHashCode(int[])\r
-        * @see #nullSafeHashCode(long[])\r
-        * @see #nullSafeHashCode(short[])\r
-        */\r
-       public static int nullSafeHashCode(Object obj) {\r
-               if (obj == null) {\r
-                       return 0;\r
-               }\r
-               if (obj.getClass().isArray()) {\r
-                       if (obj instanceof Object[]) {\r
-                               return nullSafeHashCode((Object[]) obj);\r
-                       }\r
-                       if (obj instanceof boolean[]) {\r
-                               return nullSafeHashCode((boolean[]) obj);\r
-                       }\r
-                       if (obj instanceof byte[]) {\r
-                               return nullSafeHashCode((byte[]) obj);\r
-                       }\r
-                       if (obj instanceof char[]) {\r
-                               return nullSafeHashCode((char[]) obj);\r
-                       }\r
-                       if (obj instanceof double[]) {\r
-                               return nullSafeHashCode((double[]) obj);\r
-                       }\r
-                       if (obj instanceof float[]) {\r
-                               return nullSafeHashCode((float[]) obj);\r
-                       }\r
-                       if (obj instanceof int[]) {\r
-                               return nullSafeHashCode((int[]) obj);\r
-                       }\r
-                       if (obj instanceof long[]) {\r
-                               return nullSafeHashCode((long[]) obj);\r
-                       }\r
-                       if (obj instanceof short[]) {\r
-                               return nullSafeHashCode((short[]) obj);\r
-                       }\r
-               }\r
-               return obj.hashCode();\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(Object[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + nullSafeHashCode(array[i]);\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(boolean[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + hashCode(array[i]);\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(byte[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + array[i];\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(char[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + array[i];\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(double[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + hashCode(array[i]);\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(float[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + hashCode(array[i]);\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(int[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + array[i];\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(long[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + hashCode(array[i]);\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return a hash code based on the contents of the specified array.\r
-        * If <code>array</code> is <code>null</code>, this method returns 0.\r
-        */\r
-       public static int nullSafeHashCode(short[] array) {\r
-               if (array == null) {\r
-                       return 0;\r
-               }\r
-               int hash = INITIAL_HASH;\r
-               int arraySize = array.length;\r
-               for (int i = 0; i < arraySize; i++) {\r
-                       hash = MULTIPLIER * hash + array[i];\r
-               }\r
-               return hash;\r
-       }\r
-\r
-       /**\r
-        * Return the same value as <code>{@link Boolean#hashCode()}</code>.\r
-        * @see Boolean#hashCode()\r
-        */\r
-       public static int hashCode(boolean bool) {\r
-               return bool ? 1231 : 1237;\r
-       }\r
-\r
-       /**\r
-        * Return the same value as <code>{@link Double#hashCode()}</code>.\r
-        * @see Double#hashCode()\r
-        */\r
-       public static int hashCode(double dbl) {\r
-               long bits = Double.doubleToLongBits(dbl);\r
-               return hashCode(bits);\r
-       }\r
-\r
-       /**\r
-        * Return the same value as <code>{@link Float#hashCode()}</code>.\r
-        * @see Float#hashCode()\r
-        */\r
-       public static int hashCode(float flt) {\r
-               return Float.floatToIntBits(flt);\r
-       }\r
-\r
-       /**\r
-        * Return the same value as <code>{@link Long#hashCode()}</code>.\r
-        * @see Long#hashCode()\r
-        */\r
-       public static int hashCode(long lng) {\r
-               return (int) (lng ^ (lng >>> 32));\r
-       }\r
-\r
-\r
-       //---------------------------------------------------------------------\r
-       // Convenience methods for toString output\r
-       //---------------------------------------------------------------------\r
-\r
-       /**\r
-        * Return a String representation of an object's overall identity.\r
-        * @param obj the object (may be <code>null</code>)\r
-        * @return the object's identity as String representation,\r
-        * or an empty String if the object was <code>null</code>\r
-        */\r
-       public static String identityToString(Object obj) {\r
-               if (obj == null) {\r
-                       return EMPTY_STRING;\r
-               }\r
-               return obj.getClass().getName() + "@" + getIdentityHexString(obj);\r
-       }\r
-\r
-       /**\r
-        * Return a hex String form of an object's identity hash code.\r
-        * @param obj the object\r
-        * @return the object's identity code in hex notation\r
-        */\r
-       public static String getIdentityHexString(Object obj) {\r
-               return Integer.toHexString(System.identityHashCode(obj));\r
-       }\r
-\r
-       /**\r
-        * Return a content-based String representation if <code>obj</code> is\r
-        * not <code>null</code>; otherwise returns an empty String.\r
-        * <p>Differs from {@link #nullSafeToString(Object)} in that it returns\r
-        * an empty String rather than "null" for a <code>null</code> value.\r
-        * @param obj the object to build a display String for\r
-        * @return a display String representation of <code>obj</code>\r
-        * @see #nullSafeToString(Object)\r
-        */\r
-       public static String getDisplayString(Object obj) {\r
-               if (obj == null) {\r
-                       return EMPTY_STRING;\r
-               }\r
-               return nullSafeToString(obj);\r
-       }\r
-\r
-       /**\r
-        * Determine the class name for the given object.\r
-        * <p>Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.\r
-        * @param obj the object to introspect (may be <code>null</code>)\r
-        * @return the corresponding class name\r
-        */\r
-       public static String nullSafeClassName(Object obj) {\r
-               return (obj != null ? obj.getClass().getName() : NULL_STRING);\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the specified Object.\r
-        * <p>Builds a String representation of the contents in case of an array.\r
-        * Returns <code>"null"</code> if <code>obj</code> is <code>null</code>.\r
-        * @param obj the object to build a String representation for\r
-        * @return a String representation of <code>obj</code>\r
-        */\r
-       public static String nullSafeToString(Object obj) {\r
-               if (obj == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               if (obj instanceof String) {\r
-                       return (String) obj;\r
-               }\r
-               if (obj instanceof Object[]) {\r
-                       return nullSafeToString((Object[]) obj);\r
-               }\r
-               if (obj instanceof boolean[]) {\r
-                       return nullSafeToString((boolean[]) obj);\r
-               }\r
-               if (obj instanceof byte[]) {\r
-                       return nullSafeToString((byte[]) obj);\r
-               }\r
-               if (obj instanceof char[]) {\r
-                       return nullSafeToString((char[]) obj);\r
-               }\r
-               if (obj instanceof double[]) {\r
-                       return nullSafeToString((double[]) obj);\r
-               }\r
-               if (obj instanceof float[]) {\r
-                       return nullSafeToString((float[]) obj);\r
-               }\r
-               if (obj instanceof int[]) {\r
-                       return nullSafeToString((int[]) obj);\r
-               }\r
-               if (obj instanceof long[]) {\r
-                       return nullSafeToString((long[]) obj);\r
-               }\r
-               if (obj instanceof short[]) {\r
-                       return nullSafeToString((short[]) obj);\r
-               }\r
-               String str = obj.toString();\r
-               return (str != null ? str : EMPTY_STRING);\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(Object[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append(String.valueOf(array[i]));\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(boolean[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(byte[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(char[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append("'").append(array[i]).append("'");\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(double[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(float[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(int[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(long[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-       /**\r
-        * Return a String representation of the contents of the specified array.\r
-        * <p>The String representation consists of a list of the array's elements,\r
-        * enclosed in curly braces (<code>"{}"</code>). Adjacent elements are separated\r
-        * by the characters <code>", "</code> (a comma followed by a space). Returns\r
-        * <code>"null"</code> if <code>array</code> is <code>null</code>.\r
-        * @param array the array to build a String representation for\r
-        * @return a String representation of <code>array</code>\r
-        */\r
-       public static String nullSafeToString(short[] array) {\r
-               if (array == null) {\r
-                       return NULL_STRING;\r
-               }\r
-               int length = array.length;\r
-               if (length == 0) {\r
-                       return EMPTY_ARRAY;\r
-               }\r
-               StringBuffer buffer = new StringBuffer();\r
-               for (int i = 0; i < length; i++) {\r
-                       if (i == 0) {\r
-                               buffer.append(ARRAY_START);\r
-                       }\r
-                       else {\r
-                               buffer.append(ARRAY_ELEMENT_SEPARATOR);\r
-                       }\r
-                       buffer.append(array[i]);\r
-               }\r
-               buffer.append(ARRAY_END);\r
-               return buffer.toString();\r
-       }\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/PathMatcher.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/PathMatcher.java
deleted file mode 100644 (file)
index d7a2322..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*\r
- * Copyright 2002-2007 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-/**\r
- * Strategy interface for <code>String</code>-based path matching.\r
- * \r
- * <p>Used by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver},\r
- * {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping},\r
- * {@link org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver},\r
- * and {@link org.springframework.web.servlet.mvc.WebContentInterceptor}.\r
- *\r
- * <p>The default implementation is {@link AntPathMatcher}, supporting the\r
- * Ant-style pattern syntax.\r
- *\r
- * @author Juergen Hoeller\r
- * @since 1.2\r
- * @see AntPathMatcher\r
- */\r
-public interface PathMatcher {\r
-\r
-       /**\r
-        * Does the given <code>path</code> represent a pattern that can be matched\r
-        * by an implementation of this interface?\r
-        * <p>If the return value is <code>false</code>, then the {@link #match}\r
-        * method does not have to be used because direct equality comparisons\r
-        * on the static path Strings will lead to the same result.\r
-        * @param path the path String to check\r
-        * @return <code>true</code> if the given <code>path</code> represents a pattern\r
-        */\r
-       boolean isPattern(String path);\r
-\r
-       /**\r
-        * Match the given <code>path</code> against the given <code>pattern</code>,\r
-        * according to this PathMatcher's matching strategy.\r
-        * @param pattern the pattern to match against\r
-        * @param path the path String to test\r
-        * @return <code>true</code> if the supplied <code>path</code> matched,\r
-        * <code>false</code> if it didn't\r
-        */\r
-       boolean match(String pattern, String path);\r
-\r
-       /**\r
-        * Match the given <code>path</code> against the corresponding part of the given\r
-        * <code>pattern</code>, according to this PathMatcher's matching strategy.\r
-        * <p>Determines whether the pattern at least matches as far as the given base\r
-        * path goes, assuming that a full path may then match as well.\r
-        * @param pattern the pattern to match against\r
-        * @param path the path String to test\r
-        * @return <code>true</code> if the supplied <code>path</code> matched,\r
-        * <code>false</code> if it didn't\r
-        */\r
-       boolean matchStart(String pattern, String path);\r
-\r
-       /**\r
-        * Given a pattern and a full path, determine the pattern-mapped part.\r
-        * <p>This method is supposed to find out which part of the path is matched\r
-        * dynamically through an actual pattern, that is, it strips off a statically\r
-        * defined leading path from the given full path, returning only the actually\r
-        * pattern-matched part of the path.\r
-        * <p>For example: For "myroot/*.html" as pattern and "myroot/myfile.html"\r
-        * as full path, this method should return "myfile.html". The detailed\r
-        * determination rules are specified to this PathMatcher's matching strategy.\r
-        * <p>A simple implementation may return the given full path as-is in case\r
-        * of an actual pattern, and the empty String in case of the pattern not\r
-        * containing any dynamic parts (i.e. the <code>pattern</code> parameter being\r
-        * a static path that wouldn't qualify as an actual {@link #isPattern pattern}).\r
-        * A sophisticated implementation will differentiate between the static parts\r
-        * and the dynamic parts of the given path pattern.\r
-        * @param pattern the path pattern\r
-        * @param path the full path to introspect\r
-        * @return the pattern-mapped part of the given <code>path</code>\r
-        * (never <code>null</code>)\r
-        */\r
-       String extractPathWithinPattern(String pattern, String path);\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/StringUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/StringUtils.java
deleted file mode 100644 (file)
index 6cbaee8..0000000
+++ /dev/null
@@ -1,1113 +0,0 @@
-/*\r
- * Copyright 2002-2008 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Enumeration;\r
-import java.util.Iterator;\r
-import java.util.LinkedList;\r
-import java.util.List;\r
-import java.util.Locale;\r
-import java.util.Properties;\r
-import java.util.Set;\r
-import java.util.StringTokenizer;\r
-import java.util.TreeSet;\r
-\r
-/**\r
- * Miscellaneous {@link String} utility methods.\r
- *\r
- * <p>Mainly for internal use within the framework; consider\r
- * <a href="http://jakarta.apache.org/commons/lang/">Jakarta's Commons Lang</a>\r
- * for a more comprehensive suite of String utilities.\r
- *\r
- * <p>This class delivers some simple functionality that should really\r
- * be provided by the core Java <code>String</code> and {@link StringBuffer}\r
- * classes, such as the ability to {@link #replace} all occurrences of a given\r
- * substring in a target string. It also provides easy-to-use methods to convert\r
- * between delimited strings, such as CSV strings, and collections and arrays.\r
- *\r
- * @author Rod Johnson\r
- * @author Juergen Hoeller\r
- * @author Keith Donald\r
- * @author Rob Harrop\r
- * @author Rick Evans\r
- * @since 16 April 2001\r
- */\r
-@SuppressWarnings({ "rawtypes", "unchecked" })\r
-public abstract class StringUtils {\r
-\r
-       private static final String FOLDER_SEPARATOR = "/";\r
-\r
-       private static final String WINDOWS_FOLDER_SEPARATOR = "\\";\r
-\r
-       private static final String TOP_PATH = "..";\r
-\r
-       private static final String CURRENT_PATH = ".";\r
-\r
-       private static final char EXTENSION_SEPARATOR = '.';\r
-\r
-\r
-       //---------------------------------------------------------------------\r
-       // General convenience methods for working with Strings\r
-       //---------------------------------------------------------------------\r
-\r
-       /**\r
-        * Check that the given CharSequence is neither <code>null</code> nor of length 0.\r
-        * Note: Will return <code>true</code> for a CharSequence that purely consists of whitespace.\r
-        * <p><pre>\r
-        * StringUtils.hasLength(null) = false\r
-        * StringUtils.hasLength("") = false\r
-        * StringUtils.hasLength(" ") = true\r
-        * StringUtils.hasLength("Hello") = true\r
-        * </pre>\r
-        * @param str the CharSequence to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the CharSequence is not null and has length\r
-        * @see #hasText(String)\r
-        */\r
-       public static boolean hasLength(CharSequence str) {\r
-               return (str != null && str.length() > 0);\r
-       }\r
-\r
-       /**\r
-        * Check that the given String is neither <code>null</code> nor of length 0.\r
-        * Note: Will return <code>true</code> for a String that purely consists of whitespace.\r
-        * @param str the String to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the String is not null and has length\r
-        * @see #hasLength(CharSequence)\r
-        */\r
-       public static boolean hasLength(String str) {\r
-               return hasLength((CharSequence) str);\r
-       }\r
-\r
-       /**\r
-        * Check whether the given CharSequence has actual text.\r
-        * More specifically, returns <code>true</code> if the string not <code>null</code>,\r
-        * its length is greater than 0, and it contains at least one non-whitespace character.\r
-        * <p><pre>\r
-        * StringUtils.hasText(null) = false\r
-        * StringUtils.hasText("") = false\r
-        * StringUtils.hasText(" ") = false\r
-        * StringUtils.hasText("12345") = true\r
-        * StringUtils.hasText(" 12345 ") = true\r
-        * </pre>\r
-        * @param str the CharSequence to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the CharSequence is not <code>null</code>,\r
-        * its length is greater than 0, and it does not contain whitespace only\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static boolean hasText(CharSequence str) {\r
-               if (!hasLength(str)) {\r
-                       return false;\r
-               }\r
-               int strLen = str.length();\r
-               for (int i = 0; i < strLen; i++) {\r
-                       if (!Character.isWhitespace(str.charAt(i))) {\r
-                               return true;\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Check whether the given String has actual text.\r
-        * More specifically, returns <code>true</code> if the string not <code>null</code>,\r
-        * its length is greater than 0, and it contains at least one non-whitespace character.\r
-        * @param str the String to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the String is not <code>null</code>, its length is\r
-        * greater than 0, and it does not contain whitespace only\r
-        * @see #hasText(CharSequence)\r
-        */\r
-       public static boolean hasText(String str) {\r
-               return hasText((CharSequence) str);\r
-       }\r
-\r
-       /**\r
-        * Check whether the given CharSequence contains any whitespace characters.\r
-        * @param str the CharSequence to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the CharSequence is not empty and\r
-        * contains at least 1 whitespace character\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static boolean containsWhitespace(CharSequence str) {\r
-               if (!hasLength(str)) {\r
-                       return false;\r
-               }\r
-               int strLen = str.length();\r
-               for (int i = 0; i < strLen; i++) {\r
-                       if (Character.isWhitespace(str.charAt(i))) {\r
-                               return true;\r
-                       }\r
-               }\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Check whether the given String contains any whitespace characters.\r
-        * @param str the String to check (may be <code>null</code>)\r
-        * @return <code>true</code> if the String is not empty and\r
-        * contains at least 1 whitespace character\r
-        * @see #containsWhitespace(CharSequence)\r
-        */\r
-       public static boolean containsWhitespace(String str) {\r
-               return containsWhitespace((CharSequence) str);\r
-       }\r
-\r
-       /**\r
-        * Trim leading and trailing whitespace from the given String.\r
-        * @param str the String to check\r
-        * @return the trimmed String\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static String trimWhitespace(String str) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               while (buf.length() > 0 && Character.isWhitespace(buf.charAt(0))) {\r
-                       buf.deleteCharAt(0);\r
-               }\r
-               while (buf.length() > 0 && Character.isWhitespace(buf.charAt(buf.length() - 1))) {\r
-                       buf.deleteCharAt(buf.length() - 1);\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Trim <i>all</i> whitespace from the given String:\r
-        * leading, trailing, and inbetween characters.\r
-        * @param str the String to check\r
-        * @return the trimmed String\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static String trimAllWhitespace(String str) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               int index = 0;\r
-               while (buf.length() > index) {\r
-                       if (Character.isWhitespace(buf.charAt(index))) {\r
-                               buf.deleteCharAt(index);\r
-                       }\r
-                       else {\r
-                               index++;\r
-                       }\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Trim leading whitespace from the given String.\r
-        * @param str the String to check\r
-        * @return the trimmed String\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static String trimLeadingWhitespace(String str) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               while (buf.length() > 0 && Character.isWhitespace(buf.charAt(0))) {\r
-                       buf.deleteCharAt(0);\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Trim trailing whitespace from the given String.\r
-        * @param str the String to check\r
-        * @return the trimmed String\r
-        * @see java.lang.Character#isWhitespace\r
-        */\r
-       public static String trimTrailingWhitespace(String str) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               while (buf.length() > 0 && Character.isWhitespace(buf.charAt(buf.length() - 1))) {\r
-                       buf.deleteCharAt(buf.length() - 1);\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Trim all occurences of the supplied leading character from the given String.\r
-        * @param str the String to check\r
-        * @param leadingCharacter the leading character to be trimmed\r
-        * @return the trimmed String\r
-        */\r
-       public static String trimLeadingCharacter(String str, char leadingCharacter) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               while (buf.length() > 0 && buf.charAt(0) == leadingCharacter) {\r
-                       buf.deleteCharAt(0);\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Trim all occurences of the supplied trailing character from the given String.\r
-        * @param str the String to check\r
-        * @param trailingCharacter the trailing character to be trimmed\r
-        * @return the trimmed String\r
-        */\r
-       public static String trimTrailingCharacter(String str, char trailingCharacter) {\r
-               if (!hasLength(str)) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str);\r
-               while (buf.length() > 0 && buf.charAt(buf.length() - 1) == trailingCharacter) {\r
-                       buf.deleteCharAt(buf.length() - 1);\r
-               }\r
-               return buf.toString();\r
-       }\r
-\r
-\r
-       /**\r
-        * Test if the given String starts with the specified prefix,\r
-        * ignoring upper/lower case.\r
-        * @param str the String to check\r
-        * @param prefix the prefix to look for\r
-        * @see java.lang.String#startsWith\r
-        */\r
-       public static boolean startsWithIgnoreCase(String str, String prefix) {\r
-               if (str == null || prefix == null) {\r
-                       return false;\r
-               }\r
-               if (str.startsWith(prefix)) {\r
-                       return true;\r
-               }\r
-               if (str.length() < prefix.length()) {\r
-                       return false;\r
-               }\r
-               String lcStr = str.substring(0, prefix.length()).toLowerCase();\r
-               String lcPrefix = prefix.toLowerCase();\r
-               return lcStr.equals(lcPrefix);\r
-       }\r
-\r
-       /**\r
-        * Test if the given String ends with the specified suffix,\r
-        * ignoring upper/lower case.\r
-        * @param str the String to check\r
-        * @param suffix the suffix to look for\r
-        * @see java.lang.String#endsWith\r
-        */\r
-       public static boolean endsWithIgnoreCase(String str, String suffix) {\r
-               if (str == null || suffix == null) {\r
-                       return false;\r
-               }\r
-               if (str.endsWith(suffix)) {\r
-                       return true;\r
-               }\r
-               if (str.length() < suffix.length()) {\r
-                       return false;\r
-               }\r
-\r
-               String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();\r
-               String lcSuffix = suffix.toLowerCase();\r
-               return lcStr.equals(lcSuffix);\r
-       }\r
-\r
-       /**\r
-        * Test whether the given string matches the given substring\r
-        * at the given index.\r
-        * @param str the original string (or StringBuffer)\r
-        * @param index the index in the original string to start matching against\r
-        * @param substring the substring to match at the given index\r
-        */\r
-       public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {\r
-               for (int j = 0; j < substring.length(); j++) {\r
-                       int i = index + j;\r
-                       if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {\r
-                               return false;\r
-                       }\r
-               }\r
-               return true;\r
-       }\r
-\r
-       /**\r
-        * Count the occurrences of the substring in string s.\r
-        * @param str string to search in. Return 0 if this is null.\r
-        * @param sub string to search for. Return 0 if this is null.\r
-        */\r
-       public static int countOccurrencesOf(String str, String sub) {\r
-               if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {\r
-                       return 0;\r
-               }\r
-               int count = 0, pos = 0, idx = 0;\r
-               while ((idx = str.indexOf(sub, pos)) != -1) {\r
-                       ++count;\r
-                       pos = idx + sub.length();\r
-               }\r
-               return count;\r
-       }\r
-\r
-       /**\r
-        * Replace all occurences of a substring within a string with\r
-        * another string.\r
-        * @param inString String to examine\r
-        * @param oldPattern String to replace\r
-        * @param newPattern String to insert\r
-        * @return a String with the replacements\r
-        */\r
-       public static String replace(String inString, String oldPattern, String newPattern) {\r
-               if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {\r
-                       return inString;\r
-               }\r
-               StringBuffer sbuf = new StringBuffer();\r
-               // output StringBuffer we'll build up\r
-               int pos = 0; // our position in the old string\r
-               int index = inString.indexOf(oldPattern);\r
-               // the index of an occurrence we've found, or -1\r
-               int patLen = oldPattern.length();\r
-               while (index >= 0) {\r
-                       sbuf.append(inString.substring(pos, index));\r
-                       sbuf.append(newPattern);\r
-                       pos = index + patLen;\r
-                       index = inString.indexOf(oldPattern, pos);\r
-               }\r
-               sbuf.append(inString.substring(pos));\r
-               // remember to append any characters to the right of a match\r
-               return sbuf.toString();\r
-       }\r
-\r
-       /**\r
-        * Delete all occurrences of the given substring.\r
-        * @param inString the original String\r
-        * @param pattern the pattern to delete all occurrences of\r
-        * @return the resulting String\r
-        */\r
-       public static String delete(String inString, String pattern) {\r
-               return replace(inString, pattern, "");\r
-       }\r
-\r
-       /**\r
-        * Delete any character in a given String.\r
-        * @param inString the original String\r
-        * @param charsToDelete a set of characters to delete.\r
-        * E.g. "az\n" will delete 'a's, 'z's and new lines.\r
-        * @return the resulting String\r
-        */\r
-       public static String deleteAny(String inString, String charsToDelete) {\r
-               if (!hasLength(inString) || !hasLength(charsToDelete)) {\r
-                       return inString;\r
-               }\r
-               StringBuffer out = new StringBuffer();\r
-               for (int i = 0; i < inString.length(); i++) {\r
-                       char c = inString.charAt(i);\r
-                       if (charsToDelete.indexOf(c) == -1) {\r
-                               out.append(c);\r
-                       }\r
-               }\r
-               return out.toString();\r
-       }\r
-\r
-\r
-       //---------------------------------------------------------------------\r
-       // Convenience methods for working with formatted Strings\r
-       //---------------------------------------------------------------------\r
-\r
-       /**\r
-        * Quote the given String with single quotes.\r
-        * @param str the input String (e.g. "myString")\r
-        * @return the quoted String (e.g. "'myString'"),\r
-        * or <code>null</code> if the input was <code>null</code>\r
-        */\r
-       public static String quote(String str) {\r
-               return (str != null ? "'" + str + "'" : null);\r
-       }\r
-\r
-       /**\r
-        * Turn the given Object into a String with single quotes\r
-        * if it is a String; keeping the Object as-is else.\r
-        * @param obj the input Object (e.g. "myString")\r
-        * @return the quoted String (e.g. "'myString'"),\r
-        * or the input object as-is if not a String\r
-        */\r
-       public static Object quoteIfString(Object obj) {\r
-               return (obj instanceof String ? quote((String) obj) : obj);\r
-       }\r
-\r
-       /**\r
-        * Unqualify a string qualified by a '.' dot character. For example,\r
-        * "this.name.is.qualified", returns "qualified".\r
-        * @param qualifiedName the qualified name\r
-        */\r
-       public static String unqualify(String qualifiedName) {\r
-               return unqualify(qualifiedName, '.');\r
-       }\r
-\r
-       /**\r
-        * Unqualify a string qualified by a separator character. For example,\r
-        * "this:name:is:qualified" returns "qualified" if using a ':' separator.\r
-        * @param qualifiedName the qualified name\r
-        * @param separator the separator\r
-        */\r
-       public static String unqualify(String qualifiedName, char separator) {\r
-               return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);\r
-       }\r
-\r
-       /**\r
-        * Capitalize a <code>String</code>, changing the first letter to\r
-        * upper case as per {@link Character#toUpperCase(char)}.\r
-        * No other letters are changed.\r
-        * @param str the String to capitalize, may be <code>null</code>\r
-        * @return the capitalized String, <code>null</code> if null\r
-        */\r
-       public static String capitalize(String str) {\r
-               return changeFirstCharacterCase(str, true);\r
-       }\r
-\r
-       /**\r
-        * Uncapitalize a <code>String</code>, changing the first letter to\r
-        * lower case as per {@link Character#toLowerCase(char)}.\r
-        * No other letters are changed.\r
-        * @param str the String to uncapitalize, may be <code>null</code>\r
-        * @return the uncapitalized String, <code>null</code> if null\r
-        */\r
-       public static String uncapitalize(String str) {\r
-               return changeFirstCharacterCase(str, false);\r
-       }\r
-\r
-       private static String changeFirstCharacterCase(String str, boolean capitalize) {\r
-               if (str == null || str.length() == 0) {\r
-                       return str;\r
-               }\r
-               StringBuffer buf = new StringBuffer(str.length());\r
-               if (capitalize) {\r
-                       buf.append(Character.toUpperCase(str.charAt(0)));\r
-               }\r
-               else {\r
-                       buf.append(Character.toLowerCase(str.charAt(0)));\r
-               }\r
-               buf.append(str.substring(1));\r
-               return buf.toString();\r
-       }\r
-\r
-       /**\r
-        * Extract the filename from the given path,\r
-        * e.g. "mypath/myfile.txt" to "myfile.txt".\r
-        * @param path the file path (may be <code>null</code>)\r
-        * @return the extracted filename, or <code>null</code> if none\r
-        */\r
-       public static String getFilename(String path) {\r
-               if (path == null) {\r
-                       return null;\r
-               }\r
-               int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);\r
-               return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);\r
-       }\r
-\r
-       /**\r
-        * Extract the filename extension from the given path,\r
-        * e.g. "mypath/myfile.txt" to "txt".\r
-        * @param path the file path (may be <code>null</code>)\r
-        * @return the extracted filename extension, or <code>null</code> if none\r
-        */\r
-       public static String getFilenameExtension(String path) {\r
-               if (path == null) {\r
-                       return null;\r
-               }\r
-               int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR);\r
-               return (sepIndex != -1 ? path.substring(sepIndex + 1) : null);\r
-       }\r
-\r
-       /**\r
-        * Strip the filename extension from the given path,\r
-        * e.g. "mypath/myfile.txt" to "mypath/myfile".\r
-        * @param path the file path (may be <code>null</code>)\r
-        * @return the path with stripped filename extension,\r
-        * or <code>null</code> if none\r
-        */\r
-       public static String stripFilenameExtension(String path) {\r
-               if (path == null) {\r
-                       return null;\r
-               }\r
-               int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR);\r
-               return (sepIndex != -1 ? path.substring(0, sepIndex) : path);\r
-       }\r
-\r
-       /**\r
-        * Apply the given relative path to the given path,\r
-        * assuming standard Java folder separation (i.e. "/" separators);\r
-        * @param path the path to start from (usually a full file path)\r
-        * @param relativePath the relative path to apply\r
-        * (relative to the full file path above)\r
-        * @return the full file path that results from applying the relative path\r
-        */\r
-       public static String applyRelativePath(String path, String relativePath) {\r
-               int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);\r
-               if (separatorIndex != -1) {\r
-                       String newPath = path.substring(0, separatorIndex);\r
-                       if (!relativePath.startsWith(FOLDER_SEPARATOR)) {\r
-                               newPath += FOLDER_SEPARATOR;\r
-                       }\r
-                       return newPath + relativePath;\r
-               }\r
-               else {\r
-                       return relativePath;\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Normalize the path by suppressing sequences like "path/.." and\r
-        * inner simple dots.\r
-        * <p>The result is convenient for path comparison. For other uses,\r
-        * notice that Windows separators ("\") are replaced by simple slashes.\r
-        * @param path the original path\r
-        * @return the normalized path\r
-        */\r
-       public static String cleanPath(String path) {\r
-               if (path == null) {\r
-                       return null;\r
-               }\r
-               String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);\r
-\r
-               // Strip prefix from path to analyze, to not treat it as part of the\r
-               // first path element. This is necessary to correctly parse paths like\r
-               // "file:core/../core/io/Resource.class", where the ".." should just\r
-               // strip the first "core" directory while keeping the "file:" prefix.\r
-               int prefixIndex = pathToUse.indexOf(":");\r
-               String prefix = "";\r
-               if (prefixIndex != -1) {\r
-                       prefix = pathToUse.substring(0, prefixIndex + 1);\r
-                       pathToUse = pathToUse.substring(prefixIndex + 1);\r
-               }\r
-               if (pathToUse.startsWith(FOLDER_SEPARATOR)) {\r
-                       prefix = prefix + FOLDER_SEPARATOR;\r
-                       pathToUse = pathToUse.substring(1);\r
-               }\r
-\r
-               String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);\r
-               List pathElements = new LinkedList();\r
-               int tops = 0;\r
-\r
-               for (int i = pathArray.length - 1; i >= 0; i--) {\r
-                       String element = pathArray[i];\r
-                       if (CURRENT_PATH.equals(element)) {\r
-                               // Points to current directory - drop it.\r
-                       }\r
-                       else if (TOP_PATH.equals(element)) {\r
-                               // Registering top path found.\r
-                               tops++;\r
-                       }\r
-                       else {\r
-                               if (tops > 0) {\r
-                                       // Merging path element with element corresponding to top path.\r
-                                       tops--;\r
-                               }\r
-                               else {\r
-                                       // Normal path element found.\r
-                                       pathElements.add(0, element);\r
-                               }\r
-                       }\r
-               }\r
-\r
-               // Remaining top paths need to be retained.\r
-               for (int i = 0; i < tops; i++) {\r
-                       pathElements.add(0, TOP_PATH);\r
-               }\r
-\r
-               return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);\r
-       }\r
-\r
-       /**\r
-        * Compare two paths after normalization of them.\r
-        * @param path1 first path for comparison\r
-        * @param path2 second path for comparison\r
-        * @return whether the two paths are equivalent after normalization\r
-        */\r
-       public static boolean pathEquals(String path1, String path2) {\r
-               return cleanPath(path1).equals(cleanPath(path2));\r
-       }\r
-\r
-       /**\r
-        * Parse the given <code>localeString</code> into a {@link Locale}.\r
-        * <p>This is the inverse operation of {@link Locale#toString Locale's toString}.\r
-        * @param localeString the locale string, following <code>Locale's</code>\r
-        * <code>toString()</code> format ("en", "en_UK", etc);\r
-        * also accepts spaces as separators, as an alternative to underscores\r
-        * @return a corresponding <code>Locale</code> instance\r
-        */\r
-       public static Locale parseLocaleString(String localeString) {\r
-               String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);\r
-               String language = (parts.length > 0 ? parts[0] : "");\r
-               String country = (parts.length > 1 ? parts[1] : "");\r
-               String variant = "";\r
-               if (parts.length >= 2) {\r
-                       // There is definitely a variant, and it is everything after the country\r
-                       // code sans the separator between the country code and the variant.\r
-                       int endIndexOfCountryCode = localeString.indexOf(country) + country.length();\r
-                       // Strip off any leading '_' and whitespace, what's left is the variant.\r
-                       variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));\r
-                       if (variant.startsWith("_")) {\r
-                               variant = trimLeadingCharacter(variant, '_');\r
-                       }\r
-               }\r
-               return (language.length() > 0 ? new Locale(language, country, variant) : null);\r
-       }\r
-\r
-       /**\r
-        * Determine the RFC 3066 compliant language tag,\r
-        * as used for the HTTP "Accept-Language" header.\r
-        * @param locale the Locale to transform to a language tag\r
-        * @return the RFC 3066 compliant language tag as String\r
-        */\r
-       public static String toLanguageTag(Locale locale) {\r
-               return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");\r
-       }\r
-\r
-\r
-       //---------------------------------------------------------------------\r
-       // Convenience methods for working with String arrays\r
-       //---------------------------------------------------------------------\r
-\r
-       /**\r
-        * Append the given String to the given String array, returning a new array\r
-        * consisting of the input array contents plus the given String.\r
-        * @param array the array to append to (can be <code>null</code>)\r
-        * @param str the String to append\r
-        * @return the new array (never <code>null</code>)\r
-        */\r
-       public static String[] addStringToArray(String[] array, String str) {\r
-               if (ObjectUtils.isEmpty(array)) {\r
-                       return new String[] {str};\r
-               }\r
-               String[] newArr = new String[array.length + 1];\r
-               System.arraycopy(array, 0, newArr, 0, array.length);\r
-               newArr[array.length] = str;\r
-               return newArr;\r
-       }\r
-\r
-       /**\r
-        * Concatenate the given String arrays into one,\r
-        * with overlapping array elements included twice.\r
-        * <p>The order of elements in the original arrays is preserved.\r
-        * @param array1 the first array (can be <code>null</code>)\r
-        * @param array2 the second array (can be <code>null</code>)\r
-        * @return the new array (<code>null</code> if both given arrays were <code>null</code>)\r
-        */\r
-       public static String[] concatenateStringArrays(String[] array1, String[] array2) {\r
-               if (ObjectUtils.isEmpty(array1)) {\r
-                       return array2;\r
-               }\r
-               if (ObjectUtils.isEmpty(array2)) {\r
-                       return array1;\r
-               }\r
-               String[] newArr = new String[array1.length + array2.length];\r
-               System.arraycopy(array1, 0, newArr, 0, array1.length);\r
-               System.arraycopy(array2, 0, newArr, array1.length, array2.length);\r
-               return newArr;\r
-       }\r
-\r
-       /**\r
-        * Merge the given String arrays into one, with overlapping\r
-        * array elements only included once.\r
-        * <p>The order of elements in the original arrays is preserved\r
-        * (with the exception of overlapping elements, which are only\r
-        * included on their first occurence).\r
-        * @param array1 the first array (can be <code>null</code>)\r
-        * @param array2 the second array (can be <code>null</code>)\r
-        * @return the new array (<code>null</code> if both given arrays were <code>null</code>)\r
-        */\r
-       public static String[] mergeStringArrays(String[] array1, String[] array2) {\r
-               if (ObjectUtils.isEmpty(array1)) {\r
-                       return array2;\r
-               }\r
-               if (ObjectUtils.isEmpty(array2)) {\r
-                       return array1;\r
-               }\r
-               List result = new ArrayList();\r
-               result.addAll(Arrays.asList(array1));\r
-               for (int i = 0; i < array2.length; i++) {\r
-                       String str = array2[i];\r
-                       if (!result.contains(str)) {\r
-                               result.add(str);\r
-                       }\r
-               }\r
-               return toStringArray(result);\r
-       }\r
-\r
-       /**\r
-        * Turn given source String array into sorted array.\r
-        * @param array the source array\r
-        * @return the sorted array (never <code>null</code>)\r
-        */\r
-       public static String[] sortStringArray(String[] array) {\r
-               if (ObjectUtils.isEmpty(array)) {\r
-                       return new String[0];\r
-               }\r
-               Arrays.sort(array);\r
-               return array;\r
-       }\r
-\r
-       /**\r
-        * Copy the given Collection into a String array.\r
-        * The Collection must contain String elements only.\r
-        * @param collection the Collection to copy\r
-        * @return the String array (<code>null</code> if the passed-in\r
-        * Collection was <code>null</code>)\r
-        */\r
-       public static String[] toStringArray(Collection collection) {\r
-               if (collection == null) {\r
-                       return null;\r
-               }\r
-               return (String[]) collection.toArray(new String[collection.size()]);\r
-       }\r
-\r
-       /**\r
-        * Copy the given Enumeration into a String array.\r
-        * The Enumeration must contain String elements only.\r
-        * @param enumeration the Enumeration to copy\r
-        * @return the String array (<code>null</code> if the passed-in\r
-        * Enumeration was <code>null</code>)\r
-        */\r
-       public static String[] toStringArray(Enumeration enumeration) {\r
-               if (enumeration == null) {\r
-                       return null;\r
-               }\r
-               List list = Collections.list(enumeration);\r
-               return (String[]) list.toArray(new String[list.size()]);\r
-       }\r
-\r
-       /**\r
-        * Trim the elements of the given String array,\r
-        * calling <code>String.trim()</code> on each of them.\r
-        * @param array the original String array\r
-        * @return the resulting array (of the same size) with trimmed elements\r
-        */\r
-       public static String[] trimArrayElements(String[] array) {\r
-               if (ObjectUtils.isEmpty(array)) {\r
-                       return new String[0];\r
-               }\r
-               String[] result = new String[array.length];\r
-               for (int i = 0; i < array.length; i++) {\r
-                       String element = array[i];\r
-                       result[i] = (element != null ? element.trim() : null);\r
-               }\r
-               return result;\r
-       }\r
-\r
-       /**\r
-        * Remove duplicate Strings from the given array.\r
-        * Also sorts the array, as it uses a TreeSet.\r
-        * @param array the String array\r
-        * @return an array without duplicates, in natural sort order\r
-        */\r
-       public static String[] removeDuplicateStrings(String[] array) {\r
-               if (ObjectUtils.isEmpty(array)) {\r
-                       return array;\r
-               }\r
-               Set set = new TreeSet();\r
-               for (int i = 0; i < array.length; i++) {\r
-                       set.add(array[i]);\r
-               }\r
-               return toStringArray(set);\r
-       }\r
-\r
-       /**\r
-        * Split a String at the first occurrence of the delimiter.\r
-        * Does not include the delimiter in the result.\r
-        * @param toSplit the string to split\r
-        * @param delimiter to split the string up with\r
-        * @return a two element array with index 0 being before the delimiter, and\r
-        * index 1 being after the delimiter (neither element includes the delimiter);\r
-        * or <code>null</code> if the delimiter wasn't found in the given input String\r
-        */\r
-       public static String[] split(String toSplit, String delimiter) {\r
-               if (!hasLength(toSplit) || !hasLength(delimiter)) {\r
-                       return null;\r
-               }\r
-               int offset = toSplit.indexOf(delimiter);\r
-               if (offset < 0) {\r
-                       return null;\r
-               }\r
-               String beforeDelimiter = toSplit.substring(0, offset);\r
-               String afterDelimiter = toSplit.substring(offset + delimiter.length());\r
-               return new String[] {beforeDelimiter, afterDelimiter};\r
-       }\r
-\r
-       /**\r
-        * Take an array Strings and split each element based on the given delimiter.\r
-        * A <code>Properties</code> instance is then generated, with the left of the\r
-        * delimiter providing the key, and the right of the delimiter providing the value.\r
-        * <p>Will trim both the key and value before adding them to the\r
-        * <code>Properties</code> instance.\r
-        * @param array the array to process\r
-        * @param delimiter to split each element using (typically the equals symbol)\r
-        * @return a <code>Properties</code> instance representing the array contents,\r
-        * or <code>null</code> if the array to process was null or empty\r
-        */\r
-       public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {\r
-               return splitArrayElementsIntoProperties(array, delimiter, null);\r
-       }\r
-\r
-       /**\r
-        * Take an array Strings and split each element based on the given delimiter.\r
-        * A <code>Properties</code> instance is then generated, with the left of the\r
-        * delimiter providing the key, and the right of the delimiter providing the value.\r
-        * <p>Will trim both the key and value before adding them to the\r
-        * <code>Properties</code> instance.\r
-        * @param array the array to process\r
-        * @param delimiter to split each element using (typically the equals symbol)\r
-        * @param charsToDelete one or more characters to remove from each element\r
-        * prior to attempting the split operation (typically the quotation mark\r
-        * symbol), or <code>null</code> if no removal should occur\r
-        * @return a <code>Properties</code> instance representing the array contents,\r
-        * or <code>null</code> if the array to process was <code>null</code> or empty\r
-        */\r
-       public static Properties splitArrayElementsIntoProperties(\r
-                       String[] array, String delimiter, String charsToDelete) {\r
-\r
-               if (ObjectUtils.isEmpty(array)) {\r
-                       return null;\r
-               }\r
-               Properties result = new Properties();\r
-               for (int i = 0; i < array.length; i++) {\r
-                       String element = array[i];\r
-                       if (charsToDelete != null) {\r
-                               element = deleteAny(array[i], charsToDelete);\r
-                       }\r
-                       String[] splittedElement = split(element, delimiter);\r
-                       if (splittedElement == null) {\r
-                               continue;\r
-                       }\r
-                       result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());\r
-               }\r
-               return result;\r
-       }\r
-\r
-       /**\r
-        * Tokenize the given String into a String array via a StringTokenizer.\r
-        * Trims tokens and omits empty tokens.\r
-        * <p>The given delimiters string is supposed to consist of any number of\r
-        * delimiter characters. Each of those characters can be used to separate\r
-        * tokens. A delimiter is always a single character; for multi-character\r
-        * delimiters, consider using <code>delimitedListToStringArray</code>\r
-        * @param str the String to tokenize\r
-        * @param delimiters the delimiter characters, assembled as String\r
-        * (each of those characters is individually considered as delimiter).\r
-        * @return an array of the tokens\r
-        * @see java.util.StringTokenizer\r
-        * @see java.lang.String#trim()\r
-        * @see #delimitedListToStringArray\r
-        */\r
-       public static String[] tokenizeToStringArray(String str, String delimiters) {\r
-               return tokenizeToStringArray(str, delimiters, true, true);\r
-       }\r
-\r
-       /**\r
-        * Tokenize the given String into a String array via a StringTokenizer.\r
-        * <p>The given delimiters string is supposed to consist of any number of\r
-        * delimiter characters. Each of those characters can be used to separate\r
-        * tokens. A delimiter is always a single character; for multi-character\r
-        * delimiters, consider using <code>delimitedListToStringArray</code>\r
-        * @param str the String to tokenize\r
-        * @param delimiters the delimiter characters, assembled as String\r
-        * (each of those characters is individually considered as delimiter)\r
-        * @param trimTokens trim the tokens via String's <code>trim</code>\r
-        * @param ignoreEmptyTokens omit empty tokens from the result array\r
-        * (only applies to tokens that are empty after trimming; StringTokenizer\r
-        * will not consider subsequent delimiters as token in the first place).\r
-        * @return an array of the tokens (<code>null</code> if the input String\r
-        * was <code>null</code>)\r
-        * @see java.util.StringTokenizer\r
-        * @see java.lang.String#trim()\r
-        * @see #delimitedListToStringArray\r
-        */\r
-       public static String[] tokenizeToStringArray(\r
-                       String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {\r
-\r
-               if (str == null) {\r
-                       return null;\r
-               }\r
-               StringTokenizer st = new StringTokenizer(str, delimiters);\r
-               List tokens = new ArrayList();\r
-               while (st.hasMoreTokens()) {\r
-                       String token = st.nextToken();\r
-                       if (trimTokens) {\r
-                               token = token.trim();\r
-                       }\r
-                       if (!ignoreEmptyTokens || token.length() > 0) {\r
-                               tokens.add(token);\r
-                       }\r
-               }\r
-               return toStringArray(tokens);\r
-       }\r
-\r
-       /**\r
-        * Take a String which is a delimited list and convert it to a String array.\r
-        * <p>A single delimiter can consists of more than one character: It will still\r
-        * be considered as single delimiter string, rather than as bunch of potential\r
-        * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.\r
-        * @param str the input String\r
-        * @param delimiter the delimiter between elements (this is a single delimiter,\r
-        * rather than a bunch individual delimiter characters)\r
-        * @return an array of the tokens in the list\r
-        * @see #tokenizeToStringArray\r
-        */\r
-       public static String[] delimitedListToStringArray(String str, String delimiter) {\r
-               return delimitedListToStringArray(str, delimiter, null);\r
-       }\r
-\r
-       /**\r
-        * Take a String which is a delimited list and convert it to a String array.\r
-        * <p>A single delimiter can consists of more than one character: It will still\r
-        * be considered as single delimiter string, rather than as bunch of potential\r
-        * delimiter characters - in contrast to <code>tokenizeToStringArray</code>.\r
-        * @param str the input String\r
-        * @param delimiter the delimiter between elements (this is a single delimiter,\r
-        * rather than a bunch individual delimiter characters)\r
-        * @param charsToDelete a set of characters to delete. Useful for deleting unwanted\r
-        * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.\r
-        * @return an array of the tokens in the list\r
-        * @see #tokenizeToStringArray\r
-        */\r
-       public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {\r
-               if (str == null) {\r
-                       return new String[0];\r
-               }\r
-               if (delimiter == null) {\r
-                       return new String[] {str};\r
-               }\r
-               List result = new ArrayList();\r
-               if ("".equals(delimiter)) {\r
-                       for (int i = 0; i < str.length(); i++) {\r
-                               result.add(deleteAny(str.substring(i, i + 1), charsToDelete));\r
-                       }\r
-               }\r
-               else {\r
-                       int pos = 0;\r
-                       int delPos = 0;\r
-                       while ((delPos = str.indexOf(delimiter, pos)) != -1) {\r
-                               result.add(deleteAny(str.substring(pos, delPos), charsToDelete));\r
-                               pos = delPos + delimiter.length();\r
-                       }\r
-                       if (str.length() > 0 && pos <= str.length()) {\r
-                               // Add rest of String, but not in case of empty input.\r
-                               result.add(deleteAny(str.substring(pos), charsToDelete));\r
-                       }\r
-               }\r
-               return toStringArray(result);\r
-       }\r
-\r
-       /**\r
-        * Convert a CSV list into an array of Strings.\r
-        * @param str the input String\r
-        * @return an array of Strings, or the empty array in case of empty input\r
-        */\r
-       public static String[] commaDelimitedListToStringArray(String str) {\r
-               return delimitedListToStringArray(str, ",");\r
-       }\r
-\r
-       /**\r
-        * Convenience method to convert a CSV string list to a set.\r
-        * Note that this will suppress duplicates.\r
-        * @param str the input String\r
-        * @return a Set of String entries in the list\r
-        */\r
-       public static Set commaDelimitedListToSet(String str) {\r
-               Set set = new TreeSet();\r
-               String[] tokens = commaDelimitedListToStringArray(str);\r
-               for (int i = 0; i < tokens.length; i++) {\r
-                       set.add(tokens[i]);\r
-               }\r
-               return set;\r
-       }\r
-\r
-       /**\r
-        * Convenience method to return a Collection as a delimited (e.g. CSV)\r
-        * String. E.g. useful for <code>toString()</code> implementations.\r
-        * @param coll the Collection to display\r
-        * @param delim the delimiter to use (probably a ",")\r
-        * @param prefix the String to start each element with\r
-        * @param suffix the String to end each element with\r
-        * @return the delimited String\r
-        */\r
-       public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) {\r
-               if (CollectionUtils.isEmpty(coll)) {\r
-                       return "";\r
-               }\r
-               StringBuffer sb = new StringBuffer();\r
-               Iterator it = coll.iterator();\r
-               while (it.hasNext()) {\r
-                       sb.append(prefix).append(it.next()).append(suffix);\r
-                       if (it.hasNext()) {\r
-                               sb.append(delim);\r
-                       }\r
-               }\r
-               return sb.toString();\r
-       }\r
-\r
-       /**\r
-        * Convenience method to return a Collection as a delimited (e.g. CSV)\r
-        * String. E.g. useful for <code>toString()</code> implementations.\r
-        * @param coll the Collection to display\r
-        * @param delim the delimiter to use (probably a ",")\r
-        * @return the delimited String\r
-        */\r
-       public static String collectionToDelimitedString(Collection coll, String delim) {\r
-               return collectionToDelimitedString(coll, delim, "", "");\r
-       }\r
-\r
-       /**\r
-        * Convenience method to return a Collection as a CSV String.\r
-        * E.g. useful for <code>toString()</code> implementations.\r
-        * @param coll the Collection to display\r
-        * @return the delimited String\r
-        */\r
-       public static String collectionToCommaDelimitedString(Collection coll) {\r
-               return collectionToDelimitedString(coll, ",");\r
-       }\r
-\r
-       /**\r
-        * Convenience method to return a String array as a delimited (e.g. CSV)\r
-        * String. E.g. useful for <code>toString()</code> implementations.\r
-        * @param arr the array to display\r
-        * @param delim the delimiter to use (probably a ",")\r
-        * @return the delimited String\r
-        */\r
-       public static String arrayToDelimitedString(Object[] arr, String delim) {\r
-               if (ObjectUtils.isEmpty(arr)) {\r
-                       return "";\r
-               }\r
-               StringBuffer sb = new StringBuffer();\r
-               for (int i = 0; i < arr.length; i++) {\r
-                       if (i > 0) {\r
-                               sb.append(delim);\r
-                       }\r
-                       sb.append(arr[i]);\r
-               }\r
-               return sb.toString();\r
-       }\r
-\r
-       /**\r
-        * Convenience method to return a String array as a CSV String.\r
-        * E.g. useful for <code>toString()</code> implementations.\r
-        * @param arr the array to display\r
-        * @return the delimited String\r
-        */\r
-       public static String arrayToCommaDelimitedString(Object[] arr) {\r
-               return arrayToDelimitedString(arr, ",");\r
-       }\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/SystemPropertyUtils.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/SystemPropertyUtils.java
deleted file mode 100644 (file)
index ff81a22..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*\r
- * Copyright 2002-2008 the original author or authors.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package org.argeo.osgi.boot.internal.springutil;\r
-\r
-/**\r
- * Helper class for resolving placeholders in texts. Usually applied to file paths.\r
- *\r
- * <p>A text may contain <code>${...}</code> placeholders, to be resolved as\r
- * system properties: e.g. <code>${user.dir}</code>.\r
- *\r
- * @author Juergen Hoeller\r
- * @since 1.2.5\r
- * @see #PLACEHOLDER_PREFIX\r
- * @see #PLACEHOLDER_SUFFIX\r
- * @see System#getProperty(String)\r
- */\r
-public abstract class SystemPropertyUtils {\r
-\r
-       /** Prefix for system property placeholders: "${" */\r
-       public static final String PLACEHOLDER_PREFIX = "${";\r
-\r
-       /** Suffix for system property placeholders: "}" */\r
-       public static final String PLACEHOLDER_SUFFIX = "}";\r
-\r
-\r
-       /**\r
-        * Resolve ${...} placeholders in the given text,\r
-        * replacing them with corresponding system property values.\r
-        * @param text the String to resolve\r
-        * @return the resolved String\r
-        * @see #PLACEHOLDER_PREFIX\r
-        * @see #PLACEHOLDER_SUFFIX\r
-        */\r
-       @SuppressWarnings("unused")\r
-       public static String resolvePlaceholders(String text) {\r
-               StringBuffer buf = new StringBuffer(text);\r
-\r
-               int startIndex = buf.indexOf(PLACEHOLDER_PREFIX);\r
-               while (startIndex != -1) {\r
-                       int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());\r
-                       if (endIndex != -1) {\r
-                               String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);\r
-                               int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();\r
-                               try {\r
-                                       String propVal = System.getProperty(placeholder);\r
-                                       if (propVal == null) {\r
-                                               // Fall back to searching the system environment.\r
-                                               //propVal = System.getenv(placeholder);// mbaudier - 2009-07-26\r
-                                               throw new Error("getenv no longer supported, use properties and -D instead: " + placeholder);\r
-                                       }\r
-                                       if (propVal != null) {\r
-                                               buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);\r
-                                               nextIndex = startIndex + propVal.length();\r
-                                       }\r
-                                       else {\r
-                                               System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +\r
-                                                               "] as system property: neither system property nor environment variable found");\r
-                                       }\r
-                               }\r
-                               catch (Throwable ex) {\r
-                                       System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +\r
-                                                       "] as system property: " + ex);\r
-                               }\r
-                               startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);\r
-                       }\r
-                       else {\r
-                               startIndex = -1;\r
-                       }\r
-               }\r
-\r
-               return buf.toString();\r
-       }\r
-\r
-}\r
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/log4j.properties b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/log4j.properties
deleted file mode 100644 (file)
index 1fcf25e..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-log4j.rootLogger=WARN, console
-
-log4j.logger.org.argeo=INFO
-
-## Appenders
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
-
-log4j.appender.development=org.apache.log4j.ConsoleAppender
-log4j.appender.development.layout=org.apache.log4j.PatternLayout
-log4j.appender.development.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/node.policy b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/node.policy
deleted file mode 100644 (file)
index facb613..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-grant {
-  permission java.security.AllPermission;
-};
\ No newline at end of file
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/package-info.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/package-info.java
deleted file mode 100644 (file)
index 7474d13..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Simple OSGi initialisation. */
-package org.argeo.osgi.boot;
\ No newline at end of file
diff --git a/org.argeo.util/.classpath b/org.argeo.util/.classpath
new file mode 100644 (file)
index 0000000..71eb167
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+               <attributes>
+                       <attribute name="module" value="true"/>
+               </attributes>
+       </classpathentry>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/org.argeo.util/.project b/org.argeo.util/.project
new file mode 100644 (file)
index 0000000..171ff88
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.util</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+               <nature>org.eclipse.pde.PluginNature</nature>
+       </natures>
+</projectDescription>
diff --git a/org.argeo.util/META-INF/.gitignore b/org.argeo.util/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/org.argeo.util/bnd.bnd b/org.argeo.util/bnd.bnd
new file mode 100644 (file)
index 0000000..5f42f77
--- /dev/null
@@ -0,0 +1,6 @@
+Bundle-Activator: org.argeo.osgi.internal.EnterpriseActivator
+Bundle-ActivationPolicy: lazy
+
+Import-Package:        org.osgi.*;version=0.0.0,\
+!org.apache.commons.logging,\
+*                              
diff --git a/org.argeo.util/build.properties b/org.argeo.util/build.properties
new file mode 100644 (file)
index 0000000..ae2abc5
--- /dev/null
@@ -0,0 +1 @@
+source.. = src/
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java b/org.argeo.util/src/org/argeo/osgi/internal/EnterpriseActivator.java
new file mode 100644 (file)
index 0000000..bb495dd
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.osgi.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Called to gather information about the OSGi runtime. Should not activate
+ * anything else that canonical monitoring services (not creating implicit
+ * APIs), which is the responsibility of higher levels.
+ */
+public class EnterpriseActivator implements BundleActivator {
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumAD.java
new file mode 100644 (file)
index 0000000..0fc4f32
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.osgi.metatype;
+
+import org.argeo.util.naming.SpecifiedName;
+import org.osgi.service.metatype.AttributeDefinition;
+
+public interface EnumAD extends SpecifiedName, AttributeDefinition {
+       String name();
+
+       default Object getDefault() {
+               return null;
+       }
+
+       @Override
+       default String getName() {
+               return name();
+       }
+
+       @Override
+       default String getID() {
+               return getClass().getName() + "." + name();
+       }
+
+       @Override
+       default String getDescription() {
+               return null;
+       }
+
+       @Override
+       default int getCardinality() {
+               return 0;
+       }
+
+       @Override
+       default int getType() {
+               return STRING;
+       }
+
+       @Override
+       default String[] getOptionValues() {
+               return null;
+       }
+
+       @Override
+       default String[] getOptionLabels() {
+               return null;
+       }
+
+       @Override
+       default String validate(String value) {
+               return null;
+       }
+
+       @Override
+       default String[] getDefaultValue() {
+               Object value = getDefault();
+               if (value == null)
+                       return null;
+               return new String[] { value.toString() };
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java b/org.argeo.util/src/org/argeo/osgi/metatype/EnumOCD.java
new file mode 100644 (file)
index 0000000..97c7d56
--- /dev/null
@@ -0,0 +1,54 @@
+package org.argeo.osgi.metatype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+public class EnumOCD<T extends Enum<T>> implements ObjectClassDefinition {
+       private final Class<T> enumClass;
+       private String locale;
+
+       public EnumOCD(Class<T> clazz, String locale) {
+               this.enumClass = clazz;
+               this.locale = locale;
+       }
+
+       @Override
+       public String getName() {
+               return null;
+       }
+
+       public String getLocale() {
+               return locale;
+       }
+
+       @Override
+       public String getID() {
+               return enumClass.getName();
+       }
+
+       @Override
+       public String getDescription() {
+               return null;
+       }
+
+       @Override
+       public AttributeDefinition[] getAttributeDefinitions(int filter) {
+               EnumSet<T> set = EnumSet.allOf(enumClass);
+               List<AttributeDefinition> attrs = new ArrayList<>();
+               for (T key : set)
+                       attrs.add((AttributeDefinition) key);
+               return attrs.toArray(new AttributeDefinition[attrs.size()]);
+       }
+
+       @Override
+       public InputStream getIcon(int size) throws IOException {
+               return null;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java b/org.argeo.util/src/org/argeo/osgi/metatype/package-info.java
new file mode 100644 (file)
index 0000000..bca5d1f
--- /dev/null
@@ -0,0 +1,2 @@
+/** OSGi metatype support. */
+package org.argeo.osgi.metatype;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java b/org.argeo.util/src/org/argeo/osgi/provisioning/SimpleProvisioningService.java
new file mode 100644 (file)
index 0000000..c0ec290
--- /dev/null
@@ -0,0 +1,118 @@
+package org.argeo.osgi.provisioning;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.ZipInputStream;
+
+import org.osgi.service.provisioning.ProvisioningService;
+
+public class SimpleProvisioningService implements ProvisioningService {
+       private Map<String, Object> map = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       public SimpleProvisioningService() {
+               // update count
+               map.put(PROVISIONING_UPDATE_COUNT, 0);
+       }
+
+       @Override
+       public Dictionary<String, Object> getInformation() {
+               return new Information();
+       }
+
+       @Override
+       public synchronized void setInformation(Dictionary<String, ?> info) {
+               map.clear();
+               addInformation(info);
+       }
+
+       @Override
+       public synchronized void addInformation(Dictionary<String, ?> info) {
+               Enumeration<String> e = info.keys();
+               while (e.hasMoreElements()) {
+                       String key = e.nextElement();
+                       map.put(key, info.get(key));
+               }
+               incrementProvisioningUpdateCount();
+       }
+
+       protected synchronized void incrementProvisioningUpdateCount() {
+               Integer current = (Integer) map.get(PROVISIONING_UPDATE_COUNT);
+               Integer newValue = current + 1;
+               map.put(PROVISIONING_UPDATE_COUNT, newValue);
+       }
+
+       @Override
+       public synchronized void addInformation(ZipInputStream zis) throws IOException {
+               throw new UnsupportedOperationException();
+       }
+
+       class Information extends Dictionary<String, Object> {
+
+               @Override
+               public int size() {
+                       return map.size();
+               }
+
+               @Override
+               public boolean isEmpty() {
+                       return map.isEmpty();
+               }
+
+               @Override
+               public Enumeration<String> keys() {
+                       Iterator<String> it = map.keySet().iterator();
+                       return new Enumeration<String>() {
+
+                               @Override
+                               public boolean hasMoreElements() {
+                                       return it.hasNext();
+                               }
+
+                               @Override
+                               public String nextElement() {
+                                       return it.next();
+                               }
+
+                       };
+               }
+
+               @Override
+               public Enumeration<Object> elements() {
+                       Iterator<Object> it = map.values().iterator();
+                       return new Enumeration<Object>() {
+
+                               @Override
+                               public boolean hasMoreElements() {
+                                       return it.hasNext();
+                               }
+
+                               @Override
+                               public Object nextElement() {
+                                       return it.next();
+                               }
+
+                       };
+               }
+
+               @Override
+               public Object get(Object key) {
+                       return map.get(key);
+               }
+
+               @Override
+               public Object put(String key, Object value) {
+                       throw new UnsupportedOperationException();
+               }
+
+               @Override
+               public Object remove(Object key) {
+                       throw new UnsupportedOperationException();
+               }
+
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java b/org.argeo.util/src/org/argeo/osgi/provisioning/package-info.java
new file mode 100644 (file)
index 0000000..1859887
--- /dev/null
@@ -0,0 +1,2 @@
+/** OSGi provisioning support. */
+package org.argeo.osgi.provisioning;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/JtaStatusAdapter.java
new file mode 100644 (file)
index 0000000..2dd94c6
--- /dev/null
@@ -0,0 +1,61 @@
+package org.argeo.osgi.transaction;
+
+/** JTA transaction status. */
+public class JtaStatusAdapter implements TransactionStatusAdapter<Integer> {
+       private static final Integer STATUS_ACTIVE = 0;
+       private static final Integer STATUS_COMMITTED = 3;
+       private static final Integer STATUS_COMMITTING = 8;
+       private static final Integer STATUS_MARKED_ROLLBACK = 1;
+       private static final Integer STATUS_NO_TRANSACTION = 6;
+       private static final Integer STATUS_PREPARED = 2;
+       private static final Integer STATUS_PREPARING = 7;
+       private static final Integer STATUS_ROLLEDBACK = 4;
+       private static final Integer STATUS_ROLLING_BACK = 9;
+//     private static final Integer STATUS_UNKNOWN = 5;
+
+       @Override
+       public Integer getActiveStatus() {
+               return STATUS_ACTIVE;
+       }
+
+       @Override
+       public Integer getPreparingStatus() {
+               return STATUS_PREPARING;
+       }
+
+       @Override
+       public Integer getMarkedRollbackStatus() {
+               return STATUS_MARKED_ROLLBACK;
+       }
+
+       @Override
+       public Integer getPreparedStatus() {
+               return STATUS_PREPARED;
+       }
+
+       @Override
+       public Integer getCommittingStatus() {
+               return STATUS_COMMITTING;
+       }
+
+       @Override
+       public Integer getCommittedStatus() {
+               return STATUS_COMMITTED;
+       }
+
+       @Override
+       public Integer getRollingBackStatus() {
+               return STATUS_ROLLING_BACK;
+       }
+
+       @Override
+       public Integer getRolledBackStatus() {
+               return STATUS_ROLLEDBACK;
+       }
+
+       @Override
+       public Integer getNoTransactionStatus() {
+               return STATUS_NO_TRANSACTION;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleRollbackException.java
new file mode 100644 (file)
index 0000000..cf8a80b
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.osgi.transaction;
+
+/** Internal unchecked rollback exception. */
+class SimpleRollbackException extends RuntimeException {
+       private static final long serialVersionUID = 8055601819719780566L;
+
+       public SimpleRollbackException() {
+               super();
+       }
+
+       public SimpleRollbackException(Throwable cause) {
+               super(cause);
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransaction.java
new file mode 100644 (file)
index 0000000..e668b31
--- /dev/null
@@ -0,0 +1,160 @@
+package org.argeo.osgi.transaction;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** Simple implementation of an XA transaction. */
+class SimpleTransaction<T>
+//implements Transaction, Status 
+{
+       private final Xid xid;
+       private T status;
+       private final List<XAResource> xaResources = new ArrayList<XAResource>();
+
+       private final SimpleTransactionManager transactionManager;
+       private TransactionStatusAdapter<T> tsa;
+
+       public SimpleTransaction(SimpleTransactionManager transactionManager, TransactionStatusAdapter<T> tsa) {
+               this.tsa = tsa;
+               this.status = tsa.getActiveStatus();
+               this.xid = new UuidXid();
+               this.transactionManager = transactionManager;
+       }
+
+       public synchronized void commit()
+//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+//                     SecurityException, IllegalStateException, SystemException 
+       {
+               status = tsa.getPreparingStatus();
+               for (XAResource xaRes : xaResources) {
+                       if (status.equals(tsa.getMarkedRollbackStatus()))
+                               break;
+                       try {
+                               xaRes.prepare(xid);
+                       } catch (XAException e) {
+                               status = tsa.getMarkedRollbackStatus();
+                               error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status.equals(tsa.getMarkedRollbackStatus())) {
+                       rollback();
+                       throw new SimpleRollbackException();
+               }
+               status = tsa.getPreparedStatus();
+
+               status = tsa.getCommittingStatus();
+               for (XAResource xaRes : xaResources) {
+                       if (status.equals(tsa.getMarkedRollbackStatus()))
+                               break;
+                       try {
+                               xaRes.commit(xid, false);
+                       } catch (XAException e) {
+                               status = tsa.getMarkedRollbackStatus();
+                               error("Cannot prepare " + xaRes + " for " + xid, e);
+                       }
+               }
+               if (status.equals(tsa.getMarkedRollbackStatus())) {
+                       rollback();
+                       throw new SimpleRollbackException();
+               }
+
+               // complete
+               status = tsa.getCommittedStatus();
+               clearResources(XAResource.TMSUCCESS);
+               transactionManager.unregister(xid);
+       }
+
+       public synchronized void rollback()
+//                     throws IllegalStateException, SystemException 
+       {
+               status = tsa.getRollingBackStatus();
+               for (XAResource xaRes : xaResources) {
+                       try {
+                               xaRes.rollback(xid);
+                       } catch (XAException e) {
+                               error("Cannot rollback " + xaRes + " for " + xid, e);
+                       }
+               }
+
+               // complete
+               status = tsa.getRolledBackStatus();
+               clearResources(XAResource.TMFAIL);
+               transactionManager.unregister(xid);
+       }
+
+       public synchronized boolean enlistResource(XAResource xaRes)
+//                     throws RollbackException, IllegalStateException, SystemException 
+       {
+               if (xaResources.add(xaRes)) {
+                       try {
+                               xaRes.start(getXid(), XAResource.TMNOFLAGS);
+                               return true;
+                       } catch (XAException e) {
+                               error("Cannot enlist " + xaRes, e);
+                               return false;
+                       }
+               } else
+                       return false;
+       }
+
+       public synchronized boolean delistResource(XAResource xaRes, int flag)
+//                     throws IllegalStateException, SystemException 
+       {
+               if (xaResources.remove(xaRes)) {
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               error("Cannot delist " + xaRes, e);
+                               return false;
+                       }
+                       return true;
+               } else
+                       return false;
+       }
+
+       protected void clearResources(int flag) {
+               for (XAResource xaRes : xaResources)
+                       try {
+                               xaRes.end(getXid(), flag);
+                       } catch (XAException e) {
+                               error("Cannot end " + xaRes, e);
+                       }
+               xaResources.clear();
+       }
+
+       protected void error(Object obj, Exception e) {
+               System.err.println(obj);
+               e.printStackTrace();
+       }
+
+       public synchronized T getStatus()
+//                     throws SystemException 
+       {
+               return status;
+       }
+
+//     public void registerSynchronization(Synchronization sync)
+//                     throws RollbackException, IllegalStateException, SystemException {
+//             throw new UnsupportedOperationException();
+//     }
+
+       public void setRollbackOnly()
+//                     throws IllegalStateException, SystemException 
+       {
+               status = tsa.getMarkedRollbackStatus();
+       }
+
+       @Override
+       public int hashCode() {
+               return xid.hashCode();
+       }
+
+       Xid getXid() {
+               return xid;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java b/org.argeo.util/src/org/argeo/osgi/transaction/SimpleTransactionManager.java
new file mode 100644 (file)
index 0000000..3d4edfd
--- /dev/null
@@ -0,0 +1,214 @@
+package org.argeo.osgi.transaction;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * Simple implementation of an XA transaction manager.
+ */
+public class SimpleTransactionManager
+// implements TransactionManager, UserTransaction 
+               implements WorkControl, WorkTransaction {
+       private ThreadLocal<SimpleTransaction<Integer>> current = new ThreadLocal<SimpleTransaction<Integer>>();
+
+       private Map<Xid, SimpleTransaction<Integer>> knownTransactions = Collections
+                       .synchronizedMap(new HashMap<Xid, SimpleTransaction<Integer>>());
+       private TransactionStatusAdapter<Integer> tsa = new JtaStatusAdapter();
+//     private SyncRegistry syncRegistry = new SyncRegistry();
+
+       /*
+        * WORK IMPLEMENTATION
+        */
+       @Override
+       public <T> T required(Callable<T> work) {
+               T res;
+               begin();
+               try {
+                       res = work.call();
+                       commit();
+               } catch (Exception e) {
+                       rollback();
+                       throw new SimpleRollbackException(e);
+               }
+               return res;
+       }
+
+       @Override
+       public WorkContext getWorkContext() {
+               return new WorkContext() {
+
+                       @Override
+                       public void registerXAResource(XAResource resource, String recoveryId) {
+                               getTransaction().enlistResource(resource);
+                       }
+               };
+       }
+
+       /*
+        * WORK TRANSACTION IMPLEMENTATION
+        */
+
+       @Override
+       public boolean isNoTransactionStatus() {
+               return tsa.getNoTransactionStatus().equals(getStatus());
+       }
+
+       /*
+        * JTA IMPLEMENTATION
+        */
+
+       public void begin()
+//                     throws NotSupportedException, SystemException 
+       {
+               if (getCurrent() != null)
+                       throw new UnsupportedOperationException("Nested transactions are not supported");
+               SimpleTransaction<Integer> transaction = new SimpleTransaction<Integer>(this, tsa);
+               knownTransactions.put(transaction.getXid(), transaction);
+               current.set(transaction);
+       }
+
+       public void commit()
+//                     throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
+//                     SecurityException, IllegalStateException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().commit();
+       }
+
+       public int getStatus()
+//                     throws SystemException
+       {
+               if (getCurrent() == null)
+                       return tsa.getNoTransactionStatus();
+               return getTransaction().getStatus();
+       }
+
+       public SimpleTransaction<Integer> getTransaction()
+//                     throws SystemException 
+       {
+               return getCurrent();
+       }
+
+       protected SimpleTransaction<Integer> getCurrent()
+//                     throws SystemException 
+       {
+               SimpleTransaction<Integer> transaction = current.get();
+               if (transaction == null)
+                       return null;
+               Integer status = transaction.getStatus();
+               if (status.equals(tsa.getCommittedStatus()) || status.equals(tsa.getRolledBackStatus())) {
+                       current.remove();
+                       return null;
+               }
+               return transaction;
+       }
+
+       void unregister(Xid xid) {
+               knownTransactions.remove(xid);
+       }
+
+       public void resume(SimpleTransaction<Integer> tobj)
+//                     throws InvalidTransactionException, IllegalStateException, SystemException 
+       {
+               if (getCurrent() != null)
+                       throw new IllegalStateException("Transaction " + current.get() + " already registered");
+               current.set(tobj);
+       }
+
+       public void rollback()
+//                     throws IllegalStateException, SecurityException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().rollback();
+       }
+
+       public void setRollbackOnly()
+//                     throws IllegalStateException, SystemException 
+       {
+               if (getCurrent() == null)
+                       throw new IllegalStateException("No transaction registered with the current thread.");
+               getCurrent().setRollbackOnly();
+       }
+
+       public void setTransactionTimeout(int seconds)
+//                     throws SystemException
+       {
+               throw new UnsupportedOperationException();
+       }
+
+       public SimpleTransaction<Integer> suspend()
+//                     throws SystemException
+       {
+               SimpleTransaction<Integer> transaction = getCurrent();
+               current.remove();
+               return transaction;
+       }
+
+//     public TransactionSynchronizationRegistry getTsr() {
+//             return syncRegistry;
+//     }
+//
+//     private class SyncRegistry implements TransactionSynchronizationRegistry {
+//             @Override
+//             public Object getTransactionKey() {
+//                     try {
+//                             SimpleTransaction transaction = getCurrent();
+//                             if (transaction == null)
+//                                     return null;
+//                             return getCurrent().getXid();
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get transaction key", e);
+//                     }
+//             }
+//
+//             @Override
+//             public void putResource(Object key, Object value) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public Object getResource(Object key) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public void registerInterposedSynchronization(Synchronization sync) {
+//                     throw new UnsupportedOperationException();
+//             }
+//
+//             @Override
+//             public int getTransactionStatus() {
+//                     try {
+//                             return getStatus();
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get status", e);
+//                     }
+//             }
+//
+//             @Override
+//             public boolean getRollbackOnly() {
+//                     try {
+//                             return getStatus() == Status.STATUS_MARKED_ROLLBACK;
+//                     } catch (SystemException e) {
+//                             throw new IllegalStateException("Cannot get status", e);
+//                     }
+//             }
+//
+//             @Override
+//             public void setRollbackOnly() {
+//                     try {
+//                             getCurrent().setRollbackOnly();
+//                     } catch (Exception e) {
+//                             throw new IllegalStateException("Cannot set rollback only", e);
+//                     }
+//             }
+//
+//     }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java b/org.argeo.util/src/org/argeo/osgi/transaction/TransactionStatusAdapter.java
new file mode 100644 (file)
index 0000000..87abceb
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.osgi.transaction;
+
+/** Abstract the various approaches to represent transaction status. */
+public interface TransactionStatusAdapter<T> {
+       T getActiveStatus();
+
+       T getPreparingStatus();
+
+       T getMarkedRollbackStatus();
+
+       T getPreparedStatus();
+
+       T getCommittingStatus();
+
+       T getCommittedStatus();
+
+       T getRollingBackStatus();
+
+       T getRolledBackStatus();
+
+       T getNoTransactionStatus();
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java b/org.argeo.util/src/org/argeo/osgi/transaction/UuidXid.java
new file mode 100644 (file)
index 0000000..729aef8
--- /dev/null
@@ -0,0 +1,132 @@
+package org.argeo.osgi.transaction;
+
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.UUID;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Implementation of {@link Xid} based on {@link UUID}, using max significant
+ * bits as global transaction id, and least significant bits as branch
+ * qualifier.
+ */
+public class UuidXid implements Xid, Serializable {
+       private static final long serialVersionUID = -5380531989917886819L;
+       public final static int FORMAT = (int) serialVersionUID;
+
+       private final static int BYTES_PER_LONG = Long.SIZE / Byte.SIZE;
+
+       private final int format;
+       private final byte[] globalTransactionId;
+       private final byte[] branchQualifier;
+       private final String uuid;
+       private final int hashCode;
+
+       public UuidXid() {
+               this(UUID.randomUUID());
+       }
+
+       public UuidXid(UUID uuid) {
+               this.format = FORMAT;
+               this.globalTransactionId = uuidToBytes(uuid.getMostSignificantBits());
+               this.branchQualifier = uuidToBytes(uuid.getLeastSignificantBits());
+               this.uuid = uuid.toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       public UuidXid(Xid xid) {
+               this(xid.getFormatId(), xid.getGlobalTransactionId(), xid
+                               .getBranchQualifier());
+       }
+
+       private UuidXid(int format, byte[] globalTransactionId,
+                       byte[] branchQualifier) {
+               this.format = format;
+               this.globalTransactionId = globalTransactionId;
+               this.branchQualifier = branchQualifier;
+               this.uuid = bytesToUUID(globalTransactionId, branchQualifier)
+                               .toString();
+               this.hashCode = uuid.hashCode();
+       }
+
+       @Override
+       public int getFormatId() {
+               return format;
+       }
+
+       @Override
+       public byte[] getGlobalTransactionId() {
+               return Arrays.copyOf(globalTransactionId, globalTransactionId.length);
+       }
+
+       @Override
+       public byte[] getBranchQualifier() {
+               return Arrays.copyOf(branchQualifier, branchQualifier.length);
+       }
+
+       @Override
+       public int hashCode() {
+               return hashCode;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj instanceof UuidXid) {
+                       UuidXid that = (UuidXid) obj;
+                       return Arrays.equals(globalTransactionId, that.globalTransactionId)
+                                       && Arrays.equals(branchQualifier, that.branchQualifier);
+               }
+               if (obj instanceof Xid) {
+                       Xid that = (Xid) obj;
+                       return Arrays.equals(globalTransactionId,
+                                       that.getGlobalTransactionId())
+                                       && Arrays
+                                                       .equals(branchQualifier, that.getBranchQualifier());
+               }
+               return uuid.equals(obj.toString());
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               return new UuidXid(format, globalTransactionId, branchQualifier);
+       }
+
+       @Override
+       public String toString() {
+               return uuid;
+       }
+
+       public UUID asUuid() {
+               return bytesToUUID(globalTransactionId, branchQualifier);
+       }
+
+       public static byte[] uuidToBytes(long bits) {
+               ByteBuffer buffer = ByteBuffer.allocate(BYTES_PER_LONG);
+               buffer.putLong(0, bits);
+               return buffer.array();
+       }
+
+       public static UUID bytesToUUID(byte[] most, byte[] least) {
+               if (most.length < BYTES_PER_LONG)
+                       most = Arrays.copyOf(most, BYTES_PER_LONG);
+               if (least.length < BYTES_PER_LONG)
+                       least = Arrays.copyOf(least, BYTES_PER_LONG);
+               ByteBuffer buffer = ByteBuffer.allocate(2 * BYTES_PER_LONG);
+               buffer.put(most, 0, BYTES_PER_LONG);
+               buffer.put(least, 0, BYTES_PER_LONG);
+               buffer.flip();
+               return new UUID(buffer.getLong(), buffer.getLong());
+       }
+
+       // public static void main(String[] args) {
+       // UUID uuid = UUID.randomUUID();
+       // System.out.println(uuid);
+       // uuid = bytesToUUID(uuidToBytes(uuid.getMostSignificantBits()),
+       // uuidToBytes(uuid.getLeastSignificantBits()));
+       // System.out.println(uuid);
+       // }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkContext.java
new file mode 100644 (file)
index 0000000..f50f208
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.osgi.transaction;
+
+import javax.transaction.xa.XAResource;
+
+/**
+ * A minimalistic interface similar to OSGi transaction context in order to
+ * register XA resources.
+ */
+public interface WorkContext {
+       void registerXAResource(XAResource resource, String recoveryId);
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkControl.java
new file mode 100644 (file)
index 0000000..7668095
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.osgi.transaction;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A minimalistic interface inspired by OSGi transaction control in order to
+ * commit units of work externally.
+ */
+public interface WorkControl {
+       <T> T required(Callable<T> work);
+
+       void setRollbackOnly();
+
+       WorkContext getWorkContext();
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java b/org.argeo.util/src/org/argeo/osgi/transaction/WorkTransaction.java
new file mode 100644 (file)
index 0000000..6533909
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.osgi.transaction;
+
+/**
+ * A minimalistic interface inspired by JTA user transaction in order to commit
+ * units of work externally.
+ */
+public interface WorkTransaction {
+       void begin();
+
+       void commit();
+
+       void rollback();
+
+       boolean isNoTransactionStatus();
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java b/org.argeo.util/src/org/argeo/osgi/transaction/package-info.java
new file mode 100644 (file)
index 0000000..3d37562
--- /dev/null
@@ -0,0 +1,2 @@
+/** Minimalistic and partial XA transaction manager implementation. */
+package org.argeo.osgi.transaction;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
new file mode 100644 (file)
index 0000000..e028e38
--- /dev/null
@@ -0,0 +1,517 @@
+package org.argeo.osgi.useradmin;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapObjs.extensibleObject;
+import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
+import static org.argeo.util.naming.LdapObjs.organizationalPerson;
+import static org.argeo.util.naming.LdapObjs.person;
+import static org.argeo.util.naming.LdapObjs.top;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.osgi.transaction.WorkControl;
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/** Base class for a {@link UserDirectory}. */
+public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory {
+       static final String SHARED_STATE_USERNAME = "javax.security.auth.login.name";
+       static final String SHARED_STATE_PASSWORD = "javax.security.auth.login.password";
+
+       private final Hashtable<String, Object> properties;
+       private final LdapName baseDn, userBaseDn, groupBaseDn;
+       private final String userObjectClass, userBase, groupObjectClass, groupBase;
+
+       private final boolean readOnly;
+       private final boolean disabled;
+       private final String uri;
+
+       private UserAdmin externalRoles;
+       // private List<String> indexedUserProperties = Arrays
+       // .asList(new String[] { LdapAttrs.uid.name(), LdapAttrs.mail.name(),
+       // LdapAttrs.cn.name() });
+
+       private final boolean scoped;
+
+       private String memberAttributeId = "member";
+       private List<String> credentialAttributeIds = Arrays
+                       .asList(new String[] { LdapAttrs.userPassword.name(), LdapAttrs.authPassword.name() });
+
+       // Transaction
+//     private TransactionManager transactionManager;
+       private WorkControl transactionControl;
+       private WcXaResource xaResource = new WcXaResource(this);
+
+       private String forcedPassword;
+
+       AbstractUserDirectory(URI uriArg, Dictionary<String, ?> props, boolean scoped) {
+               this.scoped = scoped;
+               properties = new Hashtable<String, Object>();
+               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                       String key = keys.nextElement();
+                       properties.put(key, props.get(key));
+               }
+
+               if (uriArg != null) {
+                       uri = uriArg.toString();
+                       // uri from properties is ignored
+               } else {
+                       String uriStr = UserAdminConf.uri.getValue(properties);
+                       if (uriStr == null)
+                               uri = null;
+                       else
+                               uri = uriStr;
+               }
+
+               forcedPassword = UserAdminConf.forcedPassword.getValue(properties);
+
+               userObjectClass = UserAdminConf.userObjectClass.getValue(properties);
+               userBase = UserAdminConf.userBase.getValue(properties);
+               groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties);
+               groupBase = UserAdminConf.groupBase.getValue(properties);
+               try {
+                       baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties));
+                       userBaseDn = new LdapName(userBase + "," + baseDn);
+                       groupBaseDn = new LdapName(groupBase + "," + baseDn);
+               } catch (InvalidNameException e) {
+                       throw new UserDirectoryException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e);
+               }
+               String readOnlyStr = UserAdminConf.readOnly.getValue(properties);
+               if (readOnlyStr == null) {
+                       readOnly = readOnlyDefault(uri);
+                       properties.put(UserAdminConf.readOnly.name(), Boolean.toString(readOnly));
+               } else
+                       readOnly = Boolean.parseBoolean(readOnlyStr);
+               String disabledStr = UserAdminConf.disabled.getValue(properties);
+               if (disabledStr != null)
+                       disabled = Boolean.parseBoolean(disabledStr);
+               else
+                       disabled = false;
+       }
+
+       /** Returns the groups this user is a direct member of. */
+       protected abstract List<LdapName> getDirectGroups(LdapName dn);
+
+       protected abstract Boolean daoHasRole(LdapName dn);
+
+       protected abstract DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException;
+
+       protected abstract List<DirectoryUser> doGetRoles(Filter f);
+
+       protected abstract AbstractUserDirectory scope(User user);
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+
+       }
+
+       protected boolean isEditing() {
+               return xaResource.wc() != null;
+       }
+
+       protected UserDirectoryWorkingCopy getWorkingCopy() {
+               UserDirectoryWorkingCopy wc = xaResource.wc();
+               if (wc == null)
+                       return null;
+               return wc;
+       }
+
+       protected void checkEdit() {
+//             Transaction transaction;
+//             try {
+//                     transaction = transactionManager.getTransaction();
+//             } catch (SystemException e) {
+//                     throw new UserDirectoryException("Cannot get transaction", e);
+//             }
+//             if (transaction == null)
+//                     throw new UserDirectoryException("A transaction needs to be active in order to edit");
+               if (xaResource.wc() == null) {
+                       try {
+//                             transaction.enlistResource(xaResource);
+                               transactionControl.getWorkContext().registerXAResource(xaResource, null);
+                       } catch (Exception e) {
+                               throw new UserDirectoryException("Cannot enlist " + xaResource, e);
+                       }
+               } else {
+               }
+       }
+
+       protected List<Role> getAllRoles(DirectoryUser user) {
+               List<Role> allRoles = new ArrayList<Role>();
+               if (user != null) {
+                       collectRoles(user, allRoles);
+                       allRoles.add(user);
+               } else
+                       collectAnonymousRoles(allRoles);
+               return allRoles;
+       }
+
+       private void collectRoles(DirectoryUser user, List<Role> allRoles) {
+               Attributes attrs = user.getAttributes();
+               // TODO centralize attribute name
+               Attribute memberOf = attrs.get(LdapAttrs.memberOf.name());
+               // if user belongs to this directory, we only check meberOf
+               if (memberOf != null && user.getDn().startsWith(getBaseDn())) {
+                       try {
+                               NamingEnumeration<?> values = memberOf.getAll();
+                               while (values.hasMore()) {
+                                       Object value = values.next();
+                                       LdapName groupDn = new LdapName(value.toString());
+                                       DirectoryUser group = doGetRole(groupDn);
+                                       if (group != null)
+                                               allRoles.add(group);
+                               }
+                       } catch (Exception e) {
+                               throw new UserDirectoryException("Cannot get memberOf groups for " + user, e);
+                       }
+               } else {
+                       for (LdapName groupDn : getDirectGroups(user.getDn())) {
+                               // TODO check for loops
+                               DirectoryUser group = doGetRole(groupDn);
+                               if (group != null) {
+                                       allRoles.add(group);
+                                       collectRoles(group, allRoles);
+                               }
+                       }
+               }
+       }
+
+       private void collectAnonymousRoles(List<Role> allRoles) {
+               // TODO gather anonymous roles
+       }
+
+       // USER ADMIN
+       @Override
+       public Role getRole(String name) {
+               return doGetRole(toDn(name));
+       }
+
+       protected DirectoryUser doGetRole(LdapName dn) {
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               DirectoryUser user;
+               try {
+                       user = daoGetRole(dn);
+               } catch (NameNotFoundException e) {
+                       user = null;
+               }
+               if (wc != null) {
+                       if (user == null && wc.getNewUsers().containsKey(dn))
+                               user = wc.getNewUsers().get(dn);
+                       else if (wc.getDeletedUsers().containsKey(dn))
+                               user = null;
+               }
+               return user;
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               Filter f = filter != null ? FrameworkUtil.createFilter(filter) : null;
+               List<DirectoryUser> res = doGetRoles(f);
+               if (wc != null) {
+                       for (Iterator<DirectoryUser> it = res.iterator(); it.hasNext();) {
+                               DirectoryUser user = it.next();
+                               LdapName dn = user.getDn();
+                               if (wc.getDeletedUsers().containsKey(dn))
+                                       it.remove();
+                       }
+                       for (DirectoryUser user : wc.getNewUsers().values()) {
+                               if (f == null || f.match(user.getProperties()))
+                                       res.add(user);
+                       }
+                       // no need to check modified users,
+                       // since doGetRoles was already based on the modified attributes
+               }
+               return res.toArray(new Role[res.size()]);
+       }
+
+       @Override
+       public User getUser(String key, String value) {
+               // TODO check value null or empty
+               List<DirectoryUser> collectedUsers = new ArrayList<DirectoryUser>();
+               if (key != null) {
+                       doGetUser(key, value, collectedUsers);
+               } else {
+                       throw new UserDirectoryException("Key cannot be null");
+               }
+
+               if (collectedUsers.size() == 1) {
+                       return collectedUsers.get(0);
+               } else if (collectedUsers.size() > 1) {
+                       // log.warn(collectedUsers.size() + " users for " + (key != null ? key + "=" :
+                       // "") + value);
+               }
+               return null;
+       }
+
+       protected void doGetUser(String key, String value, List<DirectoryUser> collectedUsers) {
+               try {
+                       Filter f = FrameworkUtil.createFilter("(" + key + "=" + value + ")");
+                       List<DirectoryUser> users = doGetRoles(f);
+                       collectedUsers.addAll(users);
+               } catch (InvalidSyntaxException e) {
+                       throw new UserDirectoryException("Cannot get user with " + key + "=" + value, e);
+               }
+       }
+
+       @Override
+       public Authorization getAuthorization(User user) {
+               if (user == null || user instanceof DirectoryUser) {
+                       return new LdifAuthorization(user, getAllRoles((DirectoryUser) user));
+               } else {
+                       // bind
+                       AbstractUserDirectory scopedUserAdmin = scope(user);
+                       try {
+                               DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
+                               if (directoryUser == null)
+                                       throw new UserDirectoryException("No scoped user found for " + user);
+                               LdifAuthorization authorization = new LdifAuthorization(directoryUser,
+                                               scopedUserAdmin.getAllRoles(directoryUser));
+                               return authorization;
+                       } finally {
+                               scopedUserAdmin.destroy();
+                       }
+               }
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               checkEdit();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = toDn(name);
+               if ((daoHasRole(dn) && !wc.getDeletedUsers().containsKey(dn)) || wc.getNewUsers().containsKey(dn))
+                       throw new UserDirectoryException("Already a role " + name);
+               BasicAttributes attrs = new BasicAttributes(true);
+               // attrs.put(LdifName.dn.name(), dn.toString());
+               Rdn nameRdn = dn.getRdn(dn.size() - 1);
+               // TODO deal with multiple attr RDN
+               attrs.put(nameRdn.getType(), nameRdn.getValue());
+               if (wc.getDeletedUsers().containsKey(dn)) {
+                       wc.getDeletedUsers().remove(dn);
+                       wc.getModifiedUsers().put(dn, attrs);
+                       return getRole(name);
+               } else {
+                       wc.getModifiedUsers().put(dn, attrs);
+                       DirectoryUser newRole = newRole(dn, type, attrs);
+                       wc.getNewUsers().put(dn, newRole);
+                       return newRole;
+               }
+       }
+
+       protected DirectoryUser newRole(LdapName dn, int type, Attributes attrs) {
+               LdifUser newRole;
+               BasicAttribute objClass = new BasicAttribute(objectClass.name());
+               if (type == Role.USER) {
+                       String userObjClass = newUserObjectClass(dn);
+                       objClass.add(userObjClass);
+                       if (inetOrgPerson.name().equals(userObjClass)) {
+                               objClass.add(organizationalPerson.name());
+                               objClass.add(person.name());
+                       } else if (organizationalPerson.name().equals(userObjClass)) {
+                               objClass.add(person.name());
+                       }
+                       objClass.add(top.name());
+                       objClass.add(extensibleObject.name());
+                       attrs.put(objClass);
+                       newRole = new LdifUser(this, dn, attrs);
+               } else if (type == Role.GROUP) {
+                       String groupObjClass = getGroupObjectClass();
+                       objClass.add(groupObjClass);
+                       // objClass.add(LdifName.extensibleObject.name());
+                       objClass.add(top.name());
+                       attrs.put(objClass);
+                       newRole = new LdifGroup(this, dn, attrs);
+               } else
+                       throw new UserDirectoryException("Unsupported type " + type);
+               return newRole;
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               checkEdit();
+               UserDirectoryWorkingCopy wc = getWorkingCopy();
+               LdapName dn = toDn(name);
+               boolean actuallyDeleted;
+               if (daoHasRole(dn) || wc.getNewUsers().containsKey(dn)) {
+                       DirectoryUser user = (DirectoryUser) getRole(name);
+                       wc.getDeletedUsers().put(dn, user);
+                       actuallyDeleted = true;
+               } else {// just removing from groups (e.g. system roles)
+                       actuallyDeleted = false;
+               }
+               for (LdapName groupDn : getDirectGroups(dn)) {
+                       DirectoryUser group = doGetRole(groupDn);
+                       group.getAttributes().get(getMemberAttributeId()).remove(dn.toString());
+               }
+               return actuallyDeleted;
+       }
+
+       // TRANSACTION
+       protected void prepare(UserDirectoryWorkingCopy wc) {
+
+       }
+
+       protected void commit(UserDirectoryWorkingCopy wc) {
+
+       }
+
+       protected void rollback(UserDirectoryWorkingCopy wc) {
+
+       }
+
+       // UTILITIES
+       protected LdapName toDn(String name) {
+               try {
+                       return new LdapName(name);
+               } catch (InvalidNameException e) {
+                       throw new UserDirectoryException("Badly formatted name", e);
+               }
+       }
+
+       // GETTERS
+       protected String getMemberAttributeId() {
+               return memberAttributeId;
+       }
+
+       protected List<String> getCredentialAttributeIds() {
+               return credentialAttributeIds;
+       }
+
+       protected String getUri() {
+               return uri;
+       }
+
+       private static boolean readOnlyDefault(String uriStr) {
+               if (uriStr == null)
+                       return true;
+               /// TODO make it more generic
+               URI uri;
+               try {
+                       uri = new URI(uriStr.split(" ")[0]);
+               } catch (URISyntaxException e) {
+                       throw new IllegalArgumentException(e);
+               }
+               if (uri.getScheme() == null)
+                       return false;// assume relative file to be writable
+               if (uri.getScheme().equals(UserAdminConf.SCHEME_FILE)) {
+                       File file = new File(uri);
+                       if (file.exists())
+                               return !file.canWrite();
+                       else
+                               return !file.getParentFile().canWrite();
+               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_LDAP)) {
+                       if (uri.getAuthority() != null)// assume writable if authenticated
+                               return false;
+               } else if (uri.getScheme().equals(UserAdminConf.SCHEME_OS)) {
+                       return true;
+               }
+               return true;// read only by default
+       }
+
+       public boolean isReadOnly() {
+               return readOnly;
+       }
+
+       public boolean isDisabled() {
+               return disabled;
+       }
+
+       protected UserAdmin getExternalRoles() {
+               return externalRoles;
+       }
+
+       protected int roleType(LdapName dn) {
+               if (dn.startsWith(groupBaseDn))
+                       return Role.GROUP;
+               else if (dn.startsWith(userBaseDn))
+                       return Role.USER;
+               else
+                       return Role.GROUP;
+       }
+
+       /** dn can be null, in that case a default should be returned. */
+       public String getUserObjectClass() {
+               return userObjectClass;
+       }
+
+       public String getUserBase() {
+               return userBase;
+       }
+
+       protected String newUserObjectClass(LdapName dn) {
+               return getUserObjectClass();
+       }
+
+       public String getGroupObjectClass() {
+               return groupObjectClass;
+       }
+
+       public String getGroupBase() {
+               return groupBase;
+       }
+
+       public LdapName getBaseDn() {
+               return (LdapName) baseDn.clone();
+       }
+
+       public Dictionary<String, Object> getProperties() {
+               return properties;
+       }
+
+       public Dictionary<String, Object> cloneProperties() {
+               return new Hashtable<>(properties);
+       }
+
+       public void setExternalRoles(UserAdmin externalRoles) {
+               this.externalRoles = externalRoles;
+       }
+
+//     public void setTransactionManager(TransactionManager transactionManager) {
+//             this.transactionManager = transactionManager;
+//     }
+
+       public String getForcedPassword() {
+               return forcedPassword;
+       }
+
+       public void setTransactionControl(WorkControl transactionControl) {
+               this.transactionControl = transactionControl;
+       }
+
+       public WcXaResource getXaResource() {
+               return xaResource;
+       }
+
+       public boolean isScoped() {
+               return scoped;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingAuthorization.java
new file mode 100644 (file)
index 0000000..05ba948
--- /dev/null
@@ -0,0 +1,77 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.osgi.service.useradmin.Authorization;
+
+/** An {@link Authorization} which combines roles form various auth sources. */
+class AggregatingAuthorization implements Authorization {
+       private final String name;
+       private final String displayName;
+       private final Set<String> systemRoles;
+       private final Set<String> roles;
+
+       public AggregatingAuthorization(String name, String displayName, Set<String> systemRoles, String[] roles) {
+               this.name = new X500Principal(name).getName();
+               this.displayName = displayName;
+               this.systemRoles = Collections.unmodifiableSet(new HashSet<>(systemRoles));
+               Set<String> temp = new HashSet<>();
+               for (String role : roles) {
+                       if (!temp.contains(role))
+                               temp.add(role);
+               }
+               this.roles = Collections.unmodifiableSet(temp);
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public boolean hasRole(String name) {
+               if (systemRoles.contains(name))
+                       return true;
+               if (roles.contains(name))
+                       return true;
+               return false;
+       }
+
+       @Override
+       public String[] getRoles() {
+               int size = systemRoles.size() + roles.size();
+               List<String> res = new ArrayList<String>(size);
+               res.addAll(systemRoles);
+               res.addAll(roles);
+               return res.toArray(new String[size]);
+       }
+
+       @Override
+       public int hashCode() {
+               if (name == null)
+                       return super.hashCode();
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof Authorization))
+                       return false;
+               Authorization that = (Authorization) obj;
+               if (name == null)
+                       return that.getName() == null;
+               return name.equals(that.getName());
+       }
+
+       @Override
+       public String toString() {
+               return displayName;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
new file mode 100644 (file)
index 0000000..c274ed9
--- /dev/null
@@ -0,0 +1,277 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Group;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Aggregates multiple {@link UserDirectory} and integrates them with system
+ * roles.
+ */
+public class AggregatingUserAdmin implements UserAdmin {
+       private final LdapName systemRolesBaseDn;
+       private final LdapName tokensBaseDn;
+
+       // DAOs
+       private AbstractUserDirectory systemRoles = null;
+       private AbstractUserDirectory tokens = null;
+       private Map<LdapName, AbstractUserDirectory> businessRoles = new HashMap<LdapName, AbstractUserDirectory>();
+
+       // TODO rather use an empty constructor and an init method
+       public AggregatingUserAdmin(String systemRolesBaseDn, String tokensBaseDn) {
+               try {
+                       this.systemRolesBaseDn = new LdapName(systemRolesBaseDn);
+                       if (tokensBaseDn != null)
+                               this.tokensBaseDn = new LdapName(tokensBaseDn);
+                       else
+                               this.tokensBaseDn = null;
+               } catch (InvalidNameException e) {
+                       throw new UserDirectoryException("Cannot initialize " + AggregatingUserAdmin.class, e);
+               }
+       }
+
+       @Override
+       public Role createRole(String name, int type) {
+               return findUserAdmin(name).createRole(name, type);
+       }
+
+       @Override
+       public boolean removeRole(String name) {
+               boolean actuallyDeleted = findUserAdmin(name).removeRole(name);
+               systemRoles.removeRole(name);
+               return actuallyDeleted;
+       }
+
+       @Override
+       public Role getRole(String name) {
+               return findUserAdmin(name).getRole(name);
+       }
+
+       @Override
+       public Role[] getRoles(String filter) throws InvalidSyntaxException {
+               List<Role> res = new ArrayList<Role>();
+               for (UserAdmin userAdmin : businessRoles.values()) {
+                       res.addAll(Arrays.asList(userAdmin.getRoles(filter)));
+               }
+               res.addAll(Arrays.asList(systemRoles.getRoles(filter)));
+               return res.toArray(new Role[res.size()]);
+       }
+
+       @Override
+       public User getUser(String key, String value) {
+               List<User> res = new ArrayList<User>();
+               for (UserAdmin userAdmin : businessRoles.values()) {
+                               User u = userAdmin.getUser(key, value);
+                               if (u != null)
+                                       res.add(u);
+               }
+               // Note: node roles cannot contain users, so it is not searched
+               return res.size() == 1 ? res.get(0) : null;
+       }
+
+       @Override
+       public Authorization getAuthorization(User user) {
+               if (user == null) {// anonymous
+                       return systemRoles.getAuthorization(null);
+               }
+               AbstractUserDirectory userReferentialOfThisUser = findUserAdmin(user.getName());
+               Authorization rawAuthorization = userReferentialOfThisUser.getAuthorization(user);
+               String usernameToUse;
+               String displayNameToUse;
+               if (user instanceof Group) {
+                       // TODO check whether this is still working
+                       String ownerDn = TokenUtils.userDn((Group) user);
+                       if (ownerDn != null) {// tokens
+                               UserAdmin ownerUserAdmin = findUserAdmin(ownerDn);
+                               User ownerUser = (User) ownerUserAdmin.getRole(ownerDn);
+                               usernameToUse = ownerDn;
+                               displayNameToUse = LdifAuthorization.extractDisplayName(ownerUser);
+                       } else {
+                               usernameToUse = rawAuthorization.getName();
+                               displayNameToUse = rawAuthorization.toString();
+                       }
+               } else {// regular users
+                       usernameToUse = rawAuthorization.getName();
+                       displayNameToUse = rawAuthorization.toString();
+               }
+
+               // gather roles from other referentials
+               final AbstractUserDirectory userAdminToUse;// possibly scoped when authenticating
+               if (user instanceof DirectoryUser) {
+                       userAdminToUse = userReferentialOfThisUser;
+               } else if (user instanceof AuthenticatingUser) {
+                       userAdminToUse = userReferentialOfThisUser.scope(user);
+               } else {
+                       throw new IllegalArgumentException("Unsupported user type " + user.getClass());
+               }
+
+               try {
+                       Set<String> sysRoles = new HashSet<String>();
+                       for (String role : rawAuthorization.getRoles()) {
+                               User userOrGroup = (User) userAdminToUse.getRole(role);
+                               Authorization auth = systemRoles.getAuthorization(userOrGroup);
+                               systemRoles: for (String systemRole : auth.getRoles()) {
+                                       if (role.equals(systemRole))
+                                               continue systemRoles;
+                                       sysRoles.add(systemRole);
+                               }
+//                     sysRoles.addAll(Arrays.asList(auth.getRoles()));
+                       }
+                       addAbstractSystemRoles(rawAuthorization, sysRoles);
+                       Authorization authorization = new AggregatingAuthorization(usernameToUse, displayNameToUse, sysRoles,
+                                       rawAuthorization.getRoles());
+                       return authorization;
+               } finally {
+                       if (userAdminToUse != null && userAdminToUse.isScoped()) {
+                               userAdminToUse.destroy();
+                       }
+               }
+       }
+
+       /**
+        * Enrich with application-specific roles which are strictly programmatic, such
+        * as anonymous/user semantics.
+        */
+       protected void addAbstractSystemRoles(Authorization rawAuthorization, Set<String> sysRoles) {
+
+       }
+
+       //
+       // USER ADMIN AGGREGATOR
+       //
+       protected void addUserDirectory(AbstractUserDirectory userDirectory) {
+               LdapName baseDn = userDirectory.getBaseDn();
+               if (isSystemRolesBaseDn(baseDn)) {
+                       this.systemRoles = userDirectory;
+                       systemRoles.setExternalRoles(this);
+               } else if (isTokensBaseDn(baseDn)) {
+                       this.tokens = userDirectory;
+                       tokens.setExternalRoles(this);
+               } else {
+                       if (businessRoles.containsKey(baseDn))
+                               throw new UserDirectoryException("There is already a user admin for " + baseDn);
+                       businessRoles.put(baseDn, userDirectory);
+               }
+               userDirectory.init();
+               postAdd(userDirectory);
+       }
+
+       /** Called after a new user directory has been added */
+       protected void postAdd(AbstractUserDirectory userDirectory) {
+       }
+
+//     private UserAdmin findUserAdmin(User user) {
+//             if (user == null)
+//                     throw new IllegalArgumentException("User should not be null");
+//             AbstractUserDirectory userAdmin = findUserAdmin(user.getName());
+//             if (user instanceof DirectoryUser) {
+//                     return userAdmin;
+//             } else {
+//                     return userAdmin.scope(user);
+//             }
+//     }
+
+       private AbstractUserDirectory findUserAdmin(String name) {
+               try {
+                       return findUserAdmin(new LdapName(name));
+               } catch (InvalidNameException e) {
+                       throw new UserDirectoryException("Badly formatted name " + name, e);
+               }
+       }
+
+       private AbstractUserDirectory findUserAdmin(LdapName name) {
+               if (name.startsWith(systemRolesBaseDn))
+                       return systemRoles;
+               if (tokensBaseDn != null && name.startsWith(tokensBaseDn))
+                       return tokens;
+               List<AbstractUserDirectory> res = new ArrayList<>(1);
+               userDirectories: for (LdapName baseDn : businessRoles.keySet()) {
+                       AbstractUserDirectory userDirectory = businessRoles.get(baseDn);
+                       if (name.startsWith(baseDn)) {
+                               if (userDirectory.isDisabled())
+                                       continue userDirectories;
+//                             if (res.isEmpty()) {
+                               res.add(userDirectory);
+//                             } else {
+//                                     for (AbstractUserDirectory ud : res) {
+//                                             LdapName bd = ud.getBaseDn();
+//                                             if (userDirectory.getBaseDn().startsWith(bd)) {
+//                                                     // child user directory
+//                                             }
+//                                     }
+//                             }
+                       }
+               }
+               if (res.size() == 0)
+                       throw new UserDirectoryException("Cannot find user admin for " + name);
+               if (res.size() > 1)
+                       throw new UserDirectoryException("Multiple user admin found for " + name);
+               return res.get(0);
+       }
+
+       protected boolean isSystemRolesBaseDn(LdapName baseDn) {
+               return baseDn.equals(systemRolesBaseDn);
+       }
+
+       protected boolean isTokensBaseDn(LdapName baseDn) {
+               return tokensBaseDn != null && baseDn.equals(tokensBaseDn);
+       }
+
+//     protected Dictionary<String, Object> currentState() {
+//             Dictionary<String, Object> res = new Hashtable<String, Object>();
+//             // res.put(NodeConstants.CN, NodeConstants.DEFAULT);
+//             for (LdapName name : businessRoles.keySet()) {
+//                     AbstractUserDirectory userDirectory = businessRoles.get(name);
+//                     String uri = UserAdminConf.propertiesAsUri(userDirectory.getProperties()).toString();
+//                     res.put(uri, "");
+//             }
+//             return res;
+//     }
+
+       public void destroy() {
+               for (LdapName name : businessRoles.keySet()) {
+                       AbstractUserDirectory userDirectory = businessRoles.get(name);
+                       destroy(userDirectory);
+               }
+               businessRoles.clear();
+               businessRoles = null;
+               destroy(systemRoles);
+               systemRoles = null;
+       }
+
+       private void destroy(AbstractUserDirectory userDirectory) {
+               preDestroy(userDirectory);
+               userDirectory.destroy();
+       }
+
+       protected void removeUserDirectory(LdapName baseDn) {
+               if (isSystemRolesBaseDn(baseDn))
+                       throw new UserDirectoryException("System roles cannot be removed ");
+               if (!businessRoles.containsKey(baseDn))
+                       throw new UserDirectoryException("No user directory registered for " + baseDn);
+               AbstractUserDirectory userDirectory = businessRoles.remove(baseDn);
+               destroy(userDirectory);
+       }
+
+       /**
+        * Called before each user directory is destroyed, so that additional actions
+        * can be performed.
+        */
+       protected void preDestroy(AbstractUserDirectory userDirectory) {
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AuthenticatingUser.java
new file mode 100644 (file)
index 0000000..01db8be
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * A special user type used during authentication in order to provide the
+ * credentials required for scoping the user admin.
+ */
+public class AuthenticatingUser implements User {
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+
+       private final String name;
+       private final Dictionary<String, Object> credentials;
+
+       public AuthenticatingUser(LdapName name) {
+               if (name == null)
+                       throw new NullPointerException("Provided name cannot be null.");
+               this.name = name.toString();
+               this.credentials = new Hashtable<>();
+       }
+
+       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
+               this.name = name;
+               this.credentials = credentials;
+       }
+
+       public AuthenticatingUser(String name, char[] password) {
+               if (name == null)
+                       throw new NullPointerException("Provided name cannot be null.");
+               this.name = name;
+               credentials = new Hashtable<>();
+               credentials.put(SHARED_STATE_NAME, name);
+               byte[] pwd = DigestUtils.charsToBytes(password);
+               credentials.put(SHARED_STATE_PWD, pwd);
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int getType() {
+               return User.USER;
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       @Override
+       public Dictionary getProperties() {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       @Override
+       public Dictionary getCredentials() {
+               return credentials;
+       }
+
+       @Override
+       public boolean hasCredential(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return "Authenticating user " + name;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DigestUtils.java
new file mode 100644 (file)
index 0000000..511c2fe
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.osgi.useradmin;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/** Utilities around digests, mostly those related to passwords. */
+class DigestUtils {
+       final static String PASSWORD_SCHEME_SHA = "SHA";
+       final static String PASSWORD_SCHEME_PBKDF2_SHA256 = "PBKDF2_SHA256";
+
+       static byte[] sha1(byte[] bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance("SHA1");
+                       digest.update(bytes);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot SHA1 digest", e);
+               }
+       }
+
+       static byte[] toPasswordScheme(String passwordScheme, char[] password, byte[] salt, Integer iterations,
+                       Integer keyLength) {
+               try {
+                       if (PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+                               MessageDigest digest = MessageDigest.getInstance("SHA1");
+                               byte[] bytes = charsToBytes(password);
+                               digest.update(bytes);
+                               return digest.digest();
+                       } else if (PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+                               KeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
+
+                               SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+                               final int ITERATION_LENGTH = 4;
+                               byte[] key = f.generateSecret(spec).getEncoded();
+                               byte[] result = new byte[ITERATION_LENGTH + salt.length + key.length];
+                               byte iterationsArr[] = new BigInteger(iterations.toString()).toByteArray();
+                               if (iterationsArr.length < ITERATION_LENGTH) {
+                                       Arrays.fill(result, 0, ITERATION_LENGTH - iterationsArr.length, (byte) 0);
+                                       System.arraycopy(iterationsArr, 0, result, ITERATION_LENGTH - iterationsArr.length,
+                                                       iterationsArr.length);
+                               } else {
+                                       System.arraycopy(iterationsArr, 0, result, 0, ITERATION_LENGTH);
+                               }
+                               System.arraycopy(salt, 0, result, ITERATION_LENGTH, salt.length);
+                               System.arraycopy(key, 0, result, ITERATION_LENGTH + salt.length, key.length);
+                               return result;
+                       } else {
+                               throw new UnsupportedOperationException("Unkown password scheme " + passwordScheme);
+                       }
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot digest", e);
+               }
+       }
+
+       static char[] bytesToChars(Object obj) {
+               if (obj instanceof char[])
+                       return (char[]) obj;
+               if (!(obj instanceof byte[]))
+                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+               // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+               // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+               // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+               return res;
+       }
+
+       static byte[] charsToBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
+       static String sha1str(String str) {
+               byte[] hash = sha1(str.getBytes(StandardCharsets.UTF_8));
+               return encodeHexString(hash);
+       }
+
+       final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+       /**
+        * From
+        * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to
+        * -a-hex-string-in-java
+        */
+       public static String encodeHexString(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 2];
+               for (int j = 0; j < bytes.length; j++) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 2] = hexArray[v >>> 4];
+                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+               }
+               return new String(hexChars);
+       }
+
+       private DigestUtils() {
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryGroup.java
new file mode 100644 (file)
index 0000000..7f80463
--- /dev/null
@@ -0,0 +1,12 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.List;
+
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.Group;
+
+/** A group in a user directroy. */
+interface DirectoryGroup extends Group, DirectoryUser {
+       List<LdapName> getMemberNames();
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/DirectoryUser.java
new file mode 100644 (file)
index 0000000..146b805
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.osgi.useradmin;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.User;
+
+/** A user in a user directory. */
+interface DirectoryUser extends User {
+       LdapName getDn();
+
+       Attributes getAttributes();
+
+       void publishAttributes(Attributes modifiedAttributes);
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java
new file mode 100644 (file)
index 0000000..a9bc941
--- /dev/null
@@ -0,0 +1,137 @@
+package org.argeo.osgi.useradmin;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.DnsBrowser;
+import org.argeo.util.naming.LdapAttrs;
+
+/** Free IPA specific conventions. */
+public class IpaUtils {
+       public final static String IPA_USER_BASE = "cn=users,cn=accounts";
+       public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts";
+       public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts";
+
+       private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
+
+       public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
+                       + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
+
+       @Deprecated
+       static String domainToUserDirectoryConfigPath(String realm) {
+               return domainToBaseDn(realm) + "?" + IPA_USER_DIRECTORY_CONFIG + "&" + UserAdminConf.realm.name() + "=" + realm;
+       }
+
+       public static void addIpaConfig(String realm, Dictionary<String, Object> properties) {
+               properties.put(UserAdminConf.baseDn.name(), domainToBaseDn(realm));
+               properties.put(UserAdminConf.realm.name(), realm);
+               properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE);
+               properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE);
+               properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString());
+       }
+
+       public static String domainToBaseDn(String domain) {
+               String[] dcs = domain.split("\\.");
+               StringBuilder sb = new StringBuilder();
+               for (int i = 0; i < dcs.length; i++) {
+                       if (i != 0)
+                               sb.append(',');
+                       String dc = dcs[i];
+                       sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
+               }
+               return sb.toString();
+       }
+
+       public static LdapName kerberosToDn(String kerberosName) {
+               String[] kname = kerberosName.split("@");
+               String username = kname[0];
+               String baseDn = domainToBaseDn(kname[1]);
+               String dn;
+               if (!username.contains("/"))
+                       dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
+               else
+                       dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
+               try {
+                       return new LdapName(dn);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
+               }
+       }
+
+       private IpaUtils() {
+
+       }
+
+       public static String kerberosDomainFromDns() {
+               String kerberosDomain;
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       InetAddress localhost = InetAddress.getLocalHost();
+                       String hostname = localhost.getHostName();
+                       String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+                       kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+                       return kerberosDomain;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot determine Kerberos domain from DNS", e);
+               }
+
+       }
+
+       public static Dictionary<String, Object> convertIpaUri(URI uri) {
+               String path = uri.getPath();
+               String kerberosRealm;
+               if (path == null || path.length() <= 1) {
+                       kerberosRealm = kerberosDomainFromDns();
+               } else {
+                       kerberosRealm = path.substring(1);
+               }
+
+               if (kerberosRealm == null)
+                       throw new UserDirectoryException("No Kerberos domain available for " + uri);
+               // TODO intergrate CA certificate in truststore
+               // String schemeToUse = SCHEME_LDAPS;
+               String schemeToUse = UserAdminConf.SCHEME_LDAP;
+               List<String> ldapHosts;
+               String ldapHostsStr = uri.getHost();
+               if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+                       try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                               ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosRealm.toLowerCase(),
+                                               schemeToUse.equals(UserAdminConf.SCHEME_LDAP) ? true : false);
+                               if (ldapHosts == null || ldapHosts.size() == 0) {
+                                       throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
+                               } else {
+                                       ldapHostsStr = ldapHosts.get(0);
+                               }
+                       } catch (NamingException | IOException e) {
+                               throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
+                       }
+               } else {
+                       ldapHosts = new ArrayList<>();
+                       ldapHosts.add(ldapHostsStr);
+               }
+
+               StringBuilder uriStr = new StringBuilder();
+               try {
+                       for (String host : ldapHosts) {
+                               URI convertedUri = new URI(schemeToUse + "://" + host + "/");
+                               uriStr.append(convertedUri).append(' ');
+                       }
+               } catch (URISyntaxException e) {
+                       throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
+               }
+
+               Hashtable<String, Object> res = new Hashtable<>();
+               res.put(UserAdminConf.uri.name(), uriStr.toString());
+               addIpaConfig(kerberosRealm, res);
+               return res;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapConnection.java
new file mode 100644 (file)
index 0000000..ed69eb1
--- /dev/null
@@ -0,0 +1,147 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.CommunicationException;
+import javax.naming.Context;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.LdapAttrs;
+
+/** A synchronized wrapper for a single {@link InitialLdapContext}. */
+// TODO implement multiple contexts and connection pooling.
+class LdapConnection {
+       private InitialLdapContext initialLdapContext = null;
+
+       LdapConnection(String url, Dictionary<String, ?> properties) {
+               try {
+                       Hashtable<String, Object> connEnv = new Hashtable<String, Object>();
+                       connEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+                       connEnv.put(Context.PROVIDER_URL, url);
+                       connEnv.put("java.naming.ldap.attributes.binary", LdapAttrs.userPassword.name());
+                       // use pooling in order to avoid connection timeout
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool", "true");
+//                     connEnv.put("com.sun.jndi.ldap.connect.pool.timeout", 300000);
+
+                       initialLdapContext = new InitialLdapContext(connEnv, null);
+                       // StartTlsResponse tls = (StartTlsResponse) ctx
+                       // .extendedOperation(new StartTlsRequest());
+                       // tls.negotiate();
+                       Object securityAuthentication = properties.get(Context.SECURITY_AUTHENTICATION);
+                       if (securityAuthentication != null)
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+                       else
+                               initialLdapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
+                       Object principal = properties.get(Context.SECURITY_PRINCIPAL);
+                       if (principal != null) {
+                               initialLdapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, principal.toString());
+                               Object creds = properties.get(Context.SECURITY_CREDENTIALS);
+                               if (creds != null) {
+                                       initialLdapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, creds.toString());
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot connect to LDAP", e);
+               }
+
+       }
+
+       public void init() {
+
+       }
+
+       public void destroy() {
+               try {
+                       // tls.close();
+                       initialLdapContext.close();
+                       initialLdapContext = null;
+               } catch (NamingException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       protected InitialLdapContext getLdapContext() {
+               return initialLdapContext;
+       }
+
+       protected void reconnect() throws NamingException {
+               initialLdapContext.reconnect(initialLdapContext.getConnectControls());
+       }
+
+       public synchronized NamingEnumeration<SearchResult> search(LdapName searchBase, String searchFilter,
+                       SearchControls searchControls) throws NamingException {
+               NamingEnumeration<SearchResult> results;
+               try {
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       results = getLdapContext().search(searchBase, searchFilter, searchControls);
+               }
+               return results;
+       }
+
+       public synchronized Attributes getAttributes(LdapName name) throws NamingException {
+               try {
+                       return getLdapContext().getAttributes(name);
+               } catch (CommunicationException e) {
+                       reconnect();
+                       return getLdapContext().getAttributes(name);
+               }
+       }
+
+       synchronized void prepareChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+               // make sure connection will work
+               reconnect();
+
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       if (!entryExists(dn))
+                               throw new UserDirectoryException("User to delete no found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       if (entryExists(dn))
+                               throw new UserDirectoryException("User to create found " + dn);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       if (!wc.getNewUsers().containsKey(dn) && !entryExists(dn))
+                               throw new UserDirectoryException("User to modify not found " + dn);
+               }
+
+       }
+
+       protected boolean entryExists(LdapName dn) throws NamingException {
+               try {
+                       return getAttributes(dn).size() != 0;
+               } catch (NameNotFoundException e) {
+                       return false;
+               }
+       }
+
+       synchronized void commitChanges(UserDirectoryWorkingCopy wc) throws NamingException {
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       getLdapContext().destroySubcontext(dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       DirectoryUser user = wc.getNewUsers().get(dn);
+                       getLdapContext().createSubcontext(dn, user.getAttributes());
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+                       getLdapContext().modifyAttributes(dn, DirContext.REPLACE_ATTRIBUTE, modifiedAttrs);
+               }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
new file mode 100644 (file)
index 0000000..f839608
--- /dev/null
@@ -0,0 +1,189 @@
+package org.argeo.osgi.useradmin;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.naming.AuthenticationNotSupportedException;
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapName;
+
+import org.osgi.framework.Filter;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** A user admin based on a LDAP server. */
+public class LdapUserAdmin extends AbstractUserDirectory {
+       private LdapConnection ldapConnection;
+
+       public LdapUserAdmin(Dictionary<String, ?> properties) {
+               this(properties, false);
+       }
+
+       public LdapUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
+               super(null, properties, scoped);
+               ldapConnection = new LdapConnection(getUri().toString(), properties);
+       }
+
+       public void destroy() {
+               ldapConnection.destroy();
+       }
+
+       @Override
+       protected AbstractUserDirectory scope(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Dictionary<String, Object> properties = cloneProperties();
+               properties.put(Context.SECURITY_PRINCIPAL, username.toString());
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DigestUtils.bytesToChars(pwd);
+                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+               } else {
+                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+               }
+               return new LdapUserAdmin(properties, true);
+       }
+
+//     protected InitialLdapContext getLdapContext() {
+//             return initialLdapContext;
+//     }
+
+       @Override
+       protected Boolean daoHasRole(LdapName dn) {
+               try {
+                       return daoGetRole(dn) != null;
+               } catch (NameNotFoundException e) {
+                       return false;
+               }
+       }
+
+       @Override
+       protected DirectoryUser daoGetRole(LdapName name) throws NameNotFoundException {
+               try {
+                       Attributes attrs = ldapConnection.getAttributes(name);
+                       if (attrs.size() == 0)
+                               return null;
+                       int roleType = roleType(name);
+                       LdifUser res;
+                       if (roleType == Role.GROUP)
+                               res = new LdifGroup(this, name, attrs);
+                       else if (roleType == Role.USER)
+                               res = new LdifUser(this, name, attrs);
+                       else
+                               throw new UserDirectoryException("Unsupported LDAP type for " + name);
+                       return res;
+               } catch (NameNotFoundException e) {
+                       throw e;
+               } catch (NamingException e) {
+                       return null;
+               }
+       }
+
+       @Override
+       protected List<DirectoryUser> doGetRoles(Filter f) {
+               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
+               try {
+                       String searchFilter = f != null ? f.toString()
+                                       : "(|(" + objectClass + "=" + getUserObjectClass() + ")(" + objectClass + "="
+                                                       + getGroupObjectClass() + "))";
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+                       LdapName searchBase = getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       results: while (results.hasMoreElements()) {
+                               SearchResult searchResult = results.next();
+                               Attributes attrs = searchResult.getAttributes();
+                               Attribute objectClassAttr = attrs.get(objectClass.name());
+                               LdapName dn = toDn(searchBase, searchResult);
+                               LdifUser role;
+                               if (objectClassAttr.contains(getGroupObjectClass())
+                                               || objectClassAttr.contains(getGroupObjectClass().toLowerCase()))
+                                       role = new LdifGroup(this, dn, attrs);
+                               else if (objectClassAttr.contains(getUserObjectClass())
+                                               || objectClassAttr.contains(getUserObjectClass().toLowerCase()))
+                                       role = new LdifUser(this, dn, attrs);
+                               else {
+//                                     log.warn("Unsupported LDAP type for " + searchResult.getName());
+                                       continue results;
+                               }
+                               res.add(role);
+                       }
+                       return res;
+               } catch (AuthenticationNotSupportedException e) {
+                       // ignore (typically an unsupported anonymous bind)
+                       // TODO better logging
+                       return res;
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new UserDirectoryException("Cannot get roles for filter " + f, e);
+               }
+       }
+
+       private LdapName toDn(LdapName baseDn, Binding binding) throws InvalidNameException {
+               return new LdapName(binding.isRelative() ? binding.getName() + "," + baseDn : binding.getName());
+       }
+
+       @Override
+       protected List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               try {
+                       String searchFilter = "(&(" + objectClass + "=" + getGroupObjectClass() + ")(" + getMemberAttributeId()
+                                       + "=" + dn + "))";
+
+                       SearchControls searchControls = new SearchControls();
+                       searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+                       LdapName searchBase = getBaseDn();
+                       NamingEnumeration<SearchResult> results = ldapConnection.search(searchBase, searchFilter, searchControls);
+
+                       while (results.hasMoreElements()) {
+                               SearchResult searchResult = (SearchResult) results.nextElement();
+                               directGroups.add(toDn(searchBase, searchResult));
+                       }
+                       return directGroups;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot populate direct members of " + dn, e);
+               }
+       }
+
+       @Override
+       protected void prepare(UserDirectoryWorkingCopy wc) {
+               try {
+                       ldapConnection.prepareChanges(wc);
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot prepare LDAP", e);
+               }
+       }
+
+       @Override
+       protected void commit(UserDirectoryWorkingCopy wc) {
+               try {
+                       ldapConnection.commitChanges(wc);
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot commit LDAP", e);
+               }
+       }
+
+       @Override
+       protected void rollback(UserDirectoryWorkingCopy wc) {
+               // prepare not impacting
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifAuthorization.java
new file mode 100644 (file)
index 0000000..15afe08
--- /dev/null
@@ -0,0 +1,85 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** Basic authorization. */
+class LdifAuthorization implements Authorization {
+       private final String name;
+       private final String displayName;
+       private final List<String> allRoles;
+
+       public LdifAuthorization(User user, List<Role> allRoles) {
+               if (user == null) {
+                       this.name = null;
+                       this.displayName = "anonymous";
+               } else {
+                       this.name = user.getName();
+                       this.displayName = extractDisplayName(user);
+               }
+               // roles
+               String[] roles = new String[allRoles.size()];
+               for (int i = 0; i < allRoles.size(); i++) {
+                       roles[i] = allRoles.get(i).getName();
+               }
+               this.allRoles = Collections.unmodifiableList(Arrays.asList(roles));
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public boolean hasRole(String name) {
+               return allRoles.contains(name);
+       }
+
+       @Override
+       public String[] getRoles() {
+               return allRoles.toArray(new String[allRoles.size()]);
+       }
+
+       @Override
+       public int hashCode() {
+               if (name == null)
+                       return super.hashCode();
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof Authorization))
+                       return false;
+               Authorization that = (Authorization) obj;
+               if (name == null)
+                       return that.getName() == null;
+               return name.equals(that.getName());
+       }
+
+       @Override
+       public String toString() {
+               return displayName;
+       }
+
+       final static String extractDisplayName(User user) {
+               Dictionary<String, Object> props = user.getProperties();
+               Object displayName = props.get(LdapAttrs.displayName);
+               if (displayName == null)
+                       displayName = props.get(LdapAttrs.cn);
+               if (displayName == null)
+                       displayName = props.get(LdapAttrs.uid);
+               if (displayName == null)
+                       displayName = user.getName();
+               if (displayName == null)
+                       throw new UserDirectoryException("Cannot set display name for " + user);
+               return displayName.toString();
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifGroup.java
new file mode 100644 (file)
index 0000000..f4e5583
--- /dev/null
@@ -0,0 +1,124 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.Role;
+
+/** Directory group implementation */
+class LdifGroup extends LdifUser implements DirectoryGroup {
+       private final String memberAttributeId;
+
+       LdifGroup(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               super(userAdmin, dn, attributes);
+               memberAttributeId = userAdmin.getMemberAttributeId();
+       }
+
+       @Override
+       public boolean addMember(Role role) {
+               try {
+                       Role foundRole = findRole(new LdapName(role.getName()));
+                       if (foundRole == null)
+                               throw new UnsupportedOperationException(
+                                               "Adding role " + role.getName() + " is unsupported within this context.");
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Role name" + role.getName() + " is badly formatted");
+               }
+
+               getUserAdmin().checkEdit();
+               if (!isEditing())
+                       startEditing();
+
+               Attribute member = getAttributes().get(memberAttributeId);
+               if (member != null) {
+                       if (member.contains(role.getName()))
+                               return false;
+                       else
+                               member.add(role.getName());
+               } else
+                       getAttributes().put(memberAttributeId, role.getName());
+               return true;
+       }
+
+       @Override
+       public boolean addRequiredMember(Role role) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean removeMember(Role role) {
+               getUserAdmin().checkEdit();
+               if (!isEditing())
+                       startEditing();
+
+               Attribute member = getAttributes().get(memberAttributeId);
+               if (member != null) {
+                       if (!member.contains(role.getName()))
+                               return false;
+                       member.remove(role.getName());
+                       return true;
+               } else
+                       return false;
+       }
+
+       @Override
+       public Role[] getMembers() {
+               List<Role> directMembers = new ArrayList<Role>();
+               for (LdapName ldapName : getMemberNames()) {
+                       Role role = findRole(ldapName);
+                       if (role == null) {
+                               throw new UserDirectoryException("Role " + ldapName + " cannot be added.");
+                       }
+                       directMembers.add(role);
+               }
+               return directMembers.toArray(new Role[directMembers.size()]);
+       }
+
+       /**
+        * Whether a role with this name can be found from this context.
+        * 
+        * @return The related {@link Role} or <code>null</code>.
+        */
+       protected Role findRole(LdapName ldapName) {
+               Role role = getUserAdmin().getRole(ldapName.toString());
+               if (role == null) {
+                       if (getUserAdmin().getExternalRoles() != null)
+                               role = getUserAdmin().getExternalRoles().getRole(ldapName.toString());
+               }
+               return role;
+       }
+
+       @Override
+       public List<LdapName> getMemberNames() {
+               Attribute memberAttribute = getAttributes().get(memberAttributeId);
+               if (memberAttribute == null)
+                       return new ArrayList<LdapName>();
+               try {
+                       List<LdapName> roles = new ArrayList<LdapName>();
+                       NamingEnumeration<?> values = memberAttribute.getAll();
+                       while (values.hasMore()) {
+                               LdapName dn = new LdapName(values.next().toString());
+                               roles.add(dn);
+                       }
+                       return roles;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot get members", e);
+               }
+       }
+
+       @Override
+       public Role[] getRequiredMembers() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getType() {
+               return GROUP;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUser.java
new file mode 100644 (file)
index 0000000..135645a
--- /dev/null
@@ -0,0 +1,413 @@
+package org.argeo.osgi.useradmin;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.AuthPassword;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.SharedSecret;
+
+/** Directory user implementation */
+class LdifUser implements DirectoryUser {
+       private final AbstractUserDirectory userAdmin;
+
+       private final LdapName dn;
+
+       private final boolean frozen;
+       private Attributes publishedAttributes;
+
+       private final AttributeDictionary properties;
+       private final AttributeDictionary credentials;
+
+       LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes) {
+               this(userAdmin, dn, attributes, false);
+       }
+
+       private LdifUser(AbstractUserDirectory userAdmin, LdapName dn, Attributes attributes, boolean frozen) {
+               this.userAdmin = userAdmin;
+               this.dn = dn;
+               this.publishedAttributes = attributes;
+               properties = new AttributeDictionary(false);
+               credentials = new AttributeDictionary(true);
+               this.frozen = frozen;
+       }
+
+       @Override
+       public String getName() {
+               return dn.toString();
+       }
+
+       @Override
+       public int getType() {
+               return USER;
+       }
+
+       @Override
+       public Dictionary<String, Object> getProperties() {
+               return properties;
+       }
+
+       @Override
+       public Dictionary<String, Object> getCredentials() {
+               return credentials;
+       }
+
+       @Override
+       public boolean hasCredential(String key, Object value) {
+               if (key == null) {
+                       // TODO check other sources (like PKCS12)
+                       // String pwd = new String((char[]) value);
+                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+                       char[] password = DigestUtils.bytesToChars(value);
+
+                       if (userAdmin.getForcedPassword() != null && userAdmin.getForcedPassword().equals(new String(password)))
+                               return true;
+
+                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
+                       if (authPassword != null) {
+                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
+                                       SharedSecret onceToken = new SharedSecret(authPassword);
+                                       if (onceToken.isExpired()) {
+                                               // AuthPassword.remove(getAttributes(), onceToken);
+                                               return false;
+                                       } else {
+                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
+                                               return true;
+                                       }
+                                       // TODO delete expired tokens?
+                               } else {
+                                       // TODO implement SHA
+                                       throw new UnsupportedOperationException(
+                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
+                               }
+                       }
+
+                       // Regular password
+//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
+                       if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
+                               return true;
+                       return false;
+               }
+
+               Object storedValue = getCredentials().get(key);
+               if (storedValue == null || value == null)
+                       return false;
+               if (!(value instanceof String || value instanceof byte[]))
+                       return false;
+               if (storedValue instanceof String && value instanceof String)
+                       return storedValue.equals(value);
+               if (storedValue instanceof byte[] && value instanceof byte[]) {
+                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
+                       String passwordScheme = null;
+                       if (storedBase64.charAt(0) == '{') {
+                               int index = storedBase64.indexOf('}');
+                               if (index > 0) {
+                                       passwordScheme = storedBase64.substring(1, index);
+                                       String storedValueBase64 = storedBase64.substring(index + 1);
+                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
+                                       char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
+                                       byte[] valueBytes;
+                                       if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
+                                       } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
+                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
+                                               BigInteger iterations = new BigInteger(iterationsArr);
+                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
+                                                               iterationsArr.length + 64);
+                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
+                                                               storedValueBytes.length);
+                                               int keyLengthBits = keyArr.length * 8;
+                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+                                                               iterations.intValue(), keyLengthBits);
+                                       } else {
+                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
+                                       }
+                                       return Arrays.equals(storedValueBytes, valueBytes);
+                               }
+                       }
+               }
+//             if (storedValue instanceof byte[] && value instanceof byte[]) {
+//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
+//             }
+               return false;
+       }
+
+       /** Hash the password */
+       byte[] sha1hash(char[] password) {
+               byte[] hashedPassword = ("{SHA}"
+                               + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
+                                               .getBytes(StandardCharsets.UTF_8);
+               return hashedPassword;
+       }
+
+//     byte[] hash(char[] password, String passwordScheme) {
+//             if (passwordScheme == null)
+//                     passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
+//             byte[] hashedPassword = ("{" + passwordScheme + "}"
+//                             + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
+//                                             .getBytes(US_ASCII);
+//             return hashedPassword;
+//     }
+
+       @Override
+       public LdapName getDn() {
+               return dn;
+       }
+
+       @Override
+       public synchronized Attributes getAttributes() {
+               return isEditing() ? getModifiedAttributes() : publishedAttributes;
+       }
+
+       /** Should only be called from working copy thread. */
+       private synchronized Attributes getModifiedAttributes() {
+               assert getWc() != null;
+               return getWc().getAttributes(getDn());
+       }
+
+       protected synchronized boolean isEditing() {
+               return getWc() != null && getModifiedAttributes() != null;
+       }
+
+       private synchronized UserDirectoryWorkingCopy getWc() {
+               return userAdmin.getWorkingCopy();
+       }
+
+       protected synchronized void startEditing() {
+               if (frozen)
+                       throw new UserDirectoryException("Cannot edit frozen view");
+               if (getUserAdmin().isReadOnly())
+                       throw new UserDirectoryException("User directory is read-only");
+               assert getModifiedAttributes() == null;
+               getWc().startEditing(this);
+               // modifiedAttributes = (Attributes) publishedAttributes.clone();
+       }
+
+       public synchronized void publishAttributes(Attributes modifiedAttributes) {
+               publishedAttributes = modifiedAttributes;
+       }
+
+       public DirectoryUser getPublished() {
+               return new LdifUser(userAdmin, dn, publishedAttributes, true);
+       }
+
+       @Override
+       public int hashCode() {
+               return dn.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj instanceof LdifUser) {
+                       LdifUser that = (LdifUser) obj;
+                       return this.dn.equals(that.dn);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return dn.toString();
+       }
+
+       protected AbstractUserDirectory getUserAdmin() {
+               return userAdmin;
+       }
+
+       private class AttributeDictionary extends Dictionary<String, Object> {
+               private final List<String> effectiveKeys = new ArrayList<String>();
+               private final List<String> attrFilter;
+               private final Boolean includeFilter;
+
+               public AttributeDictionary(Boolean includeFilter) {
+                       this.attrFilter = userAdmin.getCredentialAttributeIds();
+                       this.includeFilter = includeFilter;
+                       try {
+                               NamingEnumeration<String> ids = getAttributes().getIDs();
+                               while (ids.hasMore()) {
+                                       String id = ids.next();
+                                       if (includeFilter && attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                                       else if (!includeFilter && !attrFilter.contains(id))
+                                               effectiveKeys.add(id);
+                               }
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException("Cannot initialise attribute dictionary", e);
+                       }
+               }
+
+               @Override
+               public int size() {
+                       return effectiveKeys.size();
+               }
+
+               @Override
+               public boolean isEmpty() {
+                       return effectiveKeys.size() == 0;
+               }
+
+               @Override
+               public Enumeration<String> keys() {
+                       return Collections.enumeration(effectiveKeys);
+               }
+
+               @Override
+               public Enumeration<Object> elements() {
+                       final Iterator<String> it = effectiveKeys.iterator();
+                       return new Enumeration<Object>() {
+
+                               @Override
+                               public boolean hasMoreElements() {
+                                       return it.hasNext();
+                               }
+
+                               @Override
+                               public Object nextElement() {
+                                       String key = it.next();
+                                       return get(key);
+                               }
+
+                       };
+               }
+
+               @Override
+               public Object get(Object key) {
+                       try {
+                               Attribute attr = getAttributes().get(key.toString());
+                               if (attr == null)
+                                       return null;
+                               Object value = attr.get();
+                               if (value instanceof byte[]) {
+                                       if (key.equals(LdapAttrs.userPassword.name()))
+                                               // TODO other cases (certificates, images)
+                                               return value;
+                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
+                               }
+                               if (attr.size() == 1)
+                                       return value;
+                               if (!attr.getID().equals(LdapAttrs.objectClass.name()))
+                                       return value;
+                               // special case for object class
+                               NamingEnumeration<?> en = attr.getAll();
+                               Set<String> objectClasses = new HashSet<String>();
+                               while (en.hasMore()) {
+                                       String objectClass = en.next().toString();
+                                       objectClasses.add(objectClass);
+                               }
+
+                               if (objectClasses.contains(userAdmin.getUserObjectClass()))
+                                       return userAdmin.getUserObjectClass();
+                               else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
+                                       return userAdmin.getGroupObjectClass();
+                               else
+                                       return value;
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object put(String key, Object value) {
+                       if (key == null) {
+                               // TODO persist to other sources (like PKCS12)
+                               char[] password = DigestUtils.bytesToChars(value);
+                               byte[] hashedPassword = sha1hash(password);
+                               return put(LdapAttrs.userPassword.name(), hashedPassword);
+                       }
+                       if (key.startsWith("X-")) {
+                               return put(LdapAttrs.authPassword.name(), value);
+                       }
+
+                       userAdmin.checkEdit();
+                       if (!isEditing())
+                               startEditing();
+
+                       if (!(value instanceof String || value instanceof byte[]))
+                               throw new IllegalArgumentException("Value must be String or byte[]");
+
+                       if (includeFilter && !attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " not included");
+                       else if (!includeFilter && attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " excluded");
+
+                       try {
+                               Attribute attribute = getModifiedAttributes().get(key.toString());
+                               // if (attribute == null) // block unit tests
+                               attribute = new BasicAttribute(key.toString());
+                               if (value instanceof String && !isAsciiPrintable(((String) value)))
+                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
+                               else
+                                       attribute.add(value);
+                               Attribute previousAttribute = getModifiedAttributes().put(attribute);
+                               if (previousAttribute != null)
+                                       return previousAttribute.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException("Cannot get value for attribute " + key, e);
+                       }
+               }
+
+               @Override
+               public Object remove(Object key) {
+                       userAdmin.checkEdit();
+                       if (!isEditing())
+                               startEditing();
+
+                       if (includeFilter && !attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " not included");
+                       else if (!includeFilter && attrFilter.contains(key))
+                               throw new IllegalArgumentException("Key " + key + " excluded");
+
+                       try {
+                               Attribute attr = getModifiedAttributes().remove(key.toString());
+                               if (attr != null)
+                                       return attr.get();
+                               else
+                                       return null;
+                       } catch (NamingException e) {
+                               throw new UserDirectoryException("Cannot remove attribute " + key, e);
+                       }
+               }
+       }
+
+       private static boolean isAsciiPrintable(String str) {
+               if (str == null) {
+                       return false;
+               }
+               int sz = str.length();
+               for (int i = 0; i < sz; i++) {
+                       if (isAsciiPrintable(str.charAt(i)) == false) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       private static boolean isAsciiPrintable(char ch) {
+               return ch >= 32 && ch < 127;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java
new file mode 100644 (file)
index 0000000..8b1206a
--- /dev/null
@@ -0,0 +1,260 @@
+package org.argeo.osgi.useradmin;
+
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapObjs.inetOrgPerson;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.LdifParser;
+import org.argeo.util.naming.LdifWriter;
+import org.osgi.framework.Filter;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+/** A user admin based on a LDIF files. */
+public class LdifUserAdmin extends AbstractUserDirectory {
+       private SortedMap<LdapName, DirectoryUser> users = new TreeMap<LdapName, DirectoryUser>();
+       private SortedMap<LdapName, DirectoryGroup> groups = new TreeMap<LdapName, DirectoryGroup>();
+
+       public LdifUserAdmin(String uri, String baseDn) {
+               this(fromUri(uri, baseDn), false);
+       }
+
+       public LdifUserAdmin(Dictionary<String, ?> properties) {
+               this(properties, false);
+       }
+
+       protected LdifUserAdmin(Dictionary<String, ?> properties, boolean scoped) {
+               super(null, properties, scoped);
+       }
+
+       public LdifUserAdmin(URI uri, Dictionary<String, ?> properties) {
+               super(uri, properties, false);
+       }
+
+       @Override
+       protected AbstractUserDirectory scope(User user) {
+               Dictionary<String, Object> credentials = user.getCredentials();
+               String username = (String) credentials.get(SHARED_STATE_USERNAME);
+               if (username == null)
+                       username = user.getName();
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DigestUtils.bytesToChars(pwd);
+                       User directoryUser = (User) getRole(username);
+                       if (!directoryUser.hasCredential(null, password))
+                               throw new UserDirectoryException("Invalid credentials");
+               } else {
+                       throw new UserDirectoryException("Password is required");
+               }
+               Dictionary<String, Object> properties = cloneProperties();
+               properties.put(UserAdminConf.readOnly.name(), "true");
+               LdifUserAdmin scopedUserAdmin = new LdifUserAdmin(properties, true);
+               scopedUserAdmin.groups = Collections.unmodifiableSortedMap(groups);
+               scopedUserAdmin.users = Collections.unmodifiableSortedMap(users);
+               return scopedUserAdmin;
+       }
+
+       private static Dictionary<String, Object> fromUri(String uri, String baseDn) {
+               Hashtable<String, Object> res = new Hashtable<String, Object>();
+               res.put(UserAdminConf.uri.name(), uri);
+               res.put(UserAdminConf.baseDn.name(), baseDn);
+               return res;
+       }
+
+       public void init() {
+
+               try {
+                       URI u = new URI(getUri());
+                       if (u.getScheme().equals("file")) {
+                               File file = new File(u);
+                               if (!file.exists())
+                                       return;
+                       }
+                       load(u.toURL().openStream());
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot open URL " + getUri(), e);
+               }
+       }
+
+       public void save() {
+               if (getUri() == null)
+                       throw new UserDirectoryException("Cannot save LDIF user admin: no URI is set");
+               if (isReadOnly())
+                       throw new UserDirectoryException("Cannot save LDIF user admin: " + getUri() + " is read-only");
+               try (FileOutputStream out = new FileOutputStream(new File(new URI(getUri())))) {
+                       save(out);
+               } catch (IOException | URISyntaxException e) {
+                       throw new UserDirectoryException("Cannot save user admin to " + getUri(), e);
+               }
+       }
+
+       public void save(OutputStream out) throws IOException {
+               try {
+                       LdifWriter ldifWriter = new LdifWriter(out);
+                       for (LdapName name : groups.keySet())
+                               ldifWriter.writeEntry(name, groups.get(name).getAttributes());
+                       for (LdapName name : users.keySet())
+                               ldifWriter.writeEntry(name, users.get(name).getAttributes());
+               } finally {
+                       out.close();
+               }
+       }
+
+       protected void load(InputStream in) {
+               try {
+                       users.clear();
+                       groups.clear();
+
+                       LdifParser ldifParser = new LdifParser();
+                       SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
+                       for (LdapName key : allEntries.keySet()) {
+                               Attributes attributes = allEntries.get(key);
+                               // check for inconsistency
+                               Set<String> lowerCase = new HashSet<String>();
+                               NamingEnumeration<String> ids = attributes.getIDs();
+                               while (ids.hasMoreElements()) {
+                                       String id = ids.nextElement().toLowerCase();
+                                       if (lowerCase.contains(id))
+                                               throw new UserDirectoryException(key + " has duplicate id " + id);
+                                       lowerCase.add(id);
+                               }
+
+                               // analyse object classes
+                               NamingEnumeration<?> objectClasses = attributes.get(objectClass.name()).getAll();
+                               // System.out.println(key);
+                               objectClasses: while (objectClasses.hasMore()) {
+                                       String objectClass = objectClasses.next().toString();
+                                       // System.out.println(" " + objectClass);
+                                       if (objectClass.toLowerCase().equals(inetOrgPerson.name().toLowerCase())) {
+                                               users.put(key, new LdifUser(this, key, attributes));
+                                               break objectClasses;
+                                       } else if (objectClass.toLowerCase().equals(getGroupObjectClass().toLowerCase())) {
+                                               groups.put(key, new LdifGroup(this, key, attributes));
+                                               break objectClasses;
+                                       }
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot load user admin service from LDIF", e);
+               }
+       }
+
+       public void destroy() {
+               if (users == null || groups == null)
+                       throw new UserDirectoryException("User directory " + getBaseDn() + " is already destroyed");
+               users = null;
+               groups = null;
+       }
+
+       @Override
+       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
+               if (groups.containsKey(key))
+                       return groups.get(key);
+               if (users.containsKey(key))
+                       return users.get(key);
+               throw new NameNotFoundException(key + " not persisted");
+       }
+
+       @Override
+       protected Boolean daoHasRole(LdapName dn) {
+               return users.containsKey(dn) || groups.containsKey(dn);
+       }
+
+       protected List<DirectoryUser> doGetRoles(Filter f) {
+               ArrayList<DirectoryUser> res = new ArrayList<DirectoryUser>();
+               if (f == null) {
+                       res.addAll(users.values());
+                       res.addAll(groups.values());
+               } else {
+                       for (DirectoryUser user : users.values()) {
+                               if (f.match(user.getProperties()))
+                                       res.add(user);
+                       }
+                       for (DirectoryUser group : groups.values())
+                               if (f.match(group.getProperties()))
+                                       res.add(group);
+               }
+               return res;
+       }
+
+       @Override
+       protected List<LdapName> getDirectGroups(LdapName dn) {
+               List<LdapName> directGroups = new ArrayList<LdapName>();
+               for (LdapName name : groups.keySet()) {
+                       DirectoryGroup group = groups.get(name);
+                       if (group.getMemberNames().contains(dn))
+                               directGroups.add(group.getDn());
+               }
+               return directGroups;
+       }
+
+       @Override
+       protected void prepare(UserDirectoryWorkingCopy wc) {
+               // delete
+               for (LdapName dn : wc.getDeletedUsers().keySet()) {
+                       if (users.containsKey(dn))
+                               users.remove(dn);
+                       else if (groups.containsKey(dn))
+                               groups.remove(dn);
+                       else
+                               throw new UserDirectoryException("User to delete not found " + dn);
+               }
+               // add
+               for (LdapName dn : wc.getNewUsers().keySet()) {
+                       DirectoryUser user = wc.getNewUsers().get(dn);
+                       if (users.containsKey(dn) || groups.containsKey(dn))
+                               throw new UserDirectoryException("User to create found " + dn);
+                       else if (Role.USER == user.getType())
+                               users.put(dn, user);
+                       else if (Role.GROUP == user.getType())
+                               groups.put(dn, (DirectoryGroup) user);
+                       else
+                               throw new UserDirectoryException("Unsupported role type " + user.getType() + " for new user " + dn);
+               }
+               // modify
+               for (LdapName dn : wc.getModifiedUsers().keySet()) {
+                       Attributes modifiedAttrs = wc.getModifiedUsers().get(dn);
+                       DirectoryUser user;
+                       if (users.containsKey(dn))
+                               user = users.get(dn);
+                       else if (groups.containsKey(dn))
+                               user = groups.get(dn);
+                       else
+                               throw new UserDirectoryException("User to modify no found " + dn);
+                       user.publishAttributes(modifiedAttrs);
+               }
+       }
+
+       @Override
+       protected void commit(UserDirectoryWorkingCopy wc) {
+               save();
+       }
+
+       @Override
+       protected void rollback(UserDirectoryWorkingCopy wc) {
+               init();
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java
new file mode 100644 (file)
index 0000000..dd16e1a
--- /dev/null
@@ -0,0 +1,66 @@
+package org.argeo.osgi.useradmin;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.LdapAttrs;
+import org.osgi.framework.Filter;
+import org.osgi.service.useradmin.User;
+
+public class OsUserDirectory extends AbstractUserDirectory {
+       private final String osUsername = System.getProperty("user.name");
+       private final LdapName osUserDn;
+       private final LdifUser osUser;
+
+       public OsUserDirectory(URI uriArg, Dictionary<String, ?> props) {
+               super(uriArg, props, false);
+               try {
+                       osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn());
+                       Attributes attributes = new BasicAttributes();
+                       attributes.put(LdapAttrs.uid.name(), osUsername);
+                       osUser = new LdifUser(this, osUserDn, attributes);
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot create system user", e);
+               }
+       }
+
+       @Override
+       protected List<LdapName> getDirectGroups(LdapName dn) {
+               return new ArrayList<>();
+       }
+
+       @Override
+       protected Boolean daoHasRole(LdapName dn) {
+               return osUserDn.equals(dn);
+       }
+
+       @Override
+       protected DirectoryUser daoGetRole(LdapName key) throws NameNotFoundException {
+               if (osUserDn.equals(key))
+                       return osUser;
+               else
+                       throw new NameNotFoundException("Not an OS role");
+       }
+
+       @Override
+       protected List<DirectoryUser> doGetRoles(Filter f) {
+               List<DirectoryUser> res = new ArrayList<>();
+               if (f == null || f.match(osUser.getProperties()))
+                       res.add(osUser);
+               return res;
+       }
+
+       @Override
+       protected AbstractUserDirectory scope(User user) {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserUtils.java
new file mode 100644 (file)
index 0000000..ad6bf88
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.osgi.useradmin;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.security.URIParameter;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+public class OsUserUtils {
+       private static String LOGIN_CONTEXT_USER_NIX = "USER_NIX";
+       private static String LOGIN_CONTEXT_USER_NT = "USER_NT";
+
+       public static String getOsUsername() {
+               return System.getProperty("user.name");
+       }
+
+       public static LoginContext loginAsSystemUser(Subject subject) {
+               try {
+                       URL jaasConfigurationUrl = OsUserUtils.class.getClassLoader()
+                                       .getResource("org/argeo/osgi/useradmin/jaas-os.cfg");
+                       URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI());
+                       Configuration jaasConfiguration = Configuration.getInstance("JavaLoginConfig", uriParameter);
+                       LoginContext lc = new LoginContext(isWindows() ? LOGIN_CONTEXT_USER_NT : LOGIN_CONTEXT_USER_NIX, subject,
+                                       null, jaasConfiguration);
+                       lc.login();
+                       return lc;
+               } catch (URISyntaxException | NoSuchAlgorithmException | LoginException e) {
+                       throw new RuntimeException("Cannot login as system user", e);
+               }
+       }
+
+       public static void main(String args[]) {
+               Subject subject = new Subject();
+               LoginContext loginContext = loginAsSystemUser(subject);
+               System.out.println(subject);
+               try {
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       // silent
+               }
+       }
+
+       private static boolean isWindows() {
+               return System.getProperty("os.name").startsWith("Windows");
+       }
+
+       private OsUserUtils() {
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/TokenUtils.java
new file mode 100644 (file)
index 0000000..4b00e6c
--- /dev/null
@@ -0,0 +1,87 @@
+package org.argeo.osgi.useradmin;
+
+import static org.argeo.util.naming.LdapAttrs.description;
+import static org.argeo.util.naming.LdapAttrs.owner;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+
+import org.argeo.util.naming.NamingUtils;
+import org.osgi.service.useradmin.Group;
+
+/**
+ * Canonically implements the Argeo token conventions.
+ */
+public class TokenUtils {
+       public static Set<String> tokensUsed(Subject subject, String tokensBaseDn) {
+               Set<String> res = new HashSet<>();
+               for (Principal principal : subject.getPrincipals()) {
+                       String name = principal.getName();
+                       if (name.endsWith(tokensBaseDn)) {
+                               try {
+                                       LdapName ldapName = new LdapName(name);
+                                       String token = ldapName.getRdn(ldapName.size()).getValue().toString();
+                                       res.add(token);
+                               } catch (InvalidNameException e) {
+                                       throw new UserDirectoryException("Invalid principal " + principal, e);
+                               }
+                       }
+               }
+               return res;
+       }
+
+       /** The user related to this token group */
+       public static String userDn(Group tokenGroup) {
+               return (String) tokenGroup.getProperties().get(owner.name());
+       }
+
+       public static boolean isExpired(Group tokenGroup) {
+               return isExpired(tokenGroup, Instant.now());
+
+       }
+
+       public static boolean isExpired(Group tokenGroup, Instant instant) {
+               String expiryDateStr = (String) tokenGroup.getProperties().get(description.name());
+               if (expiryDateStr != null) {
+                       Instant expiryDate = NamingUtils.ldapDateToInstant(expiryDateStr);
+                       if (expiryDate.isBefore(instant)) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+//     private final String token;
+//
+//     public TokenUtils(String token) {
+//             this.token = token;
+//     }
+//
+//     public String getToken() {
+//             return token;
+//     }
+//
+//     @Override
+//     public int hashCode() {
+//             return token.hashCode();
+//     }
+//
+//     @Override
+//     public boolean equals(Object obj) {
+//             if ((obj instanceof TokenUtils) && ((TokenUtils) obj).token.equals(token))
+//                     return true;
+//             return false;
+//     }
+//
+//     @Override
+//     public String toString() {
+//             return "Token #" + hashCode();
+//     }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java
new file mode 100644 (file)
index 0000000..3631de4
--- /dev/null
@@ -0,0 +1,242 @@
+package org.argeo.osgi.useradmin;
+
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.Context;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.util.naming.NamingUtils;
+
+/** Properties used to configure user admins. */
+public enum UserAdminConf {
+       /** Base DN (cannot be configured externally) */
+       baseDn("dc=example,dc=com"),
+
+       /** URI of the underlying resource (cannot be configured externally) */
+       uri("ldap://localhost:10389"),
+
+       /** User objectClass */
+       userObjectClass("inetOrgPerson"),
+
+       /** Relative base DN for users */
+       userBase("ou=People"),
+
+       /** Groups objectClass */
+       groupObjectClass("groupOfNames"),
+
+       /** Relative base DN for users */
+       groupBase("ou=Groups"),
+
+       /** Read-only source */
+       readOnly(null),
+
+       /** Disabled source */
+       disabled(null),
+
+       /** Authentication realm */
+       realm(null),
+
+       /** Override all passwords with this value (typically for testing purposes) */
+       forcedPassword(null);
+
+       public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
+
+       public final static String SCHEME_LDAP = "ldap";
+       public final static String SCHEME_LDAPS = "ldaps";
+       public final static String SCHEME_FILE = "file";
+       public final static String SCHEME_OS = "os";
+       public final static String SCHEME_IPA = "ipa";
+
+       /** The default value. */
+       private Object def;
+
+       UserAdminConf(Object def) {
+               this.def = def;
+       }
+
+       public Object getDefault() {
+               return def;
+       }
+
+       /**
+        * For use as Java property.
+        * 
+        * @deprecated use {@link #name()} instead
+        */
+       @Deprecated
+       public String property() {
+               return name();
+       }
+
+       public String getValue(Dictionary<String, ?> properties) {
+               Object res = getRawValue(properties);
+               if (res == null)
+                       return null;
+               return res.toString();
+       }
+
+       @SuppressWarnings("unchecked")
+       public <T> T getRawValue(Dictionary<String, ?> properties) {
+               Object res = properties.get(name());
+               if (res == null)
+                       res = getDefault();
+               return (T) res;
+       }
+
+       /** @deprecated use {@link #valueOf(String)} instead */
+       @Deprecated
+       public static UserAdminConf local(String property) {
+               return UserAdminConf.valueOf(property);
+       }
+
+       /** Hides host and credentials. */
+       public static URI propertiesAsUri(Dictionary<String, ?> properties) {
+               StringBuilder query = new StringBuilder();
+
+               boolean first = true;
+//             for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
+//                     String key = keys.nextElement();
+//                     // TODO clarify which keys are relevant (list only the enum?)
+//                     if (!key.equals("service.factoryPid") && !key.equals("cn") && !key.equals("dn")
+//                                     && !key.equals(Constants.SERVICE_PID) && !key.startsWith("java") && !key.equals(baseDn.name())
+//                                     && !key.equals(uri.name()) && !key.equals(Constants.OBJECTCLASS)
+//                                     && !key.equals(Constants.SERVICE_ID) && !key.equals("bundle.id")) {
+//                             if (first)
+//                                     first = false;
+//                             else
+//                                     query.append('&');
+//                             query.append(valueOf(key).name());
+//                             query.append('=').append(properties.get(key).toString());
+//                     }
+//             }
+
+               keys: for (UserAdminConf key : UserAdminConf.values()) {
+                       if (key.equals(baseDn) || key.equals(uri))
+                               continue keys;
+                       Object value = properties.get(key.name());
+                       if (value == null)
+                               continue keys;
+                       if (first)
+                               first = false;
+                       else
+                               query.append('&');
+                       query.append(key.name());
+                       query.append('=').append(value.toString());
+
+               }
+
+               Object bDnObj = properties.get(baseDn.name());
+               String bDn = bDnObj != null ? bDnObj.toString() : null;
+               try {
+                       return new URI(null, null, bDn != null ? '/' + bDn : null, query.length() != 0 ? query.toString() : null,
+                                       null);
+               } catch (URISyntaxException e) {
+                       throw new UserDirectoryException("Cannot create URI from properties", e);
+               }
+       }
+
+       public static Dictionary<String, Object> uriAsProperties(String uriStr) {
+               try {
+                       Hashtable<String, Object> res = new Hashtable<String, Object>();
+                       URI u = new URI(uriStr);
+                       String scheme = u.getScheme();
+                       if (scheme != null && scheme.equals(SCHEME_IPA)) {
+                               return IpaUtils.convertIpaUri(u);
+//                             scheme = u.getScheme();
+                       }
+                       String path = u.getPath();
+                       // base DN
+                       String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
+                       if (bDn.equals("") && SCHEME_OS.equals(scheme)) {
+                               bDn = getBaseDnFromHostname();
+                       }
+
+                       if (bDn.endsWith(".ldif"))
+                               bDn = bDn.substring(0, bDn.length() - ".ldif".length());
+
+                       // Normalize base DN as LDAP name
+                       bDn = new LdapName(bDn).toString();
+
+                       String principal = null;
+                       String credentials = null;
+                       if (scheme != null)
+                               if (scheme.equals(SCHEME_LDAP) || scheme.equals(SCHEME_LDAPS)) {
+                                       // TODO additional checks
+                                       if (u.getUserInfo() != null) {
+                                               String[] userInfo = u.getUserInfo().split(":");
+                                               principal = userInfo.length > 0 ? userInfo[0] : null;
+                                               credentials = userInfo.length > 1 ? userInfo[1] : null;
+                                       }
+                               } else if (scheme.equals(SCHEME_FILE)) {
+                               } else if (scheme.equals(SCHEME_IPA)) {
+                               } else if (scheme.equals(SCHEME_OS)) {
+                               } else
+                                       throw new UserDirectoryException("Unsupported scheme " + scheme);
+                       Map<String, List<String>> query = NamingUtils.queryToMap(u);
+                       for (String key : query.keySet()) {
+                               UserAdminConf ldapProp = UserAdminConf.valueOf(key);
+                               List<String> values = query.get(key);
+                               if (values.size() == 1) {
+                                       res.put(ldapProp.name(), values.get(0));
+                               } else {
+                                       throw new UserDirectoryException("Only single values are supported");
+                               }
+                       }
+                       res.put(baseDn.name(), bDn);
+                       if (SCHEME_OS.equals(scheme))
+                               res.put(readOnly.name(), "true");
+                       if (principal != null)
+                               res.put(Context.SECURITY_PRINCIPAL, principal);
+                       if (credentials != null)
+                               res.put(Context.SECURITY_CREDENTIALS, credentials);
+                       if (scheme != null) {// relative URIs are dealt with externally
+                               if (SCHEME_OS.equals(scheme)) {
+                                       res.put(uri.name(), SCHEME_OS + ":///");
+                               } else {
+                                       URI bareUri = new URI(scheme, null, u.getHost(), u.getPort(),
+                                                       scheme.equals(SCHEME_FILE) ? u.getPath() : null, null, null);
+                                       res.put(uri.name(), bareUri.toString());
+                               }
+                       }
+                       return res;
+               } catch (Exception e) {
+                       throw new UserDirectoryException("Cannot convert " + uri + " to properties", e);
+               }
+       }
+
+       private static String getBaseDnFromHostname() {
+               String hostname;
+               try {
+                       hostname = InetAddress.getLocalHost().getHostName();
+               } catch (UnknownHostException e) {
+                       hostname = "localhost.localdomain";
+               }
+               int dotIdx = hostname.indexOf('.');
+               if (dotIdx >= 0) {
+                       String domain = hostname.substring(dotIdx + 1, hostname.length());
+                       String bDn = ("." + domain).replaceAll("\\.", ",dc=");
+                       bDn = bDn.substring(1, bDn.length());
+                       return bDn;
+               } else {
+                       return "dc=" + hostname;
+               }
+       }
+
+       /**
+        * Hash the base DN in order to have a deterministic string to be used as a cn
+        * for the underlying user directory.
+        */
+       public static String baseDnHash(Dictionary<String, Object> properties) {
+               String bDn = (String) properties.get(baseDn.name());
+               if (bDn == null)
+                       throw new UserDirectoryException("No baseDn in " + properties);
+               return DigestUtils.sha1str(bDn);
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java
new file mode 100644 (file)
index 0000000..ff80c5a
--- /dev/null
@@ -0,0 +1,25 @@
+package org.argeo.osgi.useradmin;
+
+import javax.naming.ldap.LdapName;
+import javax.transaction.xa.XAResource;
+
+/** Information about a user directory. */
+public interface UserDirectory {
+       /** The base DN of all entries in this user directory */
+       LdapName getBaseDn();
+
+       /** The related {@link XAResource} */
+       XAResource getXaResource();
+
+       boolean isReadOnly();
+
+       boolean isDisabled();
+
+       String getUserObjectClass();
+
+       String getUserBase();
+
+       String getGroupObjectClass();
+
+       String getGroupBase();
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryException.java
new file mode 100644 (file)
index 0000000..613d0fd
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.osgi.useradmin;
+
+import org.osgi.service.useradmin.UserAdmin;
+
+/**
+ * Exceptions related to Argeo's implementation of OSGi {@link UserAdmin}
+ * service.
+ */
+public class UserDirectoryException extends RuntimeException {
+       private static final long serialVersionUID = 1419352360062048603L;
+
+       public UserDirectoryException(String message) {
+               super(message);
+       }
+
+       public UserDirectoryException(String message, Throwable e) {
+               super(message, e);
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectoryWorkingCopy.java
new file mode 100644 (file)
index 0000000..0e25bdf
--- /dev/null
@@ -0,0 +1,58 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.transaction.xa.XAResource;
+
+/** {@link XAResource} for a user directory being edited. */
+class UserDirectoryWorkingCopy {
+       // private final static Log log = LogFactory
+       // .getLog(UserDirectoryWorkingCopy.class);
+
+       private Map<LdapName, DirectoryUser> newUsers = new HashMap<LdapName, DirectoryUser>();
+       private Map<LdapName, Attributes> modifiedUsers = new HashMap<LdapName, Attributes>();
+       private Map<LdapName, DirectoryUser> deletedUsers = new HashMap<LdapName, DirectoryUser>();
+
+       void cleanUp() {
+               // clean collections
+               newUsers.clear();
+               newUsers = null;
+               modifiedUsers.clear();
+               modifiedUsers = null;
+               deletedUsers.clear();
+               deletedUsers = null;
+       }
+
+       public boolean noModifications() {
+               return newUsers.size() == 0 && modifiedUsers.size() == 0
+                               && deletedUsers.size() == 0;
+       }
+
+       public Attributes getAttributes(LdapName dn) {
+               if (modifiedUsers.containsKey(dn))
+                       return modifiedUsers.get(dn);
+               return null;
+       }
+
+       public void startEditing(DirectoryUser user) {
+               LdapName dn = user.getDn();
+               if (modifiedUsers.containsKey(dn))
+                       throw new UserDirectoryException("Already editing " + dn);
+               modifiedUsers.put(dn, (Attributes) user.getAttributes().clone());
+       }
+
+       public Map<LdapName, DirectoryUser> getNewUsers() {
+               return newUsers;
+       }
+
+       public Map<LdapName, DirectoryUser> getDeletedUsers() {
+               return deletedUsers;
+       }
+
+       public Map<LdapName, Attributes> getModifiedUsers() {
+               return modifiedUsers;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java b/org.argeo.util/src/org/argeo/osgi/useradmin/WcXaResource.java
new file mode 100644 (file)
index 0000000..1630b6b
--- /dev/null
@@ -0,0 +1,135 @@
+package org.argeo.osgi.useradmin;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/** {@link XAResource} for a user directory being edited. */
+class WcXaResource implements XAResource {
+       private final AbstractUserDirectory userDirectory;
+
+       private Map<Xid, UserDirectoryWorkingCopy> workingCopies = new HashMap<Xid, UserDirectoryWorkingCopy>();
+       private Xid editingXid = null;
+       private int transactionTimeout = 0;
+
+       public WcXaResource(AbstractUserDirectory userDirectory) {
+               this.userDirectory = userDirectory;
+       }
+
+       @Override
+       public synchronized void start(Xid xid, int flags) throws XAException {
+               if (editingXid != null)
+                       throw new UserDirectoryException("Already editing " + editingXid);
+               UserDirectoryWorkingCopy wc = workingCopies.put(xid, new UserDirectoryWorkingCopy());
+               if (wc != null)
+                       throw new UserDirectoryException("There is already a working copy for " + xid);
+               this.editingXid = xid;
+       }
+
+       @Override
+       public void end(Xid xid, int flags) throws XAException {
+               checkXid(xid);
+       }
+
+       private UserDirectoryWorkingCopy wc(Xid xid) {
+               return workingCopies.get(xid);
+       }
+
+       synchronized UserDirectoryWorkingCopy wc() {
+               if (editingXid == null)
+                       return null;
+               UserDirectoryWorkingCopy wc = workingCopies.get(editingXid);
+               if (wc == null)
+                       throw new UserDirectoryException("No working copy found for " + editingXid);
+               return wc;
+       }
+
+       private synchronized void cleanUp(Xid xid) {
+               wc(xid).cleanUp();
+               workingCopies.remove(xid);
+               editingXid = null;
+       }
+
+       @Override
+       public int prepare(Xid xid) throws XAException {
+               checkXid(xid);
+               UserDirectoryWorkingCopy wc = wc(xid);
+               if (wc.noModifications())
+                       return XA_RDONLY;
+               try {
+                       userDirectory.prepare(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               }
+               return XA_OK;
+       }
+
+       @Override
+       public void commit(Xid xid, boolean onePhase) throws XAException {
+               try {
+                       checkXid(xid);
+                       UserDirectoryWorkingCopy wc = wc(xid);
+                       if (wc.noModifications())
+                               return;
+                       if (onePhase)
+                               userDirectory.prepare(wc);
+                       userDirectory.commit(wc);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void rollback(Xid xid) throws XAException {
+               try {
+                       checkXid(xid);
+                       userDirectory.rollback(wc(xid));
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       throw new XAException(XAException.XAER_RMERR);
+               } finally {
+                       cleanUp(xid);
+               }
+       }
+
+       @Override
+       public void forget(Xid xid) throws XAException {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public boolean isSameRM(XAResource xares) throws XAException {
+               return xares == this;
+       }
+
+       @Override
+       public Xid[] recover(int flag) throws XAException {
+               return new Xid[0];
+       }
+
+       @Override
+       public int getTransactionTimeout() throws XAException {
+               return transactionTimeout;
+       }
+
+       @Override
+       public boolean setTransactionTimeout(int seconds) throws XAException {
+               transactionTimeout = seconds;
+               return true;
+       }
+
+       private void checkXid(Xid xid) throws XAException {
+               if (xid == null)
+                       throw new XAException(XAException.XAER_OUTSIDE);
+               if (!xid.equals(xid))
+                       throw new XAException(XAException.XAER_NOTA);
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg b/org.argeo.util/src/org/argeo/osgi/useradmin/jaas-os.cfg
new file mode 100644 (file)
index 0000000..da04505
--- /dev/null
@@ -0,0 +1,8 @@
+USER_NIX {
+    com.sun.security.auth.module.UnixLoginModule requisite; 
+};
+
+USER_NT {
+    com.sun.security.auth.module.NTLoginModule requisite; 
+};
+
diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java b/org.argeo.util/src/org/argeo/osgi/useradmin/package-info.java
new file mode 100644 (file)
index 0000000..c108d2c
--- /dev/null
@@ -0,0 +1,2 @@
+/** LDAP and LDIF based OSGi useradmin implementation. */
+package org.argeo.osgi.useradmin;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java b/org.argeo.util/src/org/argeo/osgi/util/FilterRequirement.java
new file mode 100644 (file)
index 0000000..31f1d4d
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.osgi.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.resource.Namespace;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+/** Simplify filtering resources. */
+public class FilterRequirement implements Requirement {
+       private String namespace;
+       private String filter;
+
+       public FilterRequirement(String namespace, String filter) {
+               this.namespace = namespace;
+               this.filter = filter;
+       }
+
+       @Override
+       public Resource getResource() {
+               return null;
+       }
+
+       @Override
+       public String getNamespace() {
+               return namespace;
+       }
+
+       @Override
+       public Map<String, String> getDirectives() {
+               Map<String, String> directives = new HashMap<>();
+               directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter);
+               return directives;
+       }
+
+       @Override
+       public Map<String, Object> getAttributes() {
+               return new HashMap<>();
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java b/org.argeo.util/src/org/argeo/osgi/util/OnServiceRegistration.java
new file mode 100644 (file)
index 0000000..5a6760e
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.osgi.util;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Function;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class OnServiceRegistration<R> implements Future<R> {
+       private BundleContext ownBundleContext = FrameworkUtil.getBundle(OnServiceRegistration.class).getBundleContext();
+
+       private ServiceTracker<?, ?> st;
+
+       private R result;
+       private boolean cancelled = false;
+       private Throwable exception;
+
+       public <T> OnServiceRegistration(Class<T> clss, Function<T, R> function) {
+               this(null, clss, function);
+       }
+
+       public <T> OnServiceRegistration(BundleContext bundleContext, Class<T> clss, Function<T, R> function) {
+               st = new ServiceTracker<T, T>(bundleContext != null ? bundleContext : ownBundleContext, clss, null) {
+
+                       @Override
+                       public T addingService(ServiceReference<T> reference) {
+                               T service = super.addingService(reference);
+                               try {
+                                       if (result != null)// we only want the first one
+                                               return service;
+                                       result = function.apply(service);
+                                       return service;
+                               } catch (Exception e) {
+                                       exception = e;
+                                       return service;
+                               } finally {
+                                       close();
+                               }
+                       }
+               };
+               st.open(bundleContext == null);
+       }
+
+       @Override
+       public boolean cancel(boolean mayInterruptIfRunning) {
+               if (result != null || exception != null || cancelled)
+                       return false;
+               st.close();
+               cancelled = true;
+               return true;
+       }
+
+       @Override
+       public boolean isCancelled() {
+               return cancelled;
+       }
+
+       @Override
+       public boolean isDone() {
+               return result != null || cancelled;
+       }
+
+       @Override
+       public R get() throws InterruptedException, ExecutionException {
+               st.waitForService(0);
+               return tryGetResult();
+       }
+
+       @Override
+       public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+               st.waitForService(TimeUnit.MILLISECONDS.convert(timeout, unit));
+               if (result == null)
+                       throw new TimeoutException("No result after " + timeout + " " + unit);
+               return tryGetResult();
+       }
+
+       protected R tryGetResult() throws ExecutionException, CancellationException {
+               if (cancelled)
+                       throw new CancellationException();
+               if (exception != null)
+                       throw new ExecutionException(exception);
+               if (result == null)// this should not happen
+                       try {
+                               throw new IllegalStateException("No result available");
+                       } catch (Exception e) {
+                               exception = e;
+                               throw new ExecutionException(e);
+                       }
+               return result;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java b/org.argeo.util/src/org/argeo/osgi/util/OsgiRegister.java
new file mode 100644 (file)
index 0000000..7132b7c
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.osgi.util;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+
+import org.argeo.util.register.Register;
+import org.argeo.util.register.Singleton;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class OsgiRegister implements Register {
+       private final BundleContext bundleContext;
+       private Executor executor;
+
+       private CompletableFuture<Void> shutdownStarting = new CompletableFuture<Void>();
+
+       public OsgiRegister(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+               // TODO experiment with dedicated executors
+               this.executor = ForkJoinPool.commonPool();
+       }
+
+       @Override
+       public <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes) {
+               CompletableFuture<ServiceRegistration<?>> srf = new CompletableFuture<ServiceRegistration<?>>();
+               CompletableFuture<T> postRegistration = CompletableFuture.supplyAsync(() -> {
+                       List<String> lst = new ArrayList<>();
+                       lst.add(clss.getName());
+                       for (Class<?> c : classes) {
+                               lst.add(c.getName());
+                       }
+                       ServiceRegistration<?> sr = bundleContext.registerService(lst.toArray(new String[lst.size()]), obj,
+                                       new Hashtable<String, Object>(attributes));
+                       srf.complete(sr);
+                       return obj;
+               }, executor);
+               Singleton<T> singleton = new Singleton<T>(clss, postRegistration);
+
+               shutdownStarting. //
+                               thenCompose(singleton::prepareUnregistration). //
+                               thenRunAsync(() -> {
+                                       try {
+                                               srf.get().unregister();
+                                       } catch (InterruptedException | ExecutionException e) {
+                                               e.printStackTrace();
+                                       }
+                               }, executor);
+               return singleton;
+       }
+
+       public void shutdown() {
+               shutdownStarting.complete(null);
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/CsvParser.java b/org.argeo.util/src/org/argeo/util/CsvParser.java
new file mode 100644 (file)
index 0000000..b903f77
--- /dev/null
@@ -0,0 +1,242 @@
+package org.argeo.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Parses a CSV file interpreting the first line as a header. The
+ * {@link #parse(InputStream)} method and the setters are synchronized so that
+ * the object cannot be modified when parsing.
+ */
+public abstract class CsvParser {
+       private char separator = ',';
+       private char quote = '\"';
+
+       private Boolean noHeader = false;
+       private Boolean strictLineAsLongAsHeader = true;
+
+       /**
+        * Actually process a parsed line. If
+        * {@link #setStrictLineAsLongAsHeader(Boolean)} is true (default) the header
+        * and the tokens are guaranteed to have the same size.
+        * 
+        * @param lineNumber the current line number, starts at 1 (the header, if header
+        *                   processing is enabled, the first line otherwise)
+        * @param header     the read-only header or null if
+        *                   {@link #setNoHeader(Boolean)} is true (default is false)
+        * @param tokens     the parsed tokens
+        */
+       protected abstract void processLine(Integer lineNumber, List<String> header, List<String> tokens);
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in the stream to parse
+        * 
+        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+        */
+       @Deprecated
+       public synchronized void parse(InputStream in) {
+               parse(in, (Charset) null);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in       the stream to parse
+        * @param encoding the encoding to use.
+        * 
+        * @deprecated Use {@link #parse(InputStream, Charset)} instead.
+        */
+       @Deprecated
+       public synchronized void parse(InputStream in, String encoding) {
+               Reader reader;
+               if (encoding == null)
+                       reader = new InputStreamReader(in);
+               else
+                       try {
+                               reader = new InputStreamReader(in, encoding);
+                       } catch (UnsupportedEncodingException e) {
+                               throw new IllegalArgumentException(e);
+                       }
+               parse(reader);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param in      the stream to parse
+        * @param charset the charset to use
+        */
+       public synchronized void parse(InputStream in, Charset charset) {
+               Reader reader;
+               if (charset == null)
+                       reader = new InputStreamReader(in);
+               else
+                       reader = new InputStreamReader(in, charset);
+               parse(reader);
+       }
+
+       /**
+        * Parses the CSV file (stream is closed at the end)
+        * 
+        * @param reader the reader to use (it will be buffered)
+        */
+       public synchronized void parse(Reader reader) {
+               Integer lineCount = 0;
+               try (BufferedReader bufferedReader = new BufferedReader(reader)) {
+                       List<String> header = null;
+                       if (!noHeader) {
+                               String headerStr = bufferedReader.readLine();
+                               if (headerStr == null)// empty file
+                                       return;
+                               lineCount++;
+                               header = new ArrayList<String>();
+                               StringBuffer currStr = new StringBuffer("");
+                               Boolean wasInquote = false;
+                               while (parseLine(headerStr, header, currStr, wasInquote)) {
+                                       headerStr = bufferedReader.readLine();
+                                       if (headerStr == null)
+                                               break;
+                                       wasInquote = true;
+                               }
+                               header = Collections.unmodifiableList(header);
+                       }
+
+                       String line = null;
+                       lines: while ((line = bufferedReader.readLine()) != null) {
+                               line = preProcessLine(line);
+                               if (line == null) {
+                                       // skip line
+                                       continue lines;
+                               }
+                               lineCount++;
+                               List<String> tokens = new ArrayList<String>();
+                               StringBuffer currStr = new StringBuffer("");
+                               Boolean wasInquote = false;
+                               sublines: while (parseLine(line, tokens, currStr, wasInquote)) {
+                                       line = bufferedReader.readLine();
+                                       if (line == null)
+                                               break sublines;
+                                       wasInquote = true;
+                               }
+                               if (!noHeader && strictLineAsLongAsHeader) {
+                                       int headerSize = header.size();
+                                       int tokenSize = tokens.size();
+                                       if (tokenSize == 1 && line.trim().equals(""))
+                                               continue lines;// empty line
+                                       if (headerSize != tokenSize) {
+                                               throw new IllegalStateException("Token size " + tokenSize + " is different from header size "
+                                                               + headerSize + " at line " + lineCount + ", line: " + line + ", header: " + header
+                                                               + ", tokens: " + tokens);
+                                       }
+                               }
+                               processLine(lineCount, header, tokens);
+                       }
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot parse CSV file (line: " + lineCount + ")", e);
+               }
+       }
+
+       /**
+        * Called before each (logical) line is processed, giving a change to modify it
+        * (typically for cleaning dirty files). To be overridden, return the line
+        * unchanged by default. Skip the line if 'null' is returned.
+        */
+       protected String preProcessLine(String line) {
+               return line;
+       }
+
+       /**
+        * Parses a line character by character for performance purpose
+        * 
+        * @return whether to continue parsing this line
+        */
+       protected Boolean parseLine(String str, List<String> tokens, StringBuffer currStr, Boolean wasInquote) {
+               if (wasInquote)
+                       currStr.append('\n');
+
+               char[] arr = str.toCharArray();
+               boolean inQuote = wasInquote;
+               for (int i = 0; i < arr.length; i++) {
+                       char c = arr[i];
+                       if (c == separator) {
+                               if (!inQuote) {
+                                       tokens.add(currStr.toString());
+//                                     currStr.delete(0, currStr.length());
+                                       currStr.setLength(0);
+                                       currStr.trimToSize();
+                               } else {
+                                       // we don't remove separator that are in a quoted substring
+                                       // System.out
+                                       // .println("IN QUOTE, got a separator: [" + c + "]");
+                                       currStr.append(c);
+                               }
+                       } else if (c == quote) {
+                               if (inQuote && (i + 1) < arr.length && arr[i + 1] == quote) {
+                                       // case of double quote
+                                       currStr.append(quote);
+                                       i++;
+                               } else {// standard
+                                       inQuote = inQuote ? false : true;
+                               }
+                       } else {
+                               currStr.append(c);
+                       }
+               }
+
+               if (!inQuote) {
+                       tokens.add(currStr.toString());
+                       // System.out.println("# TOKEN: " + currStr);
+               }
+               // if (inQuote)
+               // throw new ArgeoException("Missing quote at the end of the line "
+               // + str + " (parsed: " + tokens + ")");
+               if (inQuote)
+                       return true;
+               else
+                       return false;
+               // return tokens;
+       }
+
+       public char getSeparator() {
+               return separator;
+       }
+
+       public synchronized void setSeparator(char separator) {
+               this.separator = separator;
+       }
+
+       public char getQuote() {
+               return quote;
+       }
+
+       public synchronized void setQuote(char quote) {
+               this.quote = quote;
+       }
+
+       public Boolean getNoHeader() {
+               return noHeader;
+       }
+
+       public synchronized void setNoHeader(Boolean noHeader) {
+               this.noHeader = noHeader;
+       }
+
+       public Boolean getStrictLineAsLongAsHeader() {
+               return strictLineAsLongAsHeader;
+       }
+
+       public synchronized void setStrictLineAsLongAsHeader(Boolean strictLineAsLongAsHeader) {
+               this.strictLineAsLongAsHeader = strictLineAsLongAsHeader;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java b/org.argeo.util/src/org/argeo/util/CsvParserWithLinesAsMap.java
new file mode 100644 (file)
index 0000000..8eb6e94
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.util;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CSV parser allowing to process lines as maps whose keys are the header
+ * fields.
+ */
+public abstract class CsvParserWithLinesAsMap extends CsvParser {
+
+       /**
+        * Actually processes a line.
+        * 
+        * @param lineNumber the current line number, starts at 1 (the header, if header
+        *                   processing is enabled, the first lien otherwise)
+        * @param line       the parsed tokens as a map whose keys are the header fields
+        */
+       protected abstract void processLine(Integer lineNumber, Map<String, String> line);
+
+       protected final void processLine(Integer lineNumber, List<String> header, List<String> tokens) {
+               if (header == null)
+                       throw new IllegalArgumentException("Only CSV with header is supported");
+               Map<String, String> line = new HashMap<String, String>();
+               for (int i = 0; i < header.size(); i++) {
+                       String key = header.get(i);
+                       String value = null;
+                       if (i < tokens.size())
+                               value = tokens.get(i);
+                       line.put(key, value);
+               }
+               processLine(lineNumber, line);
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/CsvWriter.java b/org.argeo.util/src/org/argeo/util/CsvWriter.java
new file mode 100644 (file)
index 0000000..c3b3a3a
--- /dev/null
@@ -0,0 +1,156 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.List;
+
+/** Write in CSV format. */
+public class CsvWriter {
+       private final Writer out;
+
+       private char separator = ',';
+       private char quote = '\"';
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out the stream to write to. Caller is responsible for closing it.
+        * 
+        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+        * 
+        */
+       @Deprecated
+       public CsvWriter(OutputStream out) {
+               this.out = new OutputStreamWriter(out);
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out      the stream to write to. Caller is responsible for closing it.
+        * @param encoding the encoding to use.
+        * 
+        * @deprecated Use {@link #CsvWriter(OutputStream, Charset)} instead.
+        */
+       @Deprecated
+       public CsvWriter(OutputStream out, String encoding) {
+               try {
+                       this.out = new OutputStreamWriter(out, encoding);
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException(e);
+               }
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out     the stream to write to. Caller is responsible for closing it.
+        * @param charset the charset to use
+        */
+       public CsvWriter(OutputStream out, Charset charset) {
+               this.out = new OutputStreamWriter(out, charset);
+       }
+
+       /**
+        * Creates a CSV writer.
+        * 
+        * @param out the stream to write to. Caller is responsible for closing it.
+        */
+       public CsvWriter(Writer writer) {
+               this.out = writer;
+       }
+
+       /**
+        * Write a CSV line. Also used to write a header if needed (this is transparent
+        * for the CSV writer): simply call it first, before writing the lines.
+        */
+       public void writeLine(List<?> tokens) {
+               try {
+                       Iterator<?> it = tokens.iterator();
+                       while (it.hasNext()) {
+                               Object obj = it.next();
+                               writeToken(obj != null ? obj.toString() : null);
+                               if (it.hasNext())
+                                       out.write(separator);
+                       }
+                       out.write('\n');
+                       out.flush();
+               } catch (IOException e) {
+                       throw new RuntimeException("Could not write " + tokens, e);
+               }
+       }
+
+       /**
+        * Write a CSV line. Also used to write a header if needed (this is transparent
+        * for the CSV writer): simply call it first, before writing the lines.
+        */
+       public void writeLine(Object[] tokens) {
+               try {
+                       for (int i = 0; i < tokens.length; i++) {
+                               if (tokens[i] == null) {
+                                       writeToken(null);
+                               } else {
+                                       writeToken(tokens[i].toString());
+                               }
+                               if (i != (tokens.length - 1))
+                                       out.write(separator);
+                       }
+                       out.write('\n');
+                       out.flush();
+               } catch (IOException e) {
+                       throw new RuntimeException("Could not write " + tokens, e);
+               }
+       }
+
+       protected void writeToken(String token) throws IOException {
+               if (token == null) {
+                       // TODO configure how to deal with null
+                       out.write("");
+                       return;
+               }
+               // +2 for possible quotes, another +2 assuming there would be an already
+               // quoted string where quotes needs to be duplicated
+               // another +2 for safety
+               // we don't want to increase buffer size while writing
+               StringBuffer buf = new StringBuffer(token.length() + 6);
+               char[] arr = token.toCharArray();
+               boolean shouldQuote = false;
+               for (char c : arr) {
+                       if (!shouldQuote) {
+                               if (c == separator)
+                                       shouldQuote = true;
+                               if (c == '\n')
+                                       shouldQuote = true;
+                       }
+
+                       if (c == quote) {
+                               shouldQuote = true;
+                               // duplicate quote
+                               buf.append(quote);
+                       }
+
+                       // generic case
+                       buf.append(c);
+               }
+
+               if (shouldQuote == true)
+                       out.write(quote);
+               out.write(buf.toString());
+               if (shouldQuote == true)
+                       out.write(quote);
+       }
+
+       public void setSeparator(char separator) {
+               this.separator = separator;
+       }
+
+       public void setQuote(char quote) {
+               this.quote = quote;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/DictionaryKeys.java b/org.argeo.util/src/org/argeo/util/DictionaryKeys.java
new file mode 100644 (file)
index 0000000..d17c86f
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.util;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * Access the keys of a {@link String}-keyed {@link Dictionary} (common throughout
+ * the OSGi APIs) as an {@link Iterable} so that they are easily usable in
+ * for-each loops.
+ */
+class DictionaryKeys implements Iterable<String> {
+       private final Dictionary<String, ?> dictionary;
+
+       public DictionaryKeys(Dictionary<String, ?> dictionary) {
+               this.dictionary = dictionary;
+       }
+
+       @Override
+       public Iterator<String> iterator() {
+               return new KeyIterator(dictionary.keys());
+       }
+
+       private static class KeyIterator implements Iterator<String> {
+               private final Enumeration<String> keys;
+
+               KeyIterator(Enumeration<String> keys) {
+                       this.keys = keys;
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return keys.hasMoreElements();
+               }
+
+               @Override
+               public String next() {
+                       return keys.nextElement();
+               }
+
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/DigestUtils.java b/org.argeo.util/src/org/argeo/util/DigestUtils.java
new file mode 100644 (file)
index 0000000..38b4e70
--- /dev/null
@@ -0,0 +1,202 @@
+package org.argeo.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** Utilities around cryptographic digests */
+public class DigestUtils {
+       public final static String MD5 = "MD5";
+       public final static String SHA1 = "SHA1";
+       public final static String SHA256 = "SHA-256";
+       public final static String SHA512 = "SHA-512";
+
+       private static Boolean debug = false;
+       // TODO: make it configurable
+       private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB
+
+       public static byte[] sha1(byte[]... bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(SHA1);
+                       for (byte[] arr : bytes)
+                               digest.update(arr);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new UnsupportedOperationException("SHA1 is not avalaible", e);
+               }
+       }
+
+       public static byte[] digestAsBytes(String algorithm, byte[]... bytes) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       for (byte[] arr : bytes)
+                               digest.update(arr);
+                       byte[] checksum = digest.digest();
+                       return checksum;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new UnsupportedOperationException("Cannot digest with algorithm " + algorithm, e);
+               }
+       }
+
+       public static String digest(String algorithm, byte[]... bytes) {
+               return toHexString(digestAsBytes(algorithm, bytes));
+       }
+
+       public static String digest(String algorithm, InputStream in) {
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       // ReadableByteChannel channel = Channels.newChannel(in);
+                       // ByteBuffer bb = ByteBuffer.allocateDirect(byteBufferCapacity);
+                       // while (channel.read(bb) > 0)
+                       // digest.update(bb);
+                       byte[] buffer = new byte[byteBufferCapacity];
+                       int read = 0;
+                       while ((read = in.read(buffer)) > 0) {
+                               digest.update(buffer, 0, read);
+                       }
+
+                       byte[] checksum = digest.digest();
+                       String res = toHexString(checksum);
+                       return res;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(in);
+               }
+       }
+
+       public static String digest(String algorithm, File file) {
+               FileInputStream fis = null;
+               FileChannel fc = null;
+               try {
+                       fis = new FileInputStream(file);
+                       fc = fis.getChannel();
+
+                       // Get the file's size and then map it into memory
+                       int sz = (int) fc.size();
+                       ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);
+                       return digest(algorithm, bb);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot digest " + file + " with algorithm " + algorithm, e);
+               } finally {
+                       StreamUtils.closeQuietly(fis);
+                       if (fc.isOpen())
+                               try {
+                                       fc.close();
+                               } catch (IOException e) {
+                                       // silent
+                               }
+               }
+       }
+
+       protected static String digest(String algorithm, ByteBuffer bb) {
+               long begin = System.currentTimeMillis();
+               try {
+                       MessageDigest digest = MessageDigest.getInstance(algorithm);
+                       digest.update(bb);
+                       byte[] checksum = digest.digest();
+                       String res = toHexString(checksum);
+                       long end = System.currentTimeMillis();
+                       if (debug)
+                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+                       return res;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e);
+               }
+       }
+
+       public static String sha1hex(Path path) {
+               return digest(SHA1, path, byteBufferCapacity);
+       }
+
+       public static String digest(String algorithm, Path path, long bufferSize) {
+               byte[] digest = digestAsBytes(algorithm, path, bufferSize);
+               return toHexString(digest);
+       }
+
+       public static byte[] digestAsBytes(String algorithm, Path file, long bufferSize) {
+               long begin = System.currentTimeMillis();
+               try {
+                       MessageDigest md = MessageDigest.getInstance(algorithm);
+                       FileChannel fc = FileChannel.open(file);
+                       long fileSize = Files.size(file);
+                       if (fileSize <= bufferSize) {
+                               ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, fileSize);
+                               md.update(bb);
+                       } else {
+                               long lastCycle = (fileSize / bufferSize) - 1;
+                               long position = 0;
+                               for (int i = 0; i <= lastCycle; i++) {
+                                       ByteBuffer bb;
+                                       if (i != lastCycle) {
+                                               bb = fc.map(MapMode.READ_ONLY, position, bufferSize);
+                                               position = position + bufferSize;
+                                       } else {
+                                               bb = fc.map(MapMode.READ_ONLY, position, fileSize - position);
+                                               position = fileSize;
+                                       }
+                                       md.update(bb);
+                               }
+                       }
+                       long end = System.currentTimeMillis();
+                       if (debug)
+                               System.out.println((end - begin) + " ms / " + ((end - begin) / 1000) + " s");
+                       return md.digest();
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest " + file + "  with algorithm " + algorithm, e);
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot digest " + file + "  with algorithm " + algorithm, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               File file;
+               if (args.length > 0)
+                       file = new File(args[0]);
+               else {
+                       System.err.println("Usage: <file> [<algorithm>]" + " (see http://java.sun.com/j2se/1.5.0/"
+                                       + "docs/guide/security/CryptoSpec.html#AppA)");
+                       return;
+               }
+
+               if (args.length > 1) {
+                       String algorithm = args[1];
+                       System.out.println(digest(algorithm, file));
+               } else {
+                       String algorithm = "MD5";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       algorithm = "SHA";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       System.out.println(algorithm + ": " + sha1hex(file.toPath()));
+                       algorithm = "SHA-256";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+                       algorithm = "SHA-512";
+                       System.out.println(algorithm + ": " + digest(algorithm, file));
+               }
+       }
+
+       final private static char[] hexArray = "0123456789abcdef".toCharArray();
+
+       /** Converts a byte array to an hex String. */
+       public static String toHexString(byte[] bytes) {
+               char[] hexChars = new char[bytes.length * 2];
+               for (int j = 0; j < bytes.length; j++) {
+                       int v = bytes[j] & 0xFF;
+                       hexChars[j * 2] = hexArray[v >>> 4];
+                       hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+               }
+               return new String(hexChars);
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/DirH.java b/org.argeo.util/src/org/argeo/util/DirH.java
new file mode 100644 (file)
index 0000000..013897d
--- /dev/null
@@ -0,0 +1,116 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Hashes the hashes of the files in a directory. */
+public class DirH {
+
+       private final static Charset charset = Charset.forName("UTF-16");
+       private final static long bufferSize = 200 * 1024 * 1024;
+       private final static String algorithm = "SHA";
+
+       private final static byte EOL = (byte) '\n';
+       private final static byte SPACE = (byte) ' ';
+
+       private final int hashSize;
+
+       private final byte[][] hashes;
+       private final byte[][] fileNames;
+       private final byte[] digest;
+       private final byte[] dirName;
+
+       /**
+        * @param dirName can be null or empty
+        */
+       private DirH(byte[][] hashes, byte[][] fileNames, byte[] dirName) {
+               if (hashes.length != fileNames.length)
+                       throw new IllegalArgumentException(hashes.length + " hashes and " + fileNames.length + " file names");
+               this.hashes = hashes;
+               this.fileNames = fileNames;
+               this.dirName = dirName == null ? new byte[0] : dirName;
+               if (hashes.length == 0) {// empty dir
+                       hashSize = 20;
+                       // FIXME what is the digest of an empty dir?
+                       digest = new byte[hashSize];
+                       Arrays.fill(digest, SPACE);
+                       return;
+               }
+               hashSize = hashes[0].length;
+               for (int i = 0; i < hashes.length; i++) {
+                       if (hashes[i].length != hashSize)
+                               throw new IllegalArgumentException(
+                                               "Hash size for " + new String(fileNames[i], charset) + " is " + hashes[i].length);
+               }
+
+               try {
+                       MessageDigest md = MessageDigest.getInstance(algorithm);
+                       for (int i = 0; i < hashes.length; i++) {
+                               md.update(this.hashes[i]);
+                               md.update(SPACE);
+                               md.update(this.fileNames[i]);
+                               md.update(EOL);
+                       }
+                       digest = md.digest();
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalArgumentException("Cannot digest", e);
+               }
+       }
+
+       public void print(PrintStream out) {
+               out.print(DigestUtils.toHexString(digest));
+               if (dirName.length > 0) {
+                       out.print(' ');
+                       out.print(new String(dirName, charset));
+               }
+               out.print('\n');
+               for (int i = 0; i < hashes.length; i++) {
+                       out.print(DigestUtils.toHexString(hashes[i]));
+                       out.print(' ');
+                       out.print(new String(fileNames[i], charset));
+                       out.print('\n');
+               }
+       }
+
+       public static DirH digest(Path dir) {
+               try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
+                       List<byte[]> hs = new ArrayList<byte[]>();
+                       List<String> fNames = new ArrayList<>();
+                       for (Path file : files) {
+                               if (!Files.isDirectory(file)) {
+                                       byte[] digest = DigestUtils.digestAsBytes(algorithm, file, bufferSize);
+                                       hs.add(digest);
+                                       fNames.add(file.getFileName().toString());
+                               }
+                       }
+
+                       byte[][] fileNames = new byte[fNames.size()][];
+                       for (int i = 0; i < fNames.size(); i++) {
+                               fileNames[i] = fNames.get(i).getBytes(charset);
+                       }
+                       byte[][] hashes = hs.toArray(new byte[hs.size()][]);
+                       return new DirH(hashes, fileNames, dir.toString().getBytes(charset));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot digest " + dir, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               try {
+                       DirH dirH = DirH.digest(Paths.get("/home/mbaudier/tmp/"));
+                       dirH.print(System.out);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/FsUtils.java b/org.argeo.util/src/org/argeo/util/FsUtils.java
new file mode 100644 (file)
index 0000000..b317f4b
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** Utilities around the standard Java file abstractions. */
+public class FsUtils {
+       /**
+        * Deletes this path, recursively if needed. Does nothing if the path does not
+        * exist.
+        */
+       public static void delete(Path path) {
+               try {
+                       if (!Files.exists(path))
+                               return;
+                       Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                               @Override
+                               public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+                                       if (e != null)
+                                               throw e;
+                                       Files.delete(directory);
+                                       return FileVisitResult.CONTINUE;
+                               }
+
+                               @Override
+                               public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                                       Files.delete(file);
+                                       return FileVisitResult.CONTINUE;
+                               }
+                       });
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot delete " + path, e);
+               }
+       }
+
+       /** Singleton. */
+       private FsUtils() {
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/LangUtils.java b/org.argeo.util/src/org/argeo/util/LangUtils.java
new file mode 100644 (file)
index 0000000..1622945
--- /dev/null
@@ -0,0 +1,284 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/** Utilities around Java basic features. */
+public class LangUtils {
+       /*
+        * NON-API OSGi
+        */
+       /**
+        * Returns an array with the names of the provided classes. Useful when
+        * registering services with multiple interfaces in OSGi.
+        */
+       public static String[] names(Class<?>... clzz) {
+               String[] res = new String[clzz.length];
+               for (int i = 0; i < clzz.length; i++)
+                       res[i] = clzz[i].getName();
+               return res;
+       }
+
+       /*
+        * MAP
+        */
+       /**
+        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+        * null, but if the value is null, it returns an empty {@link Dictionary}.
+        */
+       public static Map<String, Object> map(String key, Object value) {
+               assert key != null;
+               HashMap<String, Object> props = new HashMap<>();
+               if (value != null)
+                       props.put(key, value);
+               return props;
+       }
+
+       /*
+        * DICTIONARY
+        */
+
+       /**
+        * Creates a new {@link Dictionary} with one key-value pair. Key should not be
+        * null, but if the value is null, it returns an empty {@link Dictionary}.
+        */
+       public static Dictionary<String, Object> dict(String key, Object value) {
+               assert key != null;
+               Hashtable<String, Object> props = new Hashtable<>();
+               if (value != null)
+                       props.put(key, value);
+               return props;
+       }
+
+       /** @deprecated Use {@link #dict(String, Object)} instead. */
+       @Deprecated
+       public static Dictionary<String, Object> dico(String key, Object value) {
+               return dict(key, value);
+       }
+
+       /** Converts a {@link Dictionary} to a {@link Map} of strings. */
+       public static Map<String, String> dictToStringMap(Dictionary<String, ?> properties) {
+               if (properties == null) {
+                       return null;
+               }
+               Map<String, String> res = new HashMap<>(properties.size());
+               Enumeration<String> keys = properties.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       res.put(key, properties.get(key).toString());
+               }
+               return res;
+       }
+
+       /**
+        * Get a string property from this map, expecting to find it, or
+        * <code>null</code> if not found.
+        */
+       public static String get(Map<String, ?> map, String key) {
+               Object res = map.get(key);
+               if (res == null)
+                       return null;
+               return res.toString();
+       }
+
+       /**
+        * Get a string property from this map, expecting to find it.
+        * 
+        * @throws IllegalArgumentException if the key was not found
+        */
+       public static String getNotNull(Map<String, ?> map, String key) {
+               Object res = map.get(key);
+               if (res == null)
+                       throw new IllegalArgumentException("Map " + map + " should contain key " + key);
+               return res.toString();
+       }
+
+       /**
+        * Wraps the keys of the provided {@link Dictionary} as an {@link Iterable}.
+        */
+       public static Iterable<String> keys(Dictionary<String, ?> props) {
+               assert props != null;
+               return new DictionaryKeys(props);
+       }
+
+       static String toJson(Dictionary<String, ?> props) {
+               return toJson(props, false);
+       }
+
+       static String toJson(Dictionary<String, ?> props, boolean pretty) {
+               StringBuilder sb = new StringBuilder();
+               sb.append('{');
+               if (pretty)
+                       sb.append('\n');
+               Enumeration<String> keys = props.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       if (pretty)
+                               sb.append(' ');
+                       sb.append('\"').append(key).append('\"');
+                       if (pretty)
+                               sb.append(" : ");
+                       else
+                               sb.append(':');
+                       sb.append('\"').append(props.get(key)).append('\"');
+                       if (keys.hasMoreElements())
+                               sb.append(", ");
+                       if (pretty)
+                               sb.append('\n');
+               }
+               sb.append('}');
+               return sb.toString();
+       }
+
+       static void storeAsProperties(Dictionary<String, Object> props, Path path) throws IOException {
+               if (props == null)
+                       throw new IllegalArgumentException("Props cannot be null");
+               Properties toStore = new Properties();
+               for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                       String key = keys.nextElement();
+                       toStore.setProperty(key, props.get(key).toString());
+               }
+               try (OutputStream out = Files.newOutputStream(path)) {
+                       toStore.store(out, null);
+               }
+       }
+
+       static void appendAsLdif(String dnBase, String dnKey, Dictionary<String, Object> props, Path path)
+                       throws IOException {
+               if (props == null)
+                       throw new IllegalArgumentException("Props cannot be null");
+               Object dnValue = props.get(dnKey);
+               String dnStr = dnKey + '=' + dnValue + ',' + dnBase;
+               LdapName dn;
+               try {
+                       dn = new LdapName(dnStr);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot interpret DN " + dnStr, e);
+               }
+               if (dnValue == null)
+                       throw new IllegalArgumentException("DN key " + dnKey + " must have a value");
+               try (Writer writer = Files.newBufferedWriter(path, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
+                       writer.append("\ndn: ");
+                       writer.append(dn.toString());
+                       writer.append('\n');
+                       for (Enumeration<String> keys = props.keys(); keys.hasMoreElements();) {
+                               String key = keys.nextElement();
+                               Object value = props.get(key);
+                               writer.append(key);
+                               writer.append(": ");
+                               // FIXME deal with binary and multiple values
+                               writer.append(value.toString());
+                               writer.append('\n');
+                       }
+               }
+       }
+
+       static Dictionary<String, Object> loadFromProperties(Path path) throws IOException {
+               Properties toLoad = new Properties();
+               try (InputStream in = Files.newInputStream(path)) {
+                       toLoad.load(in);
+               }
+               Dictionary<String, Object> res = new Hashtable<String, Object>();
+               for (Object key : toLoad.keySet())
+                       res.put(key.toString(), toLoad.get(key));
+               return res;
+       }
+
+       /*
+        * COLLECTIONS
+        */
+       /**
+        * Convert a comma-separated separated {@link String} or a {@link String} array
+        * to a {@link List} of {@link String}, trimming them. Useful to quickly
+        * interpret OSGi services properties.
+        * 
+        * @return a {@link List} containing the trimmed {@link String}s, or an empty
+        *         {@link List} if the argument was <code>null</code>.
+        */
+       public static List<String> toStringList(Object value) {
+               List<String> values = new ArrayList<>();
+               if (value == null)
+                       return values;
+               String[] arr;
+               if (value instanceof String) {
+                       arr = ((String) value).split(",");
+               } else if (value instanceof String[]) {
+                       arr = (String[]) value;
+               } else {
+                       throw new IllegalArgumentException("Unsupported value type " + value.getClass());
+               }
+               for (String str : arr) {
+                       values.add(str.trim());
+               }
+               return values;
+       }
+
+       /*
+        * EXCEPTIONS
+        */
+       /**
+        * Chain the messages of all causes (one per line, <b>starts with a line
+        * return</b>) without all the stack
+        */
+       public static String chainCausesMessages(Throwable t) {
+               StringBuffer buf = new StringBuffer();
+               chainCauseMessage(buf, t);
+               return buf.toString();
+       }
+
+       /** Recursive chaining of messages */
+       private static void chainCauseMessage(StringBuffer buf, Throwable t) {
+               buf.append('\n').append(' ').append(t.getClass().getCanonicalName()).append(": ").append(t.getMessage());
+               if (t.getCause() != null)
+                       chainCauseMessage(buf, t.getCause());
+       }
+
+       /*
+        * TIME
+        */
+       /** Formats time elapsed since start. */
+       public static String since(ZonedDateTime start) {
+               ZonedDateTime now = ZonedDateTime.now();
+               return duration(start, now);
+       }
+
+       /** Formats a duration. */
+       public static String duration(Temporal start, Temporal end) {
+               long count = ChronoUnit.DAYS.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " days" : count + " day";
+               count = ChronoUnit.HOURS.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " hours" : count + " hours";
+               count = ChronoUnit.MINUTES.between(start, end);
+               if (count != 0)
+                       return count > 1 ? count + " minutes" : count + " minute";
+               count = ChronoUnit.SECONDS.between(start, end);
+               return count > 1 ? count + " seconds" : count + " second";
+       }
+
+       /** Singleton constructor. */
+       private LangUtils() {
+
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/OS.java b/org.argeo.util/src/org/argeo/util/OS.java
new file mode 100644 (file)
index 0000000..d8127b6
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.util;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+
+/** When OS specific informations are needed. */
+public class OS {
+       public final static OS LOCAL = new OS();
+
+       private final String arch, name, version;
+
+       /** The OS of the running JVM */
+       protected OS() {
+               arch = System.getProperty("os.arch");
+               name = System.getProperty("os.name");
+               version = System.getProperty("os.version");
+       }
+
+       public String getArch() {
+               return arch;
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public String getVersion() {
+               return version;
+       }
+
+       public boolean isMSWindows() {
+               // only MS Windows would use such an horrendous separator...
+               return File.separatorChar == '\\';
+       }
+
+       public String[] getDefaultShellCommand() {
+               if (!isMSWindows())
+                       return new String[] { "/bin/sh", "-l", "-i" };
+               else
+                       return new String[] { "cmd.exe", "/C" };
+       }
+
+       public static Integer getJvmPid() {
+               /*
+                * This method works on most platforms (including Linux). Although when Java 9
+                * comes along, there is a better way: long pid =
+                * ProcessHandle.current().getPid();
+                *
+                * See:
+                * http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-
+                * process-id
+                */
+               String pidAndHost = ManagementFactory.getRuntimeMXBean().getName();
+               return Integer.parseInt(pidAndHost.substring(0, pidAndHost.indexOf('@')));
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/PasswordEncryption.java b/org.argeo.util/src/org/argeo/util/PasswordEncryption.java
new file mode 100644 (file)
index 0000000..c95c787
--- /dev/null
@@ -0,0 +1,216 @@
+package org.argeo.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PasswordEncryption {
+       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+       /** Stronger with 256, but causes problem with Oracle JVM */
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH = 256;
+       public final static Integer DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED = 128;
+       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+//     public final static String DEFAULT_CHARSET = "UTF-8";
+       public final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+       private Integer iterationCount = DEFAULT_ITERATION_COUNT;
+       private Integer secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH;
+       private String secreteKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY;
+       private String secreteKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION;
+       private String cipherName = DEFAULT_CIPHER_NAME;
+
+       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56,
+                       (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+
+       private Key key;
+       private Cipher ecipher;
+       private Cipher dcipher;
+
+       private String securityProviderName = null;
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copy of nor
+        * reference to the passed array is kept
+        */
+       public PasswordEncryption(char[] password) {
+               this(password, DEFAULT_SALT_8, DEFAULT_IV_16);
+       }
+
+       /**
+        * This is up to the caller to clear the passed array. Neither copies of nor
+        * references to the passed arrays are kept
+        */
+       public PasswordEncryption(char[] password, byte[] passwordSalt, byte[] initializationVector) {
+               try {
+                       initKeyAndCiphers(password, passwordSalt, initializationVector);
+               } catch (InvalidKeyException e) {
+                       Integer previousSecreteKeyLength = secreteKeyLength;
+                       secreteKeyLength = DEFAULT_SECRETE_KEY_LENGTH_RESTRICTED;
+                       System.err.println("'" + e.getMessage() + "', will use " + secreteKeyLength
+                                       + " secrete key length instead of " + previousSecreteKeyLength);
+                       try {
+                               initKeyAndCiphers(password, passwordSalt, initializationVector);
+                       } catch (GeneralSecurityException e1) {
+                               throw new IllegalStateException("Cannot get secret key (with restricted length)", e1);
+                       }
+               } catch (GeneralSecurityException e) {
+                       throw new IllegalStateException("Cannot get secret key", e);
+               }
+       }
+
+       protected void initKeyAndCiphers(char[] password, byte[] passwordSalt, byte[] initializationVector)
+                       throws GeneralSecurityException {
+               byte[] salt = new byte[8];
+               System.arraycopy(passwordSalt, 0, salt, 0, salt.length);
+               // for (int i = 0; i < password.length && i < salt.length; i++)
+               // salt[i] = (byte) password[i];
+               byte[] iv = new byte[16];
+               System.arraycopy(initializationVector, 0, iv, 0, iv.length);
+
+               SecretKeyFactory keyFac = SecretKeyFactory.getInstance(getSecretKeyFactoryName());
+               PBEKeySpec keySpec = new PBEKeySpec(password, salt, getIterationCount(), getKeyLength());
+               String secKeyEncryption = getSecretKeyEncryption();
+               if (secKeyEncryption != null) {
+                       SecretKey tmp = keyFac.generateSecret(keySpec);
+                       key = new SecretKeySpec(tmp.getEncoded(), getSecretKeyEncryption());
+               } else {
+                       key = keyFac.generateSecret(keySpec);
+               }
+               if (securityProviderName != null)
+                       ecipher = Cipher.getInstance(getCipherName(), securityProviderName);
+               else
+                       ecipher = Cipher.getInstance(getCipherName());
+               ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+               dcipher = Cipher.getInstance(getCipherName());
+               dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+       }
+
+       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut) throws IOException {
+               try {
+                       CipherOutputStream out = new CipherOutputStream(encryptedOut, ecipher);
+                       StreamUtils.copy(decryptedIn, out);
+                       StreamUtils.closeQuietly(out);
+               } catch (IOException e) {
+                       throw e;
+               } finally {
+                       StreamUtils.closeQuietly(decryptedIn);
+               }
+       }
+
+       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut) throws IOException {
+               try {
+                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn, dcipher);
+                       StreamUtils.copy(decryptedIn, decryptedOut);
+               } catch (IOException e) {
+                       throw e;
+               } finally {
+                       StreamUtils.closeQuietly(encryptedIn);
+               }
+       }
+
+       public byte[] encryptString(String str) {
+               ByteArrayOutputStream out = null;
+               ByteArrayInputStream in = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
+                       encrypt(in, out);
+                       return out.toByteArray();
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       /** Closes the input stream */
+       public String decryptAsString(InputStream in) {
+               ByteArrayOutputStream out = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       decrypt(in, out);
+                       return new String(out.toByteArray(), DEFAULT_CHARSET);
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       protected Key getKey() {
+               return key;
+       }
+
+       protected Cipher getEcipher() {
+               return ecipher;
+       }
+
+       protected Cipher getDcipher() {
+               return dcipher;
+       }
+
+       protected Integer getIterationCount() {
+               return iterationCount;
+       }
+
+       protected Integer getKeyLength() {
+               return secreteKeyLength;
+       }
+
+       protected String getSecretKeyFactoryName() {
+               return secreteKeyFactoryName;
+       }
+
+       protected String getSecretKeyEncryption() {
+               return secreteKeyEncryption;
+       }
+
+       protected String getCipherName() {
+               return cipherName;
+       }
+
+       public void setIterationCount(Integer iterationCount) {
+               this.iterationCount = iterationCount;
+       }
+
+       public void setSecreteKeyLength(Integer keyLength) {
+               this.secreteKeyLength = keyLength;
+       }
+
+       public void setSecreteKeyFactoryName(String secreteKeyFactoryName) {
+               this.secreteKeyFactoryName = secreteKeyFactoryName;
+       }
+
+       public void setSecreteKeyEncryption(String secreteKeyEncryption) {
+               this.secreteKeyEncryption = secreteKeyEncryption;
+       }
+
+       public void setCipherName(String cipherName) {
+               this.cipherName = cipherName;
+       }
+
+       public void setSecurityProviderName(String securityProviderName) {
+               this.securityProviderName = securityProviderName;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/ServiceChannel.java b/org.argeo.util/src/org/argeo/util/ServiceChannel.java
new file mode 100644 (file)
index 0000000..7997384
--- /dev/null
@@ -0,0 +1,78 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.CompletionHandler;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/** An {@link AsynchronousByteChannel} based on an {@link ExecutorService}. */
+public class ServiceChannel implements AsynchronousByteChannel {
+       private final ReadableByteChannel in;
+       private final WritableByteChannel out;
+
+       private boolean open = true;
+
+       private ExecutorService executor;
+
+       public ServiceChannel(ReadableByteChannel in, WritableByteChannel out, ExecutorService executor) {
+               this.in = in;
+               this.out = out;
+               this.executor = executor;
+       }
+
+       @Override
+       public Future<Integer> read(ByteBuffer dst) {
+               return executor.submit(() -> in.read(dst));
+       }
+
+       @Override
+       public <A> void read(ByteBuffer dst, A attachment, CompletionHandler<Integer, ? super A> handler) {
+               try {
+                       Future<Integer> res = read(dst);
+                       handler.completed(res.get(), attachment);
+               } catch (Exception e) {
+                       handler.failed(e, attachment);
+               }
+       }
+
+       @Override
+       public Future<Integer> write(ByteBuffer src) {
+               return executor.submit(() -> out.write(src));
+       }
+
+       @Override
+       public <A> void write(ByteBuffer src, A attachment, CompletionHandler<Integer, ? super A> handler) {
+               try {
+                       Future<Integer> res = write(src);
+                       handler.completed(res.get(), attachment);
+               } catch (Exception e) {
+                       handler.failed(e, attachment);
+               }
+       }
+
+       @Override
+       public synchronized void close() throws IOException {
+               try {
+                       in.close();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               try {
+                       out.close();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+               open = false;
+               notifyAll();
+       }
+
+       @Override
+       public synchronized boolean isOpen() {
+               return open;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/StreamUtils.java b/org.argeo.util/src/org/argeo/util/StreamUtils.java
new file mode 100644 (file)
index 0000000..6d7d940
--- /dev/null
@@ -0,0 +1,81 @@
+package org.argeo.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+/** Utilities to be used when Apache Commons IO is not available. */
+class StreamUtils {
+       private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+       /*
+        * APACHE COMMONS IO (inspired)
+        */
+
+       /** @return the number of bytes */
+       public static Long copy(InputStream in, OutputStream out)
+                       throws IOException {
+               Long count = 0l;
+               byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+               while (true) {
+                       int length = in.read(buf);
+                       if (length < 0)
+                               break;
+                       out.write(buf, 0, length);
+                       count = count + length;
+               }
+               return count;
+       }
+
+       /** @return the number of chars */
+       public static Long copy(Reader in, Writer out) throws IOException {
+               Long count = 0l;
+               char[] buf = new char[DEFAULT_BUFFER_SIZE];
+               while (true) {
+                       int length = in.read(buf);
+                       if (length < 0)
+                               break;
+                       out.write(buf, 0, length);
+                       count = count + length;
+               }
+               return count;
+       }
+
+       public static void closeQuietly(InputStream in) {
+               if (in != null)
+                       try {
+                               in.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(OutputStream out) {
+               if (out != null)
+                       try {
+                               out.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(Reader in) {
+               if (in != null)
+                       try {
+                               in.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(Writer out) {
+               if (out != null)
+                       try {
+                               out.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/Tester.java b/org.argeo.util/src/org/argeo/util/Tester.java
new file mode 100644 (file)
index 0000000..31a2be4
--- /dev/null
@@ -0,0 +1,126 @@
+package org.argeo.util;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/** A generic tester based on Java assertions and functional programming. */
+public class Tester {
+       private Map<String, TesterStatus> results = Collections.synchronizedSortedMap(new TreeMap<>());
+
+       private ClassLoader classLoader;
+
+       /** Use {@link Thread#getContextClassLoader()} by default. */
+       public Tester() {
+               this(Thread.currentThread().getContextClassLoader());
+       }
+
+       public Tester(ClassLoader classLoader) {
+               this.classLoader = classLoader;
+       }
+
+       public void execute(String className) {
+               Class<?> clss;
+               try {
+                       clss = classLoader.loadClass(className);
+                       boolean assertionsEnabled = clss.desiredAssertionStatus();
+                       if (!assertionsEnabled)
+                               throw new IllegalStateException("Test runner " + getClass().getName()
+                                               + " requires Java assertions to be enabled. Call the JVM with the -ea argument.");
+               } catch (Exception e1) {
+                       throw new IllegalArgumentException("Cannot initalise test for " + className, e1);
+
+               }
+               List<Method> methods = findMethods(clss);
+               if (methods.size() == 0)
+                       throw new IllegalArgumentException("No test method found in " + clss);
+               // TODO make order more predictable?
+               for (Method method : methods) {
+                       String uid = method.getDeclaringClass().getName() + "#" + method.getName();
+                       TesterStatus testStatus = new TesterStatus(uid);
+                       Object obj = null;
+                       try {
+                               beforeTest(uid, method);
+                               obj = clss.getDeclaredConstructor().newInstance();
+                               method.invoke(obj);
+                               testStatus.setPassed();
+                               afterTestPassed(uid, method, obj);
+                       } catch (Exception e) {
+                               testStatus.setFailed(e);
+                               afterTestFailed(uid, method, obj, e);
+                       } finally {
+                               results.put(uid, testStatus);
+                       }
+               }
+       }
+
+       protected void beforeTest(String uid, Method method) {
+               // System.out.println(uid + ": STARTING");
+       }
+
+       protected void afterTestPassed(String uid, Method method, Object obj) {
+               System.out.println(uid + ": PASSED");
+       }
+
+       protected void afterTestFailed(String uid, Method method, Object obj, Throwable e) {
+               System.out.println(uid + ": FAILED");
+               e.printStackTrace();
+       }
+
+       protected List<Method> findMethods(Class<?> clss) {
+               List<Method> methods = new ArrayList<Method>();
+//             Method call = getMethod(clss, "call");
+//             if (call != null)
+//                     methods.add(call);
+//
+               for (Method method : clss.getMethods()) {
+                       if (method.getName().startsWith("test")) {
+                               methods.add(method);
+                       }
+               }
+               return methods;
+       }
+
+       protected Method getMethod(Class<?> clss, String name, Class<?>... parameterTypes) {
+               try {
+                       return clss.getMethod(name, parameterTypes);
+               } catch (NoSuchMethodException e) {
+                       return null;
+               } catch (SecurityException e) {
+                       throw new IllegalStateException(e);
+               }
+       }
+
+       public static void main(String[] args) {
+               // deal with arguments
+               String className;
+               if (args.length < 1) {
+                       System.err.println(usage());
+                       System.exit(1);
+                       throw new IllegalArgumentException();
+               } else {
+                       className = args[0];
+               }
+
+               Tester test = new Tester();
+               try {
+                       test.execute(className);
+               } catch (Throwable e) {
+                       e.printStackTrace();
+               }
+
+               Map<String, TesterStatus> r = test.results;
+               for (String uid : r.keySet()) {
+                       TesterStatus testStatus = r.get(uid);
+                       System.out.println(testStatus);
+               }
+       }
+
+       public static String usage() {
+               return "java " + Tester.class.getName() + " [test class name]";
+
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/TesterStatus.java b/org.argeo.util/src/org/argeo/util/TesterStatus.java
new file mode 100644 (file)
index 0000000..d1d14ed
--- /dev/null
@@ -0,0 +1,98 @@
+package org.argeo.util;
+
+import java.io.Serializable;
+
+/** The status of a test. */
+public class TesterStatus implements Serializable {
+       private static final long serialVersionUID = 6272975746885487000L;
+
+       private Boolean passed = null;
+       private final String uid;
+       private Throwable throwable = null;
+
+       public TesterStatus(String uid) {
+               this.uid = uid;
+       }
+
+       /** For cloning. */
+       public TesterStatus(String uid, Boolean passed, Throwable throwable) {
+               this(uid);
+               this.passed = passed;
+               this.throwable = throwable;
+       }
+
+       public synchronized Boolean isRunning() {
+               return passed == null;
+       }
+
+       public synchronized Boolean isPassed() {
+               assert passed != null;
+               return passed;
+       }
+
+       public synchronized Boolean isFailed() {
+               assert passed != null;
+               return !passed;
+       }
+
+       public synchronized void setPassed() {
+               setStatus(true);
+       }
+
+       public synchronized void setFailed() {
+               setStatus(false);
+       }
+
+       public synchronized void setFailed(Throwable throwable) {
+               setStatus(false);
+               setThrowable(throwable);
+       }
+
+       protected void setStatus(Boolean passed) {
+               if (this.passed != null)
+                       throw new IllegalStateException("Passed status of test " + uid + " is already set (to " + passed + ")");
+               this.passed = passed;
+       }
+
+       protected void setThrowable(Throwable throwable) {
+               if (this.throwable != null)
+                       throw new IllegalStateException("Throwable of test " + uid + " is already set (to " + passed + ")");
+               this.throwable = throwable;
+       }
+
+       public String getUid() {
+               return uid;
+       }
+
+       public Throwable getThrowable() {
+               return throwable;
+       }
+
+       @Override
+       protected Object clone() throws CloneNotSupportedException {
+               // TODO Auto-generated method stub
+               return super.clone();
+       }
+
+       @Override
+       public boolean equals(Object o) {
+               if (o instanceof TesterStatus) {
+                       TesterStatus other = (TesterStatus) o;
+                       // we don't check consistency for performance purposes
+                       // this equals() is supposed to be used in collections or for transfer
+                       return other.uid.equals(uid);
+               }
+               return false;
+       }
+
+       @Override
+       public int hashCode() {
+               return uid.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return uid + "\t" + (passed ? "passed" : "failed");
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/Throughput.java b/org.argeo.util/src/org/argeo/util/Throughput.java
new file mode 100644 (file)
index 0000000..266ddbc
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.util;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+/** A throughput, that is, a value per unit of time. */
+public class Throughput {
+       private final static NumberFormat usNumberFormat = NumberFormat.getInstance(Locale.US);
+
+       public enum Unit {
+               s, m, h, d
+       }
+
+       private final Double value;
+       private final Unit unit;
+
+       public Throughput(Double value, Unit unit) {
+               this.value = value;
+               this.unit = unit;
+       }
+
+       public Throughput(Long periodMs, Long count, Unit unit) {
+               if (unit.equals(Unit.s))
+                       value = ((double) count * 1000d) / periodMs;
+               else if (unit.equals(Unit.m))
+                       value = ((double) count * 60d * 1000d) / periodMs;
+               else if (unit.equals(Unit.h))
+                       value = ((double) count * 60d * 60d * 1000d) / periodMs;
+               else if (unit.equals(Unit.d))
+                       value = ((double) count * 24d * 60d * 60d * 1000d) / periodMs;
+               else
+                       throw new IllegalArgumentException("Unsupported unit " + unit);
+               this.unit = unit;
+       }
+
+       public Throughput(Double value, String unitStr) {
+               this(value, Unit.valueOf(unitStr));
+       }
+
+       public Throughput(String def) {
+               int index = def.indexOf('/');
+               if (def.length() < 3 || index <= 0 || index != def.length() - 2)
+                       throw new IllegalArgumentException(
+                                       def + " no a proper throughput definition" + " (should be <value>/<unit>, e.g. 3.54/s or 1500/h");
+               String valueStr = def.substring(0, index);
+               String unitStr = def.substring(index + 1);
+               try {
+                       this.value = usNumberFormat.parse(valueStr).doubleValue();
+               } catch (ParseException e) {
+                       throw new IllegalArgumentException("Cannot parse " + valueStr + " as a number.", e);
+               }
+               this.unit = Unit.valueOf(unitStr);
+       }
+
+       public Long asMsPeriod() {
+               if (unit.equals(Unit.s))
+                       return Math.round(1000d / value);
+               else if (unit.equals(Unit.m))
+                       return Math.round((60d * 1000d) / value);
+               else if (unit.equals(Unit.h))
+                       return Math.round((60d * 60d * 1000d) / value);
+               else if (unit.equals(Unit.d))
+                       return Math.round((24d * 60d * 60d * 1000d) / value);
+               else
+                       throw new IllegalArgumentException("Unsupported unit " + unit);
+       }
+
+       @Override
+       public String toString() {
+               return usNumberFormat.format(value) + '/' + unit;
+       }
+
+       public Double getValue() {
+               return value;
+       }
+
+       public Unit getUnit() {
+               return unit;
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java b/org.argeo.util/src/org/argeo/util/naming/AttributesDictionary.java
new file mode 100644 (file)
index 0000000..7c645f3
--- /dev/null
@@ -0,0 +1,171 @@
+package org.argeo.util.naming;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+
+public class AttributesDictionary extends Dictionary<String, Object> {
+       private final Attributes attributes;
+
+       /** The provided attributes is wrapped, not copied. */
+       public AttributesDictionary(Attributes attributes) {
+               if (attributes == null)
+                       throw new IllegalArgumentException("Attributes cannot be null");
+               this.attributes = attributes;
+       }
+
+       @Override
+       public int size() {
+               return attributes.size();
+       }
+
+       @Override
+       public boolean isEmpty() {
+               return attributes.size() == 0;
+       }
+
+       @Override
+       public Enumeration<String> keys() {
+               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+               return new Enumeration<String>() {
+
+                       @Override
+                       public boolean hasMoreElements() {
+                               return namingEnumeration.hasMoreElements();
+                       }
+
+                       @Override
+                       public String nextElement() {
+                               return namingEnumeration.nextElement();
+                       }
+
+               };
+       }
+
+       @Override
+       public Enumeration<Object> elements() {
+               NamingEnumeration<String> namingEnumeration = attributes.getIDs();
+               return new Enumeration<Object>() {
+
+                       @Override
+                       public boolean hasMoreElements() {
+                               return namingEnumeration.hasMoreElements();
+                       }
+
+                       @Override
+                       public Object nextElement() {
+                               String key = namingEnumeration.nextElement();
+                               return get(key);
+                       }
+
+               };
+       }
+
+       @Override
+       /** @returns a <code>String</code> or <code>String[]</code> */
+       public Object get(Object key) {
+               try {
+                       if (key == null)
+                               throw new IllegalArgumentException("Key cannot be null");
+                       Attribute attr = attributes.get(key.toString());
+                       if (attr == null)
+                               return null;
+                       if (attr.size() == 0)
+                               throw new IllegalStateException("There must be at least one value");
+                       else if (attr.size() == 1) {
+                               return attr.get().toString();
+                       } else {// multiple
+                               String[] res = new String[attr.size()];
+                               for (int i = 0; i < attr.size(); i++) {
+                                       Object value = attr.get();
+                                       if (value == null)
+                                               throw new RuntimeException("Values cannot be null");
+                                       res[i] = attr.get(i).toString();
+                               }
+                               return res;
+                       }
+               } catch (NamingException e) {
+                       throw new RuntimeException("Cannot get value for " + key, e);
+               }
+       }
+
+       @Override
+       public Object put(String key, Object value) {
+               if (key == null)
+                       throw new IllegalArgumentException("Key cannot be null");
+               if (value == null)
+                       throw new IllegalArgumentException("Value cannot be null");
+
+               Object oldValue = get(key);
+               Attribute attr = attributes.get(key);
+               if (attr == null) {
+                       attr = new BasicAttribute(key);
+                       attributes.put(attr);
+               }
+
+               if (value instanceof String[]) {
+                       String[] values = (String[]) value;
+                       // clean additional values
+                       for (int i = values.length; i < attr.size(); i++)
+                               attr.remove(i);
+                       // set values
+                       for (int i = 0; i < values.length; i++) {
+                               attr.set(i, values[i]);
+                       }
+               } else {
+                       if (attr.size() > 1)
+                               throw new IllegalArgumentException("Attribute " + key + " is multi-valued");
+                       if (attr.size() == 1) {
+                               try {
+                                       if (!attr.get(0).equals(value))
+                                               attr.set(0, value.toString());
+                               } catch (NamingException e) {
+                                       throw new RuntimeException("Cannot check existing value", e);
+                               }
+                       } else {
+                               attr.add(value.toString());
+                       }
+               }
+               return oldValue;
+       }
+
+       @Override
+       public Object remove(Object key) {
+               if (key == null)
+                       throw new IllegalArgumentException("Key cannot be null");
+               Object oldValue = get(key);
+               if (oldValue == null)
+                       return null;
+               return attributes.remove(key.toString());
+       }
+
+       /**
+        * Copy the <b>content</b> of an {@link Attributes} to the provided
+        * {@link Dictionary}.
+        */
+       public static void copy(Attributes attributes, Dictionary<String, Object> dictionary) {
+               AttributesDictionary ad = new AttributesDictionary(attributes);
+               Enumeration<String> keys = ad.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       dictionary.put(key, ad.get(key));
+               }
+       }
+
+       /**
+        * Copy a {@link Dictionary} into an {@link Attributes}.
+        */
+       public static void copy(Dictionary<String, Object> dictionary, Attributes attributes) {
+               AttributesDictionary ad = new AttributesDictionary(attributes);
+               Enumeration<String> keys = dictionary.keys();
+               while (keys.hasMoreElements()) {
+                       String key = keys.nextElement();
+                       ad.put(key, dictionary.get(key));
+               }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java b/org.argeo.util/src/org/argeo/util/naming/AuthPassword.java
new file mode 100644 (file)
index 0000000..973b90f
--- /dev/null
@@ -0,0 +1,140 @@
+package org.argeo.util.naming;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.StringTokenizer;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.argeo.osgi.useradmin.UserDirectoryException;
+
+/** LDAP authPassword field according to RFC 3112 */
+public class AuthPassword implements CallbackHandler {
+       private final String authScheme;
+       private final String authInfo;
+       private final String authValue;
+
+       public AuthPassword(String value) {
+               StringTokenizer st = new StringTokenizer(value, "$");
+               // TODO make it more robust, deal with bad formatting
+               this.authScheme = st.nextToken().trim();
+               this.authInfo = st.nextToken().trim();
+               this.authValue = st.nextToken().trim();
+
+               String expectedAuthScheme = getExpectedAuthScheme();
+               if (expectedAuthScheme != null && !authScheme.equals(expectedAuthScheme))
+                       throw new IllegalArgumentException(
+                                       "Auth scheme " + authScheme + " is not compatible with " + expectedAuthScheme);
+       }
+
+       protected AuthPassword(String authInfo, String authValue) {
+               this.authScheme = getExpectedAuthScheme();
+               if (authScheme == null)
+                       throw new IllegalArgumentException("Expected auth scheme cannot be null");
+               this.authInfo = authInfo;
+               this.authValue = authValue;
+       }
+
+       protected AuthPassword(AuthPassword authPassword) {
+               this.authScheme = authPassword.getAuthScheme();
+               this.authInfo = authPassword.getAuthInfo();
+               this.authValue = authPassword.getAuthValue();
+       }
+
+       protected String getExpectedAuthScheme() {
+               return null;
+       }
+
+       protected boolean matchAuthValue(Object object) {
+               return authValue.equals(object.toString());
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (!(obj instanceof AuthPassword))
+                       return false;
+               AuthPassword authPassword = (AuthPassword) obj;
+               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo)
+                               && authValue.equals(authValue);
+       }
+
+       public boolean keyEquals(AuthPassword authPassword) {
+               return authScheme.equals(authPassword.authScheme) && authInfo.equals(authPassword.authInfo);
+       }
+
+       @Override
+       public int hashCode() {
+               return authValue.hashCode();
+       }
+
+       @Override
+       public String toString() {
+               return toAuthPassword();
+       }
+
+       public final String toAuthPassword() {
+               return getAuthScheme() + '$' + authInfo + '$' + authValue;
+       }
+
+       public String getAuthScheme() {
+               return authScheme;
+       }
+
+       public String getAuthInfo() {
+               return authInfo;
+       }
+
+       public String getAuthValue() {
+               return authValue;
+       }
+
+       public static AuthPassword matchAuthValue(Attributes attributes, char[] value) {
+               try {
+                       Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+                       if (authPassword != null) {
+                               NamingEnumeration<?> values = authPassword.getAll();
+                               while (values.hasMore()) {
+                                       Object val = values.next();
+                                       AuthPassword token = new AuthPassword(val.toString());
+                                       String auth;
+                                       if (Arrays.binarySearch(value, '$') >= 0) {
+                                               auth = token.authInfo + '$' + token.authValue;
+                                       } else {
+                                               auth = token.authValue;
+                                       }
+                                       if (Arrays.equals(auth.toCharArray(), value))
+                                               return token;
+                                       // if (token.matchAuthValue(value))
+                                       // return token;
+                               }
+                       }
+                       return null;
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot check attribute", e);
+               }
+       }
+
+       public static boolean remove(Attributes attributes, AuthPassword value) {
+               Attribute authPassword = attributes.get(LdapAttrs.authPassword.name());
+               return authPassword.remove(value.toAuthPassword());
+       }
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               for (Callback callback : callbacks) {
+                       if (callback instanceof NameCallback)
+                               ((NameCallback) callback).setName(toAuthPassword());
+                       else if (callback instanceof PasswordCallback)
+                               ((PasswordCallback) callback).setPassword(getAuthValue().toCharArray());
+               }
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/Distinguished.java b/org.argeo.util/src/org/argeo/util/naming/Distinguished.java
new file mode 100644 (file)
index 0000000..6aefc16
--- /dev/null
@@ -0,0 +1,36 @@
+package org.argeo.util.naming;
+
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+/**
+ * An object that can be identified with an X.500 distinguished name.
+ * 
+ * @see https://tools.ietf.org/html/rfc1779
+ */
+public interface Distinguished {
+       /** The related distinguished name. */
+       String dn();
+
+       /** The related distinguished name as an {@link LdapName}. */
+       default LdapName distinguishedName() {
+               try {
+                       return new LdapName(dn());
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Distinguished name " + dn() + " is not properly formatted.", e);
+               }
+       }
+
+       /** List all DNs of an enumeration as strings. */
+       static Set<String> enumToDns(EnumSet<? extends Distinguished> enumSet) {
+               Set<String> res = new TreeSet<>();
+               for (Enum<? extends Distinguished> enm : enumSet) {
+                       res.add(((Distinguished) enm).dn());
+               }
+               return res;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java b/org.argeo.util/src/org/argeo/util/naming/DnsBrowser.java
new file mode 100644 (file)
index 0000000..1a67eea
--- /dev/null
@@ -0,0 +1,179 @@
+package org.argeo.util.naming;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.naming.Binding;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+public class DnsBrowser implements Closeable {
+       private final DirContext initialCtx;
+
+       public DnsBrowser() throws NamingException {
+               this(null);
+       }
+
+       public DnsBrowser(String dnsServerUrls) throws NamingException {
+               Hashtable<String, Object> env = new Hashtable<>();
+               env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+               if (dnsServerUrls != null)
+                       env.put("java.naming.provider.url", dnsServerUrls);
+               initialCtx = new InitialDirContext(env);
+       }
+
+       public Map<String, List<String>> getAllRecords(String name) throws NamingException {
+               Map<String, List<String>> res = new TreeMap<>();
+               Attributes attrs = initialCtx.getAttributes(name);
+               NamingEnumeration<String> ids = attrs.getIDs();
+               while (ids.hasMore()) {
+                       String recordType = ids.next();
+                       List<String> lst = new ArrayList<String>();
+                       res.put(recordType, lst);
+                       Attribute attr = attrs.get(recordType);
+                       addValues(attr, lst);
+               }
+               return Collections.unmodifiableMap(res);
+       }
+
+       /**
+        * Return a single record (typically A, AAAA, etc. or null if not available.
+        * Will fail if multiple records.
+        */
+       public String getRecord(String name, String recordType) throws NamingException {
+               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+               if (attrs.size() == 0)
+                       return null;
+               Attribute attr = attrs.get(recordType);
+               if (attr.size() > 1)
+                       throw new IllegalArgumentException("Multiple record type " + recordType);
+               assert attr.size() != 0;
+               Object value = attr.get();
+               assert value != null;
+               return value.toString();
+       }
+
+       /**
+        * Return records of a given type.
+        */
+       public List<String> getRecords(String name, String recordType) throws NamingException {
+               List<String> res = new ArrayList<String>();
+               Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+               Attribute attr = attrs.get(recordType);
+               addValues(attr, res);
+               return res;
+       }
+
+       /** Ordered, with preferred first. */
+       public List<String> getSrvRecordsAsHosts(String name, boolean withPort) throws NamingException {
+               List<String> raw = getRecords(name, "SRV");
+               if (raw.size() == 0)
+                       return null;
+               SortedSet<SrvRecord> res = new TreeSet<>();
+               for (int i = 0; i < raw.size(); i++) {
+                       String record = raw.get(i);
+                       String[] arr = record.split(" ");
+                       Integer priority = Integer.parseInt(arr[0]);
+                       Integer weight = Integer.parseInt(arr[1]);
+                       Integer port = Integer.parseInt(arr[2]);
+                       String hostname = arr[3];
+                       SrvRecord order = new SrvRecord(priority, weight, port, hostname);
+                       res.add(order);
+               }
+               List<String> lst = new ArrayList<>();
+               for (SrvRecord order : res) {
+                       lst.add(order.toHost(withPort));
+               }
+               return Collections.unmodifiableList(lst);
+       }
+
+       private void addValues(Attribute attr, List<String> lst) throws NamingException {
+               NamingEnumeration<?> values = attr.getAll();
+               while (values.hasMore()) {
+                       Object value = values.next();
+                       if (value != null) {
+                               if (value instanceof byte[]) {
+                                       String str = Base64.getEncoder().encodeToString((byte[]) value);
+                                       lst.add(str);
+                               } else
+                                       lst.add(value.toString());
+                       }
+               }
+
+       }
+
+       public List<String> listEntries(String name) throws NamingException {
+               List<String> res = new ArrayList<String>();
+               NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
+               while (ne.hasMore()) {
+                       Binding b = ne.next();
+                       res.add(b.getName());
+               }
+               return Collections.unmodifiableList(res);
+       }
+
+       @Override
+       public void close() throws IOException {
+               destroy();
+       }
+
+       public void destroy() {
+               try {
+                       initialCtx.close();
+               } catch (NamingException e) {
+                       // silent
+               }
+       }
+
+       public static void main(String[] args) {
+               if (args.length == 0) {
+                       printUsage(System.err);
+                       System.exit(1);
+               }
+               try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+                       String hostname = args[0];
+                       String recordType = args.length > 1 ? args[1] : "A";
+                       if (recordType.equals("*")) {
+                               Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
+                               for (String type : records.keySet()) {
+                                       for (String record : records.get(type)) {
+                                               String typeLabel;
+                                               if ("44".equals(type))
+                                                       typeLabel = "SSHFP";
+                                               else if ("46".equals(type))
+                                                       typeLabel = "RRSIG";
+                                               else if ("48".equals(type))
+                                                       typeLabel = "DNSKEY";
+                                               else
+                                                       typeLabel = type;
+                                               System.out.println(typeLabel + "\t" + record);
+                                       }
+                               }
+                       } else {
+                               System.out.println(dnsBrowser.getRecord(hostname, recordType));
+                       }
+
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public static void printUsage(PrintStream out) {
+               out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
+       }
+
+}
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.csv
new file mode 100644 (file)
index 0000000..676d727
--- /dev/null
@@ -0,0 +1,129 @@
+uid,,,0.9.2342.19200300.100.1.1,,RFC 4519
+mail,,,0.9.2342.19200300.100.1.3,,RFC 4524
+info,,,0.9.2342.19200300.100.1.4,,RFC 4524
+drink,,,0.9.2342.19200300.100.1.5,,RFC 4524
+roomNumber,,,0.9.2342.19200300.100.1.6,,RFC 4524
+photo,,,0.9.2342.19200300.100.1.7,,RFC 2798
+userClass,,,0.9.2342.19200300.100.1.8,,RFC 4524
+host,,,0.9.2342.19200300.100.1.9,,RFC 4524
+manager,,,0.9.2342.19200300.100.1.10,,RFC 4524
+documentIdentifier,,,0.9.2342.19200300.100.1.11,,RFC 4524
+documentTitle,,,0.9.2342.19200300.100.1.12,,RFC 4524
+documentVersion,,,0.9.2342.19200300.100.1.13,,RFC 4524
+documentAuthor,,,0.9.2342.19200300.100.1.14,,RFC 4524
+documentLocation,,,0.9.2342.19200300.100.1.15,,RFC 4524
+homePhone,,,0.9.2342.19200300.100.1.20,,RFC 4524
+secretary,,,0.9.2342.19200300.100.1.21,,RFC 4524
+dc,,,0.9.2342.19200300.100.1.25,,RFC 4519
+associatedDomain,,,0.9.2342.19200300.100.1.37,,RFC 4524
+associatedName,,,0.9.2342.19200300.100.1.38,,RFC 4524
+homePostalAddress,,,0.9.2342.19200300.100.1.39,,RFC 4524
+personalTitle,,,0.9.2342.19200300.100.1.40,,RFC 4524
+mobile,,,0.9.2342.19200300.100.1.41,,RFC 4524
+pager,,,0.9.2342.19200300.100.1.42,,RFC 4524
+co,,,0.9.2342.19200300.100.1.43,,RFC 4524
+uniqueIdentifier,,,0.9.2342.19200300.100.1.44,,RFC 4524
+organizationalStatus,,,0.9.2342.19200300.100.1.45,,RFC 4524
+buildingName,,,0.9.2342.19200300.100.1.48,,RFC 4524
+audio,,,0.9.2342.19200300.100.1.55,,RFC 2798
+documentPublisher,,,0.9.2342.19200300.100.1.56,,RFC 4524
+jpegPhoto,,,0.9.2342.19200300.100.1.60,,RFC 2798
+vendorName,,,1.3.6.1.1.4,,RFC 3045
+vendorVersion,,,1.3.6.1.1.5,,RFC 3045
+entryUUID,,,1.3.6.1.1.16.4,,RFC 4530
+entryDN,,,1.3.6.1.1.20,,RFC 5020
+labeledURI,,,1.3.6.1.4.1.250.1.57,,RFC 2798
+numSubordinates,,,1.3.6.1.4.1.453.16.2.103,,draft-ietf-boreham-numsubordinates
+namingContexts,,,1.3.6.1.4.1.1466.101.120.5,,RFC 4512
+altServer,,,1.3.6.1.4.1.1466.101.120.6,,RFC 4512
+supportedExtension,,,1.3.6.1.4.1.1466.101.120.7,,RFC 4512
+supportedControl,,,1.3.6.1.4.1.1466.101.120.13,,RFC 4512
+supportedSASLMechanisms,,,1.3.6.1.4.1.1466.101.120.14,,RFC 4512
+supportedLDAPVersion,,,1.3.6.1.4.1.1466.101.120.15,,RFC 4512
+ldapSyntaxes,,,1.3.6.1.4.1.1466.101.120.16,,RFC 4512
+supportedAuthPasswordSchemes,,,1.3.6.1.4.1.4203.1.3.3,,RFC 3112
+authPassword,,,1.3.6.1.4.1.4203.1.3.4,,RFC 3112
+supportedFeatures,,,1.3.6.1.4.1.4203.1.3.5,,RFC 4512
+inheritable,,,1.3.6.1.4.1.7628.5.4.1,,draft-ietf-ldup-subentry
+blockInheritance,,,1.3.6.1.4.1.7628.5.4.2,,draft-ietf-ldup-subentry
+objectClass,,,2.5.4.0,,RFC 4512
+aliasedObjectName,,,2.5.4.1,,RFC 4512
+cn,,,2.5.4.3,,RFC 4519
+sn,,,2.5.4.4,,RFC 4519
+serialNumber,,,2.5.4.5,,RFC 4519
+c,,,2.5.4.6,,RFC 4519
+l,,,2.5.4.7,,RFC 4519
+st,,,2.5.4.8,,RFC 4519
+street,,,2.5.4.9,,RFC 4519
+o,,,2.5.4.10,,RFC 4519
+ou,,,2.5.4.11,,RFC 4519
+title,,,2.5.4.12,,RFC 4519
+description,,,2.5.4.13,,RFC 4519
+searchGuide,,,2.5.4.14,,RFC 4519
+businessCategory,,,2.5.4.15,,RFC 4519
+postalAddress,,,2.5.4.16,,RFC 4519
+postalCode,,,2.5.4.17,,RFC 4519
+postOfficeBox,,,2.5.4.18,,RFC 4519
+physicalDeliveryOfficeName,,,2.5.4.19,,RFC 4519
+telephoneNumber,,,2.5.4.20,,RFC 4519
+telexNumber,,,2.5.4.21,,RFC 4519
+teletexTerminalIdentifier,,,2.5.4.22,,RFC 4519
+facsimileTelephoneNumber,,,2.5.4.23,,RFC 4519
+x121Address,,,2.5.4.24,,RFC 4519
+internationalISDNNumber,,,2.5.4.25,,RFC 4519
+registeredAddress,,,2.5.4.26,,RFC 4519
+destinationIndicator,,,2.5.4.27,,RFC 4519
+preferredDeliveryMethod,,,2.5.4.28,,RFC 4519
+member,,,2.5.4.31,,RFC 4519
+owner,,,2.5.4.32,,RFC 4519
+roleOccupant,,,2.5.4.33,,RFC 4519
+seeAlso,,,2.5.4.34,,RFC 4519
+userPassword,,,2.5.4.35,,RFC 4519
+userCertificate,,,2.5.4.36,,RFC 4523
+cACertificate,,,2.5.4.37,,RFC 4523
+authorityRevocationList,,,2.5.4.38,,RFC 4523
+certificateRevocationList,,,2.5.4.39,,RFC 4523
+crossCertificatePair,,,2.5.4.40,,RFC 4523
+name,,,2.5.4.41,,RFC 4519
+givenName,,,2.5.4.42,,RFC 4519
+initials,,,2.5.4.43,,RFC 4519
+generationQualifier,,,2.5.4.44,,RFC 4519
+x500UniqueIdentifier,,,2.5.4.45,,RFC 4519
+dnQualifier,,,2.5.4.46,,RFC 4519
+enhancedSearchGuide,,,2.5.4.47,,RFC 4519
+distinguishedName,,,2.5.4.49,,RFC 4519
+uniqueMember,,,2.5.4.50,,RFC 4519
+houseIdentifier,,,2.5.4.51,,RFC 4519
+supportedAlgorithms,,,2.5.4.52,,RFC 4523
+deltaRevocationList,,,2.5.4.53,,RFC 4523
+createTimestamp,,,2.5.18.1,,RFC 4512
+modifyTimestamp,,,2.5.18.2,,RFC 4512
+creatorsName,,,2.5.18.3,,RFC 4512
+modifiersName,,,2.5.18.4,,RFC 4512
+subschemaSubentry,,,2.5.18.10,,RFC 4512
+dITStructureRules,,,2.5.21.1,,RFC 4512
+dITContentRules,,,2.5.21.2,,RFC 4512
+matchingRules,,,2.5.21.4,,RFC 4512
+attributeTypes,,,2.5.21.5,,RFC 4512
+objectClasses,,,2.5.21.6,,RFC 4512
+nameForms,,,2.5.21.7,,RFC 4512
+matchingRuleUse,,,2.5.21.8,,RFC 4512
+structuralObjectClass,,,2.5.21.9,,RFC 4512
+governingStructureRule,,,2.5.21.10,,RFC 4512
+carLicense,,,2.16.840.1.113730.3.1.1,,RFC 2798
+departmentNumber,,,2.16.840.1.113730.3.1.2,,RFC 2798
+employeeNumber,,,2.16.840.1.113730.3.1.3,,RFC 2798
+employeeType,,,2.16.840.1.113730.3.1.4,,RFC 2798
+changeNumber,,,2.16.840.1.113730.3.1.5,,draft-good-ldap-changelog
+targetDN,,,2.16.840.1.113730.3.1.6,,draft-good-ldap-changelog
+changeType,,,2.16.840.1.113730.3.1.7,,draft-good-ldap-changelog
+changes,,,2.16.840.1.113730.3.1.8,,draft-good-ldap-changelog
+newRDN,,,2.16.840.1.113730.3.1.9,,draft-good-ldap-changelog
+deleteOldRDN,,,2.16.840.1.113730.3.1.10,,draft-good-ldap-changelog
+newSuperior,,,2.16.840.1.113730.3.1.11,,draft-good-ldap-changelog
+ref,,,2.16.840.1.113730.3.1.34,,RFC 3296
+changelog,,,2.16.840.1.113730.3.1.35,,draft-good-ldap-changelog
+preferredLanguage,,,2.16.840.1.113730.3.1.39,,RFC 2798
+userSMIMECertificate,,,2.16.840.1.113730.3.1.40,,RFC 2798
+userPKCS12,,,2.16.840.1.113730.3.1.216,,RFC 2798
+displayName,,,2.16.840.1.113730.3.1.241,,RFC 2798
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java b/org.argeo.util/src/org/argeo/util/naming/LdapAttrs.java
new file mode 100644 (file)
index 0000000..43cfe03
--- /dev/null
@@ -0,0 +1,349 @@
+package org.argeo.util.naming;
+
+import java.util.function.Supplier;
+
+/**
+ * Standard LDAP attributes as per:<br>
+ * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
+ * - <a href=
+ * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
+ * LDAP (partial)</a>
+ */
+public enum LdapAttrs implements SpecifiedName, Supplier<String> {
+       /** */
+       uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
+       /** */
+       mail("0.9.2342.19200300.100.1.3", "RFC 4524"),
+       /** */
+       info("0.9.2342.19200300.100.1.4", "RFC 4524"),
+       /** */
+       drink("0.9.2342.19200300.100.1.5", "RFC 4524"),
+       /** */
+       roomNumber("0.9.2342.19200300.100.1.6", "RFC 4524"),
+       /** */
+       photo("0.9.2342.19200300.100.1.7", "RFC 2798"),
+       /** */
+       userClass("0.9.2342.19200300.100.1.8", "RFC 4524"),
+       /** */
+       host("0.9.2342.19200300.100.1.9", "RFC 4524"),
+       /** */
+       manager("0.9.2342.19200300.100.1.10", "RFC 4524"),
+       /** */
+       documentIdentifier("0.9.2342.19200300.100.1.11", "RFC 4524"),
+       /** */
+       documentTitle("0.9.2342.19200300.100.1.12", "RFC 4524"),
+       /** */
+       documentVersion("0.9.2342.19200300.100.1.13", "RFC 4524"),
+       /** */
+       documentAuthor("0.9.2342.19200300.100.1.14", "RFC 4524"),
+       /** */
+       documentLocation("0.9.2342.19200300.100.1.15", "RFC 4524"),
+       /** */
+       homePhone("0.9.2342.19200300.100.1.20", "RFC 4524"),
+       /** */
+       secretary("0.9.2342.19200300.100.1.21", "RFC 4524"),
+       /** */
+       dc("0.9.2342.19200300.100.1.25", "RFC 4519"),
+       /** */
+       associatedDomain("0.9.2342.19200300.100.1.37", "RFC 4524"),
+       /** */
+       associatedName("0.9.2342.19200300.100.1.38", "RFC 4524"),
+       /** */
+       homePostalAddress("0.9.2342.19200300.100.1.39", "RFC 4524"),
+       /** */
+       personalTitle("0.9.2342.19200300.100.1.40", "RFC 4524"),
+       /** */
+       mobile("0.9.2342.19200300.100.1.41", "RFC 4524"),
+       /** */
+       pager("0.9.2342.19200300.100.1.42", "RFC 4524"),
+       /** */
+       co("0.9.2342.19200300.100.1.43", "RFC 4524"),
+       /** */
+       uniqueIdentifier("0.9.2342.19200300.100.1.44", "RFC 4524"),
+       /** */
+       organizationalStatus("0.9.2342.19200300.100.1.45", "RFC 4524"),
+       /** */
+       buildingName("0.9.2342.19200300.100.1.48", "RFC 4524"),
+       /** */
+       audio("0.9.2342.19200300.100.1.55", "RFC 2798"),
+       /** */
+       documentPublisher("0.9.2342.19200300.100.1.56", "RFC 4524"),
+       /** */
+       jpegPhoto("0.9.2342.19200300.100.1.60", "RFC 2798"),
+       /** */
+       vendorName("1.3.6.1.1.4", "RFC 3045"),
+       /** */
+       vendorVersion("1.3.6.1.1.5", "RFC 3045"),
+       /** */
+       entryUUID("1.3.6.1.1.16.4", "RFC 4530"),
+       /** */
+       entryDN("1.3.6.1.1.20", "RFC 5020"),
+       /** */
+       labeledURI("1.3.6.1.4.1.250.1.57", "RFC 2798"),
+       /** */
+       numSubordinates("1.3.6.1.4.1.453.16.2.103", "draft-ietf-boreham-numsubordinates"),
+       /** */
+       namingContexts("1.3.6.1.4.1.1466.101.120.5", "RFC 4512"),
+       /** */
+       altServer("1.3.6.1.4.1.1466.101.120.6", "RFC 4512"),
+       /** */
+       supportedExtension("1.3.6.1.4.1.1466.101.120.7", "RFC 4512"),
+       /** */
+       supportedControl("1.3.6.1.4.1.1466.101.120.13", "RFC 4512"),
+       /** */
+       supportedSASLMechanisms("1.3.6.1.4.1.1466.101.120.14", "RFC 4512"),
+       /** */
+       supportedLDAPVersion("1.3.6.1.4.1.1466.101.120.15", "RFC 4512"),
+       /** */
+       ldapSyntaxes("1.3.6.1.4.1.1466.101.120.16", "RFC 4512"),
+       /** */
+       supportedAuthPasswordSchemes("1.3.6.1.4.1.4203.1.3.3", "RFC 3112"),
+       /** */
+       authPassword("1.3.6.1.4.1.4203.1.3.4", "RFC 3112"),
+       /** */
+       supportedFeatures("1.3.6.1.4.1.4203.1.3.5", "RFC 4512"),
+       /** */
+       inheritable("1.3.6.1.4.1.7628.5.4.1", "draft-ietf-ldup-subentry"),
+       /** */
+       blockInheritance("1.3.6.1.4.1.7628.5.4.2", "draft-ietf-ldup-subentry"),
+       /** */
+       objectClass("2.5.4.0", "RFC 4512"),
+       /** */
+       aliasedObjectName("2.5.4.1", "RFC 4512"),
+       /** */
+       cn("2.5.4.3", "RFC 4519"),
+       /** */
+       sn("2.5.4.4", "RFC 4519"),
+       /** */
+       serialNumber("2.5.4.5", "RFC 4519"),
+       /** */
+       c("2.5.4.6", "RFC 4519"),
+       /** */
+       l("2.5.4.7", "RFC 4519"),
+       /** */
+       st("2.5.4.8", "RFC 4519"),
+       /** */
+       street("2.5.4.9", "RFC 4519"),
+       /** */
+       o("2.5.4.10", "RFC 4519"),
+       /** */
+       ou("2.5.4.11", "RFC 4519"),
+       /** */
+       title("2.5.4.12", "RFC 4519"),
+       /** */
+       description("2.5.4.13", "RFC 4519"),
+       /** */
+       searchGuide("2.5.4.14", "RFC 4519"),
+       /** */
+       businessCategory("2.5.4.15", "RFC 4519"),
+       /** */
+       postalAddress("2.5.4.16", "RFC 4519"),
+       /** */
+       postalCode("2.5.4.17", "RFC 4519"),
+       /** */
+       postOfficeBox("2.5.4.18", "RFC 4519"),
+       /** */
+       physicalDeliveryOfficeName("2.5.4.19", "RFC 4519"),
+       /** */
+       telephoneNumber("2.5.4.20", "RFC 4519"),
+       /** */
+       telexNumber("2.5.4.21", "RFC 4519"),
+       /** */
+       teletexTerminalIdentifier("2.5.4.22", "RFC 4519"),
+       /** */
+       facsimileTelephoneNumber("2.5.4.23", "RFC 4519"),
+       /** */
+       x121Address("2.5.4.24", "RFC 4519"),
+       /** */
+       internationalISDNNumber("2.5.4.25", "RFC 4519"),
+       /** */
+       registeredAddress("2.5.4.26", "RFC 4519"),
+       /** */
+       destinationIndicator("2.5.4.27", "RFC 4519"),
+       /** */
+       preferredDeliveryMethod("2.5.4.28", "RFC 4519"),
+       /** */
+       member("2.5.4.31", "RFC 4519"),
+       /** */
+       owner("2.5.4.32", "RFC 4519"),
+       /** */
+       roleOccupant("2.5.4.33", "RFC 4519"),
+       /** */
+       seeAlso("2.5.4.34", "RFC 4519"),
+       /** */
+       userPassword("2.5.4.35", "RFC 4519"),
+       /** */
+       userCertificate("2.5.4.36", "RFC 4523"),
+       /** */
+       cACertificate("2.5.4.37", "RFC 4523"),
+       /** */
+       authorityRevocationList("2.5.4.38", "RFC 4523"),
+       /** */
+       certificateRevocationList("2.5.4.39", "RFC 4523"),
+       /** */
+       crossCertificatePair("2.5.4.40", "RFC 4523"),
+       /** */
+       name("2.5.4.41", "RFC 4519"),
+       /** */
+       givenName("2.5.4.42", "RFC 4519"),
+       /** */
+       initials("2.5.4.43", "RFC 4519"),
+       /** */
+       generationQualifier("2.5.4.44", "RFC 4519"),
+       /** */
+       x500UniqueIdentifier("2.5.4.45", "RFC 4519"),
+       /** */
+       dnQualifier("2.5.4.46", "RFC 4519"),
+       /** */
+       enhancedSearchGuide("2.5.4.47", "RFC 4519"),
+       /** */
+       distinguishedName("2.5.4.49", "RFC 4519"),
+       /** */
+       uniqueMember("2.5.4.50", "RFC 4519"),
+       /** */
+       houseIdentifier("2.5.4.51", "RFC 4519"),
+       /** */
+       supportedAlgorithms("2.5.4.52", "RFC 4523"),
+       /** */
+       deltaRevocationList("2.5.4.53", "RFC 4523"),
+       /** */
+       createTimestamp("2.5.18.1", "RFC 4512"),
+       /** */
+       modifyTimestamp("2.5.18.2", "RFC 4512"),
+       /** */
+       creatorsName("2.5.18.3", "RFC 4512"),
+       /** */
+       modifiersName("2.5.18.4", "RFC 4512"),
+       /** */
+       subschemaSubentry("2.5.18.10", "RFC 4512"),
+       /** */
+       dITStructureRules("2.5.21.1", "RFC 4512"),
+       /** */
+       dITContentRules("2.5.21.2", "RFC 4512"),
+       /** */
+       matchingRules("2.5.21.4", "RFC 4512"),
+       /** */
+       attributeTypes("2.5.21.5", "RFC 4512"),
+       /** */
+       objectClasses("2.5.21.6", "RFC 4512"),
+       /** */
+       nameForms("2.5.21.7", "RFC 4512"),
+       /** */
+       matchingRuleUse("2.5.21.8", "RFC 4512"),
+       /** */
+       structuralObjectClass("2.5.21.9", "RFC 4512"),
+       /** */
+       governingStructureRule("2.5.21.10", "RFC 4512"),
+       /** */
+       carLicense("2.16.840.1.113730.3.1.1", "RFC 2798"),
+       /** */
+       departmentNumber("2.16.840.1.113730.3.1.2", "RFC 2798"),
+       /** */
+       employeeNumber("2.16.840.1.113730.3.1.3", "RFC 2798"),
+       /** */
+       employeeType("2.16.840.1.113730.3.1.4", "RFC 2798"),
+       /** */
+       changeNumber("2.16.840.1.113730.3.1.5", "draft-good-ldap-changelog"),
+       /** */
+       targetDN("2.16.840.1.113730.3.1.6", "draft-good-ldap-changelog"),
+       /** */
+       changeType("2.16.840.1.113730.3.1.7", "draft-good-ldap-changelog"),
+       /** */
+       changes("2.16.840.1.113730.3.1.8", "draft-good-ldap-changelog"),
+       /** */
+       newRDN("2.16.840.1.113730.3.1.9", "draft-good-ldap-changelog"),
+       /** */
+       deleteOldRDN("2.16.840.1.113730.3.1.10", "draft-good-ldap-changelog"),
+       /** */
+       newSuperior("2.16.840.1.113730.3.1.11", "draft-good-ldap-changelog"),
+       /** */
+       ref("2.16.840.1.113730.3.1.34", "RFC 3296"),
+       /** */
+       changelog("2.16.840.1.113730.3.1.35", "draft-good-ldap-changelog"),
+       /** */
+       preferredLanguage("2.16.840.1.113730.3.1.39", "RFC 2798"),
+       /** */
+       userSMIMECertificate("2.16.840.1.113730.3.1.40", "RFC 2798"),
+       /** */
+       userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
+       /** */
+       displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
+
+       // Sun memberOf
+       memberOf("1.2.840.113556.1.2.102", "389 DS memberOf"),
+
+       // KERBEROS (partial)
+       krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions"),
+
+       // RFC 2985 and RFC 3039 (partial)
+       dateOfBirth("1.3.6.1.5.5.7.9.1", "RFC 2985"),
+       /** */
+       placeOfBirth("1.3.6.1.5.5.7.9.2", "RFC 2985"),
+       /** */
+       gender("1.3.6.1.5.5.7.9.3", "RFC 2985"),
+       /** */
+       countryOfCitizenship("1.3.6.1.5.5.7.9.4", "RFC 2985"),
+       /** */
+       countryOfResidence("1.3.6.1.5.5.7.9.5", "RFC 2985"),
+       //
+       ;
+
+       public final static String DN = "dn";
+
+//     private final static String LDAP_ = "ldap:";
+
+       private final String oid, spec;
+
+       LdapAttrs(String oid, String spec) {
+               this.oid = oid;
+               this.spec = spec;
+       }
+
+       @Override
+       public String getID() {
+               return oid;
+       }
+
+       @Override
+       public String getSpec() {
+               return spec;
+       }
+
+       public String getPrefix() {
+               return prefix();
+       }
+
+       public static String prefix() {
+               return "ldap";
+       }
+
+       @Deprecated
+       public String property() {
+               return get();
+       }
+
+       @Deprecated
+       public String qualified() {
+               return get();
+       }
+
+       public String get() {
+               String prefix = getPrefix();
+               return prefix != null ? prefix + ":" + name() : name();
+       }
+
+       public String getNamespace() {
+               return namespace();
+       }
+
+       public static String namespace() {
+               return "http://www.argeo.org/ns/ldap";
+       }
+
+       @Override
+       public final String toString() {
+               // must return the name
+               return name();
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.csv
new file mode 100644 (file)
index 0000000..3d907cb
--- /dev/null
@@ -0,0 +1,42 @@
+account,,,0.9.2342.19200300.100.4.5,,RFC 4524
+document,,,0.9.2342.19200300.100.4.6,,RFC 4524
+room,,,0.9.2342.19200300.100.4.7,,RFC 4524
+documentSeries,,,0.9.2342.19200300.100.4.9,,RFC 4524
+domain,,,0.9.2342.19200300.100.4.13,,RFC 4524
+rFC822localPart,,,0.9.2342.19200300.100.4.14,,RFC 4524
+domainRelatedObject,,,0.9.2342.19200300.100.4.17,,RFC 4524
+friendlyCountry,,,0.9.2342.19200300.100.4.18,,RFC 4524
+simpleSecurityObject,,,0.9.2342.19200300.100.4.19,,RFC 4524
+uidObject,,,1.3.6.1.1.3.1,,RFC 4519
+extensibleObject,,,1.3.6.1.4.1.1466.101.120.111,,RFC 4512
+dcObject,,,1.3.6.1.4.1.1466.344,,RFC 4519
+authPasswordObject,,,1.3.6.1.4.1.4203.1.4.7,,RFC 3112
+namedObject,,,1.3.6.1.4.1.5322.13.1.1,,draft-howard-namedobject
+inheritableLDAPSubEntry,,,1.3.6.1.4.1.7628.5.6.1.1,,draft-ietf-ldup-subentry
+top,,,2.5.6.0,,RFC 4512
+alias,,,2.5.6.1,,RFC 4512
+country,,,2.5.6.2,,RFC 4519
+locality,,,2.5.6.3,,RFC 4519
+organization,,,2.5.6.4,,RFC 4519
+organizationalUnit,,,2.5.6.5,,RFC 4519
+person,,,2.5.6.6,,RFC 4519
+organizationalPerson,,,2.5.6.7,,RFC 4519
+organizationalRole,,,2.5.6.8,,RFC 4519
+groupOfNames,,,2.5.6.9,,RFC 4519
+residentialPerson,,,2.5.6.10,,RFC 4519
+applicationProcess,,,2.5.6.11,,RFC 4519
+device,,,2.5.6.14,,RFC 4519
+strongAuthenticationUser,,,2.5.6.15,,RFC 4523
+certificationAuthority,,,2.5.6.16,,RFC 4523
+certificationAuthority-V2,,,2.5.6.16.2,,RFC 4523
+groupOfUniqueNames,,,2.5.6.17,,RFC 4519
+userSecurityInformation,,,2.5.6.18,,RFC 4523
+cRLDistributionPoint,,,2.5.6.19,,RFC 4523
+pkiUser,,,2.5.6.21,,RFC 4523
+pkiCA,,,2.5.6.22,,RFC 4523
+deltaCRL,,,2.5.6.23,,RFC 4523
+subschema,,,2.5.20.1,,RFC 4512
+ldapSubEntry,,,2.16.840.1.113719.2.142.6.1.1,,draft-ietf-ldup-subentry
+changeLogEntry,,,2.16.840.1.113730.3.2.1,,draft-good-ldap-changelog
+inetOrgPerson,,,2.16.840.1.113730.3.2.2,,RFC 2798
+referral,,,2.16.840.1.113730.3.2.6,,RFC 3296
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java b/org.argeo.util/src/org/argeo/util/naming/LdapObjs.java
new file mode 100644 (file)
index 0000000..c616d14
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.util.naming;
+
+/**
+ * Standard LDAP object classes as per
+ * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
+ * oid-reference</a>
+ */
+public enum LdapObjs implements SpecifiedName {
+       account("0.9.2342.19200300.100.4.5", "RFC 4524"),
+       /** */
+       document("0.9.2342.19200300.100.4.6", "RFC 4524"),
+       /** */
+       room("0.9.2342.19200300.100.4.7", "RFC 4524"),
+       /** */
+       documentSeries("0.9.2342.19200300.100.4.9", "RFC 4524"),
+       /** */
+       domain("0.9.2342.19200300.100.4.13", "RFC 4524"),
+       /** */
+       rFC822localPart("0.9.2342.19200300.100.4.14", "RFC 4524"),
+       /** */
+       domainRelatedObject("0.9.2342.19200300.100.4.17", "RFC 4524"),
+       /** */
+       friendlyCountry("0.9.2342.19200300.100.4.18", "RFC 4524"),
+       /** */
+       simpleSecurityObject("0.9.2342.19200300.100.4.19", "RFC 4524"),
+       /** */
+       uidObject("1.3.6.1.1.3.1", "RFC 4519"),
+       /** */
+       extensibleObject("1.3.6.1.4.1.1466.101.120.111", "RFC 4512"),
+       /** */
+       dcObject("1.3.6.1.4.1.1466.344", "RFC 4519"),
+       /** */
+       authPasswordObject("1.3.6.1.4.1.4203.1.4.7", "RFC 3112"),
+       /** */
+       namedObject("1.3.6.1.4.1.5322.13.1.1", "draft-howard-namedobject"),
+       /** */
+       inheritableLDAPSubEntry("1.3.6.1.4.1.7628.5.6.1.1", "draft-ietf-ldup-subentry"),
+       /** */
+       top("2.5.6.0", "RFC 4512"),
+       /** */
+       alias("2.5.6.1", "RFC 4512"),
+       /** */
+       country("2.5.6.2", "RFC 4519"),
+       /** */
+       locality("2.5.6.3", "RFC 4519"),
+       /** */
+       organization("2.5.6.4", "RFC 4519"),
+       /** */
+       organizationalUnit("2.5.6.5", "RFC 4519"),
+       /** */
+       person("2.5.6.6", "RFC 4519"),
+       /** */
+       organizationalPerson("2.5.6.7", "RFC 4519"),
+       /** */
+       organizationalRole("2.5.6.8", "RFC 4519"),
+       /** */
+       groupOfNames("2.5.6.9", "RFC 4519"),
+       /** */
+       residentialPerson("2.5.6.10", "RFC 4519"),
+       /** */
+       applicationProcess("2.5.6.11", "RFC 4519"),
+       /** */
+       device("2.5.6.14", "RFC 4519"),
+       /** */
+       strongAuthenticationUser("2.5.6.15", "RFC 4523"),
+       /** */
+       certificationAuthority("2.5.6.16", "RFC 4523"),
+       // /** Should be certificationAuthority-V2 */
+       // certificationAuthority_V2("2.5.6.16.2", "RFC 4523") {
+       // },
+       /** */
+       groupOfUniqueNames("2.5.6.17", "RFC 4519"),
+       /** */
+       userSecurityInformation("2.5.6.18", "RFC 4523"),
+       /** */
+       cRLDistributionPoint("2.5.6.19", "RFC 4523"),
+       /** */
+       pkiUser("2.5.6.21", "RFC 4523"),
+       /** */
+       pkiCA("2.5.6.22", "RFC 4523"),
+       /** */
+       deltaCRL("2.5.6.23", "RFC 4523"),
+       /** */
+       subschema("2.5.20.1", "RFC 4512"),
+       /** */
+       ldapSubEntry("2.16.840.1.113719.2.142.6.1.1", "draft-ietf-ldup-subentry"),
+       /** */
+       changeLogEntry("2.16.840.1.113730.3.2.1", "draft-good-ldap-changelog"),
+       /** */
+       inetOrgPerson("2.16.840.1.113730.3.2.2", "RFC 2798"),
+       /** */
+       referral("2.16.840.1.113730.3.2.6", "RFC 3296");
+
+       private final static String LDAP_ = "ldap:";
+       private final String oid, spec;
+
+       private LdapObjs(String oid, String spec) {
+               this.oid = oid;
+               this.spec = spec;
+       }
+
+       public String getOid() {
+               return oid;
+       }
+
+       public String getSpec() {
+               return spec;
+       }
+
+       public String property() {
+               return new StringBuilder(LDAP_).append(name()).toString();
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifParser.java b/org.argeo.util/src/org/argeo/util/naming/LdifParser.java
new file mode 100644 (file)
index 0000000..d68173a
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.util.naming;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.osgi.useradmin.UserDirectoryException;
+
+/** Basic LDIF parser. */
+public class LdifParser {
+       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+
+       protected Attributes addAttributes(SortedMap<LdapName, Attributes> res, int lineNumber, LdapName currentDn,
+                       Attributes currentAttributes) {
+               try {
+                       Rdn nameRdn = currentDn.getRdn(currentDn.size() - 1);
+                       Attribute nameAttr = currentAttributes.get(nameRdn.getType());
+                       if (nameAttr == null)
+                               currentAttributes.put(nameRdn.getType(), nameRdn.getValue());
+                       else if (!nameAttr.get().equals(nameRdn.getValue()))
+                               throw new UserDirectoryException(
+                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + currentDn
+                                                               + " (shortly before line " + lineNumber + " in LDIF file)");
+                       Attributes previous = res.put(currentDn, currentAttributes);
+                       return previous;
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot add " + currentDn, e);
+               }
+       }
+
+       /** With UTF-8 charset */
+       public SortedMap<LdapName, Attributes> read(InputStream in) throws IOException {
+               try (Reader reader = new InputStreamReader(in, DEFAULT_CHARSET)) {
+                       return read(reader);
+               } finally {
+                       try {
+                               in.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+               }
+       }
+
+       /** Will close the reader. */
+       public SortedMap<LdapName, Attributes> read(Reader reader) throws IOException {
+               SortedMap<LdapName, Attributes> res = new TreeMap<LdapName, Attributes>();
+               try {
+                       List<String> lines = new ArrayList<>();
+                       try (BufferedReader br = new BufferedReader(reader)) {
+                               String line;
+                               while ((line = br.readLine()) != null) {
+                                       lines.add(line);
+                               }
+                       }
+                       if (lines.size() == 0)
+                               return res;
+                       // add an empty new line since the last line is not checked
+                       if (!lines.get(lines.size() - 1).equals(""))
+                               lines.add("");
+
+                       LdapName currentDn = null;
+                       Attributes currentAttributes = null;
+                       StringBuilder currentEntry = new StringBuilder();
+
+                       readLines: for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
+                               String line = lines.get(lineNumber);
+                               boolean isLastLine = false;
+                               if (lineNumber == lines.size() - 1)
+                                       isLastLine = true;
+                               if (line.startsWith(" ")) {
+                                       currentEntry.append(line.substring(1));
+                                       if (!isLastLine)
+                                               continue readLines;
+                               }
+
+                               if (currentEntry.length() != 0 || isLastLine) {
+                                       // read previous attribute
+                                       StringBuilder attrId = new StringBuilder(8);
+                                       boolean isBase64 = false;
+                                       readAttrId: for (int i = 0; i < currentEntry.length(); i++) {
+                                               char c = currentEntry.charAt(i);
+                                               if (c == ':') {
+                                                       if (i + 1 < currentEntry.length() && currentEntry.charAt(i + 1) == ':')
+                                                               isBase64 = true;
+                                                       currentEntry.delete(0, i + (isBase64 ? 2 : 1));
+                                                       break readAttrId;
+                                               } else {
+                                                       attrId.append(c);
+                                               }
+                                       }
+
+                                       String attributeId = attrId.toString();
+                                       // TODO should we really trim the end of the string as well?
+                                       String cleanValueStr = currentEntry.toString().trim();
+                                       Object attributeValue = isBase64 ? Base64.getDecoder().decode(cleanValueStr) : cleanValueStr;
+
+                                       // manage DN attributes
+                                       if (attributeId.equals(LdapAttrs.DN) || isLastLine) {
+                                               if (currentDn != null) {
+                                                       //
+                                                       // ADD
+                                                       //
+                                                       Attributes previous = addAttributes(res, lineNumber, currentDn, currentAttributes);
+                                                       if (previous != null) {
+//                                                             log.warn("There was already an entry with DN " + currentDn
+//                                                                             + ", which has been discarded by a subsequent one.");
+                                                       }
+                                               }
+
+                                               if (attributeId.equals(LdapAttrs.DN))
+                                                       try {
+                                                               currentDn = new LdapName(attributeValue.toString());
+                                                               currentAttributes = new BasicAttributes(true);
+                                                       } catch (InvalidNameException e) {
+//                                                             log.error(attributeValue + " not a valid DN, skipping the entry.");
+                                                               currentDn = null;
+                                                               currentAttributes = null;
+                                                       }
+                                       }
+
+                                       // store attribute
+                                       if (currentAttributes != null) {
+                                               Attribute attribute = currentAttributes.get(attributeId);
+                                               if (attribute == null) {
+                                                       attribute = new BasicAttribute(attributeId);
+                                                       currentAttributes.put(attribute);
+                                               }
+                                               attribute.add(attributeValue);
+                                       }
+                                       currentEntry = new StringBuilder();
+                               }
+                               currentEntry.append(line);
+                       }
+               } finally {
+                       try {
+                               reader.close();
+                       } catch (IOException e) {
+                               // silent
+                       }
+               }
+               return res;
+       }
+}
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java b/org.argeo.util/src/org/argeo/util/naming/LdifWriter.java
new file mode 100644 (file)
index 0000000..457380b
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.util.naming;
+
+import static org.argeo.util.naming.LdapAttrs.DN;
+import static org.argeo.util.naming.LdapAttrs.member;
+import static org.argeo.util.naming.LdapAttrs.objectClass;
+import static org.argeo.util.naming.LdapAttrs.uniqueMember;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+import org.argeo.osgi.useradmin.UserDirectoryException;
+
+/** Basic LDIF writer */
+public class LdifWriter {
+       private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
+       private final Writer writer;
+
+       /** Writer must be closed by caller */
+       public LdifWriter(Writer writer) {
+               this.writer = writer;
+       }
+
+       /** Stream must be closed by caller */
+       public LdifWriter(OutputStream out) {
+               this(new OutputStreamWriter(out, DEFAULT_CHARSET));
+       }
+
+       public void writeEntry(LdapName name, Attributes attributes) throws IOException {
+               try {
+                       // check consistency
+                       Rdn nameRdn = name.getRdn(name.size() - 1);
+                       Attribute nameAttr = attributes.get(nameRdn.getType());
+                       if (!nameAttr.get().equals(nameRdn.getValue()))
+                               throw new UserDirectoryException(
+                                               "Attribute " + nameAttr.getID() + "=" + nameAttr.get() + " not consistent with DN " + name);
+
+                       writer.append(DN + ": ").append(name.toString()).append('\n');
+                       Attribute objectClassAttr = attributes.get(objectClass.name());
+                       if (objectClassAttr != null)
+                               writeAttribute(objectClassAttr);
+                       attributes: for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+                               Attribute attribute = attrs.next();
+                               if (attribute.getID().equals(DN) || attribute.getID().equals(objectClass.name()))
+                                       continue attributes;// skip DN attribute
+                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+                                       continue attributes;// skip member and uniqueMember attributes, so that they are always written last
+                               writeAttribute(attribute);
+                       }
+                       // write member and uniqueMember attributes last
+                       for (NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMore();) {
+                               Attribute attribute = attrs.next();
+                               if (attribute.getID().equals(member.name()) || attribute.getID().equals(uniqueMember.name()))
+                                       writeMemberAttribute(attribute);
+                       }
+                       writer.append('\n');
+                       writer.flush();
+               } catch (NamingException e) {
+                       throw new UserDirectoryException("Cannot write LDIF", e);
+               }
+       }
+
+       public void write(Map<LdapName, Attributes> entries) throws IOException {
+               for (LdapName dn : entries.keySet())
+                       writeEntry(dn, entries.get(dn));
+       }
+
+       protected void writeAttribute(Attribute attribute) throws NamingException, IOException {
+               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+                       Object value = attrValues.next();
+                       if (value instanceof byte[]) {
+                               String encoded = Base64.getEncoder().encodeToString((byte[]) value);
+                               writer.append(attribute.getID()).append(":: ").append(encoded).append('\n');
+                       } else {
+                               writer.append(attribute.getID()).append(": ").append(value.toString()).append('\n');
+                       }
+               }
+       }
+
+       protected void writeMemberAttribute(Attribute attribute) throws NamingException, IOException {
+               // Note: duplicate entries will be swallowed
+               SortedSet<String> values = new TreeSet<>();
+               for (NamingEnumeration<?> attrValues = attribute.getAll(); attrValues.hasMore();) {
+                       String value = attrValues.next().toString();
+                       values.add(value);
+               }
+
+               for (String value : values) {
+                       writer.append(attribute.getID()).append(": ").append(value).append('\n');
+               }
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java b/org.argeo.util/src/org/argeo/util/naming/NamingUtils.java
new file mode 100644 (file)
index 0000000..ff4ed31
--- /dev/null
@@ -0,0 +1,106 @@
+package org.argeo.util.naming;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class NamingUtils {
+       /** As per https://tools.ietf.org/html/rfc4517#section-3.3.13 */
+       private final static DateTimeFormatter utcLdapDate = DateTimeFormatter.ofPattern("uuuuMMddHHmmssX")
+                       .withZone(ZoneOffset.UTC);
+
+       /** @return null if not parseable */
+       public static Instant ldapDateToInstant(String ldapDate) {
+               try {
+                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toInstant();
+               } catch (DateTimeParseException e) {
+                       return null;
+               }
+       }
+
+       /** @return null if not parseable */
+       public static ZonedDateTime ldapDateToZonedDateTime(String ldapDate) {
+               try {
+                       return OffsetDateTime.parse(ldapDate, utcLdapDate).toZonedDateTime();
+               } catch (DateTimeParseException e) {
+                       return null;
+               }
+       }
+
+       public static Calendar ldapDateToCalendar(String ldapDate) {
+               OffsetDateTime instant = OffsetDateTime.parse(ldapDate, utcLdapDate);
+               GregorianCalendar calendar = new GregorianCalendar();
+               calendar.set(Calendar.DAY_OF_MONTH, instant.get(ChronoField.DAY_OF_MONTH));
+               calendar.set(Calendar.MONTH, instant.get(ChronoField.MONTH_OF_YEAR));
+               calendar.set(Calendar.YEAR, instant.get(ChronoField.YEAR));
+               return calendar;
+       }
+
+       public static String instantToLdapDate(ZonedDateTime instant) {
+               return utcLdapDate.format(instant.withZoneSameInstant(ZoneOffset.UTC));
+       }
+
+       public static String getQueryValue(Map<String, List<String>> query, String key) {
+               if (!query.containsKey(key))
+                       return null;
+               List<String> val = query.get(key);
+               if (val.size() == 1)
+                       return val.get(0);
+               else
+                       throw new IllegalArgumentException("There are " + val.size() + " value(s) for " + key);
+       }
+
+       public static Map<String, List<String>> queryToMap(URI uri) {
+               return queryToMap(uri.getQuery());
+       }
+
+       private static Map<String, List<String>> queryToMap(String queryPart) {
+               try {
+                       final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
+                       if (queryPart == null)
+                               return query_pairs;
+                       final String[] pairs = queryPart.split("&");
+                       for (String pair : pairs) {
+                               final int idx = pair.indexOf("=");
+                               final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name())
+                                               : pair;
+                               if (!query_pairs.containsKey(key)) {
+                                       query_pairs.put(key, new LinkedList<String>());
+                               }
+                               final String value = idx > 0 && pair.length() > idx + 1
+                                               ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name())
+                                               : null;
+                               query_pairs.get(key).add(value);
+                       }
+                       return query_pairs;
+               } catch (UnsupportedEncodingException e) {
+                       throw new IllegalArgumentException("Cannot convert " + queryPart + " to map", e);
+               }
+       }
+
+       private NamingUtils() {
+
+       }
+
+       public static void main(String args[]) {
+               ZonedDateTime now = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC);
+               String str = utcLdapDate.format(now);
+               System.out.println(str);
+               utcLdapDate.parse(str);
+               utcLdapDate.parse("19520512000000Z");
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/NodeOID.java b/org.argeo.util/src/org/argeo/util/naming/NodeOID.java
new file mode 100644 (file)
index 0000000..ea163d6
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.util.naming;
+
+interface NodeOID {
+       String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
+
+       // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
+       
+       // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
+       String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
+       
+       // ATTRIBUTE TYPES
+       String ATTRIBUTE_TYPES = BASE + ".4";
+
+       // OBJECT CLASSES
+       String OBJECT_CLASSES = BASE + ".6";
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java b/org.argeo.util/src/org/argeo/util/naming/SharedSecret.java
new file mode 100644 (file)
index 0000000..7f05754
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.util.naming;
+
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+public class SharedSecret extends AuthPassword {
+       public final static String X_SHARED_SECRET = "X-SharedSecret";
+       private final Instant expiry;
+
+       public SharedSecret(String authInfo, String authValue) {
+               super(authInfo, authValue);
+               expiry = null;
+       }
+
+       public SharedSecret(AuthPassword authPassword) {
+               super(authPassword);
+               String authInfo = getAuthInfo();
+               if (authInfo.length() == 16) {
+                       expiry = NamingUtils.ldapDateToInstant(authInfo);
+               } else {
+                       expiry = null;
+               }
+       }
+
+       public SharedSecret(ZonedDateTime expiryTimestamp, String value) {
+               super(NamingUtils.instantToLdapDate(expiryTimestamp), value);
+               expiry = expiryTimestamp.withZoneSameInstant(ZoneOffset.UTC).toInstant();
+       }
+
+       public SharedSecret(int hours, String value) {
+               this(ZonedDateTime.now().plusHours(hours), value);
+       }
+
+       @Override
+       protected String getExpectedAuthScheme() {
+               return X_SHARED_SECRET;
+       }
+
+       public boolean isExpired() {
+               if (expiry == null)
+                       return false;
+               return expiry.isBefore(Instant.now());
+       }
+
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java b/org.argeo.util/src/org/argeo/util/naming/SpecifiedName.java
new file mode 100644 (file)
index 0000000..22f2a2d
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.util.naming;
+
+/**
+ * A name which has been specified and for which an id has been defined
+ * (typically an OID).
+ */
+public interface SpecifiedName {
+       /** The name */
+       String name();
+
+       /** An RFC or the URLof some specification */
+       default String getSpec() {
+               return null;
+       }
+
+       /** Typically an OID */
+       default String getID() {
+               return getClass().getName() + "." + name();
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java b/org.argeo.util/src/org/argeo/util/naming/SrvRecord.java
new file mode 100644 (file)
index 0000000..f2476a9
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.util.naming;
+
+class SrvRecord implements Comparable<SrvRecord> {
+       private final Integer priority;
+       private final Integer weight;
+       private final Integer port;
+       private final String hostname;
+
+       public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
+               this.priority = priority;
+               this.weight = weight;
+               this.port = port;
+               this.hostname = hostname;
+       }
+
+       @Override
+       public int compareTo(SrvRecord other) {
+               // https: // en.wikipedia.org/wiki/SRV_record
+               if (priority != other.priority)
+                       return priority - other.priority;
+               if (weight != other.weight)
+                       return other.weight - other.weight;
+               String host = toHost(false);
+               String otherHost = other.toHost(false);
+               if (host.length() == otherHost.length())
+                       return host.compareTo(otherHost);
+               else
+                       return host.length() - otherHost.length();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof SrvRecord) {
+                       SrvRecord other = (SrvRecord) obj;
+                       return priority == other.priority && weight == other.weight && port == other.port
+                                       && hostname.equals(other.hostname);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return priority + " " + weight;
+       }
+
+       public String toHost(boolean withPort) {
+               String hostStr = hostname;
+               if (hostname.charAt(hostname.length() - 1) == '.')
+                       hostStr = hostname.substring(0, hostname.length() - 1);
+               return hostStr + (withPort ? ":" + port : "");
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/naming/package-info.java b/org.argeo.util/src/org/argeo/util/naming/package-info.java
new file mode 100644 (file)
index 0000000..f62af36
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic naming and LDAP support. */
+package org.argeo.util.naming;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/package-info.java b/org.argeo.util/src/org/argeo/util/package-info.java
new file mode 100644 (file)
index 0000000..4354b0a
--- /dev/null
@@ -0,0 +1,2 @@
+/** Generic Java utilities. */
+package org.argeo.util;
\ No newline at end of file
diff --git a/org.argeo.util/src/org/argeo/util/register/Component.java b/org.argeo.util/src/org/argeo/util/register/Component.java
new file mode 100644 (file)
index 0000000..4a812f8
--- /dev/null
@@ -0,0 +1,283 @@
+package org.argeo.util.register;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+/**
+ * A wrapper for an object, whose dependencies and life cycle can be managed.
+ */
+public class Component<I> {
+
+       private final I instance;
+
+       private final Runnable init;
+       private final Runnable close;
+
+       private final Map<Class<? super I>, PublishedType<? super I>> types;
+       private final Set<Dependency<?>> dependencies;
+
+       private CompletableFuture<Void> activationStarted = null;
+       private CompletableFuture<Void> activated = null;
+
+       private CompletableFuture<Void> deactivationStarted = null;
+       private CompletableFuture<Void> deactivated = null;
+
+       private Set<Dependency<?>> dependants = new HashSet<>();
+
+       Component(Consumer<Component<?>> register, I instance, Runnable init, Runnable close,
+                       Set<Dependency<?>> dependencies, Set<Class<? super I>> classes) {
+               assert instance != null;
+               assert init != null;
+               assert close != null;
+               assert dependencies != null;
+               assert classes != null;
+
+               this.instance = instance;
+               this.init = init;
+               this.close = close;
+
+               // types
+               Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
+               for (Class<? super I> clss : classes) {
+//                     if (!clss.isAssignableFrom(instance.getClass()))
+//                             throw new IllegalArgumentException(
+//                                             "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
+                       types.put(clss, new PublishedType<>(this, clss));
+               }
+               this.types = Collections.unmodifiableMap(types);
+
+               // dependencies
+               this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+               for (Dependency<?> dependency : this.dependencies) {
+                       dependency.setDependantComponent(this);
+               }
+
+               // deactivated by default
+               deactivated = CompletableFuture.completedFuture(null);
+               deactivationStarted = CompletableFuture.completedFuture(null);
+
+               // TODO check whether context is active, so that we start right away
+               prepareNextActivation();
+
+               register.accept(this);
+       }
+
+       private void prepareNextActivation() {
+               activationStarted = new CompletableFuture<Void>();
+               activated = activationStarted //
+                               .thenComposeAsync(this::dependenciesActivated) //
+                               .thenRun(this.init) //
+                               .thenRun(() -> prepareNextDeactivation());
+       }
+
+       private void prepareNextDeactivation() {
+               deactivationStarted = new CompletableFuture<Void>();
+               deactivated = deactivationStarted //
+                               .thenComposeAsync(this::dependantsDeactivated) //
+                               .thenRun(this.close) //
+                               .thenRun(() -> prepareNextActivation());
+       }
+
+       public CompletableFuture<Void> getActivated() {
+               return activated;
+       }
+
+       public CompletableFuture<Void> getDeactivated() {
+               return deactivated;
+       }
+
+       void startActivating() {
+               if (activated.isDone() || activationStarted.isDone())
+                       return;
+               activationStarted.complete(null);
+       }
+
+       void startDeactivating() {
+               if (deactivated.isDone() || deactivationStarted.isDone())
+                       return;
+               deactivationStarted.complete(null);
+       }
+
+       CompletableFuture<Void> dependenciesActivated(Void v) {
+               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
+               for (Dependency<?> dependency : this.dependencies) {
+                       CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
+                                       .thenCompose(dependency::set);
+                       constraints.add(dependencyActivated);
+               }
+               return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+       }
+
+       CompletableFuture<Void> dependantsDeactivated(Void v) {
+               Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
+               for (Dependency<?> dependant : this.dependants) {
+                       CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
+                                       .thenCompose(dependant::unset);
+                       constraints.add(dependantDeactivated);
+               }
+               CompletableFuture<Void> dependantsDeactivated = CompletableFuture
+                               .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+               return dependantsDeactivated;
+
+       }
+
+       void addDependant(Dependency<?> dependant) {
+               dependants.add(dependant);
+       }
+
+       I getInstance() {
+               return instance;
+       }
+
+       @SuppressWarnings("unchecked")
+       <T> PublishedType<T> getType(Class<T> clss) {
+               if (!types.containsKey(clss))
+                       throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
+               return (PublishedType<T>) types.get(clss);
+       }
+
+       <T> boolean isPublishedType(Class<T> clss) {
+               return types.containsKey(clss);
+       }
+
+       public static class PublishedType<T> {
+               private Component<? extends T> component;
+               private Class<T> clss;
+
+//             private CompletableFuture<Component<? extends T>> publisherAvailable;
+               private CompletableFuture<T> value;
+
+               public PublishedType(Component<? extends T> component, Class<T> clss) {
+                       this.clss = clss;
+                       this.component = component;
+                       value = CompletableFuture.completedFuture((T) component.instance);
+//                     value = publisherAvailable.thenApply((c) -> c.getInstance());
+               }
+
+               Component<?> getPublisher() {
+                       return component;
+               }
+
+//             CompletableFuture<Component<? extends T>> publisherAvailable() {
+//                     return publisherAvailable;
+//             }
+
+               Class<T> getType() {
+                       return clss;
+               }
+       }
+
+       public static class Builder<I> {
+               private final I instance;
+
+               private Runnable init;
+               private Runnable close;
+
+               private Set<Dependency<?>> dependencies = new HashSet<>();
+               private Set<Class<? super I>> types = new HashSet<>();
+
+               public Builder(I instance) {
+                       this.instance = instance;
+               }
+
+               public Component<I> build(Consumer<Component<?>> register) {
+                       // default values
+                       if (types.isEmpty()) {
+                               types.add(getInstanceClass());
+                       }
+
+                       if (init == null)
+                               init = () -> {
+                               };
+                       if (close == null)
+                               close = () -> {
+                               };
+
+                       // instantiation
+                       Component<I> component = new Component<I>(register, instance, init, close, dependencies, types);
+                       for (Dependency<?> dependency : dependencies) {
+                               dependency.type.getPublisher().addDependant(dependency);
+                       }
+                       return component;
+               }
+
+               public Builder<I> addType(Class<? super I> clss) {
+                       types.add(clss);
+                       return this;
+               }
+
+               public Builder<I> addInit(Runnable init) {
+                       if (this.init != null)
+                               throw new IllegalArgumentException("init method is already set");
+                       this.init = init;
+                       return this;
+               }
+
+               public Builder<I> addClose(Runnable close) {
+                       if (this.close != null)
+                               throw new IllegalArgumentException("close method is already set");
+                       this.close = close;
+                       return this;
+               }
+
+               public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
+                       dependencies.add(new Dependency<D>(type, set, unset));
+                       return this;
+               }
+
+               public I get() {
+                       return instance;
+               }
+
+               @SuppressWarnings("unchecked")
+               private Class<I> getInstanceClass() {
+                       return (Class<I>) instance.getClass();
+               }
+
+       }
+
+       static class Dependency<D> {
+               private PublishedType<D> type;
+               private Consumer<D> set;
+               private Consumer<D> unset;
+
+               // live
+               Component<?> dependantComponent;
+               CompletableFuture<Void> setStage;
+               CompletableFuture<Void> unsetStage;
+
+               public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
+                       super();
+                       this.type = types;
+                       this.set = set;
+                       this.unset = unset != null ? unset : (v) -> set.accept(null);
+               }
+
+               // live
+               void setDependantComponent(Component<?> component) {
+                       this.dependantComponent = component;
+               }
+
+               CompletableFuture<Void> publisherActivated() {
+                       return type.getPublisher().activated.copy();
+               }
+
+               CompletableFuture<Void> dependantDeactivated() {
+                       return dependantComponent.deactivated.copy();
+               }
+
+               CompletableFuture<Void> set(Void v) {
+                       return type.value.thenAccept(set);
+               }
+
+               CompletableFuture<Void> unset(Void v) {
+                       return type.value.thenAccept(unset);
+               }
+
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/register/Register.java b/org.argeo.util/src/org/argeo/util/register/Register.java
new file mode 100644 (file)
index 0000000..1706280
--- /dev/null
@@ -0,0 +1,10 @@
+package org.argeo.util.register;
+
+import java.util.Map;
+
+/** A dynamic register of objects. */
+public interface Register {
+       <T> Singleton<T> set(T obj, Class<T> clss, Map<String, Object> attributes, Class<?>... classes);
+
+       void shutdown();
+}
diff --git a/org.argeo.util/src/org/argeo/util/register/Singleton.java b/org.argeo.util/src/org/argeo/util/register/Singleton.java
new file mode 100644 (file)
index 0000000..5d70e9a
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.util.register;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+public class Singleton<T> {
+       private final Class<T> clss;
+       private final CompletableFuture<T> registrationStage;
+       private final List<Consumer<T>> unregistrationHooks = new ArrayList<>();
+
+       public Singleton(Class<T> clss, CompletableFuture<T> registrationStage) {
+               this.clss = clss;
+               this.registrationStage = registrationStage;
+       }
+
+       CompletionStage<T> getRegistrationStage() {
+               return registrationStage.minimalCompletionStage();
+       }
+
+       public void addUnregistrationHook(Consumer<T> todo) {
+               unregistrationHooks.add(todo);
+       }
+
+       public Future<T> getValue() {
+               return registrationStage.copy();
+       }
+
+       public CompletableFuture<Void> prepareUnregistration(Void v) {
+               List<CompletableFuture<Void>> lst = new ArrayList<>();
+               for (Consumer<T> hook : unregistrationHooks) {
+                       lst.add(registrationStage.thenAcceptAsync(hook));
+               }
+               CompletableFuture<Void> prepareUnregistrationStage = CompletableFuture
+                               .allOf(lst.toArray(new CompletableFuture<?>[lst.size()]));
+               return prepareUnregistrationStage;
+       }
+}
diff --git a/org.argeo.util/src/org/argeo/util/register/StaticRegister.java b/org.argeo.util/src/org/argeo/util/register/StaticRegister.java
new file mode 100644 (file)
index 0000000..c186aff
--- /dev/null
@@ -0,0 +1,97 @@
+package org.argeo.util.register;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/** A minimal component register. */
+public class StaticRegister {
+       private final static AtomicBoolean started = new AtomicBoolean(false);
+       private final static IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
+
+       public static Consumer<Component<?>> asConsumer() {
+               return (c) -> registerComponent(c);
+       }
+
+//     public static BiFunction<Class<?>, Predicate<Map<String, Object>>, Component<?>> asProvider() {
+//
+//     }
+
+       static synchronized <T> Component<? extends T> find(Class<T> clss, Predicate<Map<String, Object>> filter) {
+               Set<Component<? extends T>> result = new HashSet<>();
+               instances: for (Object instance : components.keySet()) {
+                       if (!clss.isAssignableFrom(instance.getClass()))
+                               continue instances;
+                       Component<? extends T> component = (Component<? extends T>) components.get(instance);
+
+                       // TODO filter
+                       if (component.isPublishedType(clss))
+                               result.add(component);
+               }
+               if (result.isEmpty())
+                       return null;
+               return result.iterator().next();
+
+       }
+
+       static synchronized void registerComponent(Component<?> component) {
+               if (started.get()) // TODO make it really dynamic
+                       throw new IllegalStateException("Already activated");
+               if (components.containsKey(component.getInstance()))
+                       throw new IllegalArgumentException("Already registered as component");
+               components.put(component.getInstance(), component);
+       }
+
+       static synchronized Component<?> get(Object instance) {
+               if (!components.containsKey(instance))
+                       throw new IllegalArgumentException("Not registered as component");
+               return components.get(instance);
+       }
+
+       synchronized static void activate() {
+               if (started.get())
+                       throw new IllegalStateException("Already activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startActivating();
+                       constraints.add(component.getActivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
+                                       .get();
+               } catch (ExecutionException | InterruptedException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       synchronized static void deactivate() {
+               if (!started.get())
+                       throw new IllegalStateException("Not activated");
+               Set<CompletableFuture<?>> constraints = new HashSet<>();
+               for (Component<?> component : components.values()) {
+                       component.startDeactivating();
+                       constraints.add(component.getDeactivated());
+               }
+
+               // wait
+               try {
+                       CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
+                                       .get();
+               } catch (ExecutionException | InterruptedException e) {
+                       throw new RuntimeException(e);
+               }
+       }
+
+       synchronized static void clear() {
+               components.clear();
+       }
+
+}
diff --git a/pom.xml b/pom.xml
deleted file mode 100644 (file)
index 08dec90..0000000
--- a/pom.xml
+++ /dev/null
@@ -1,222 +0,0 @@
-<?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.maven</groupId>
-               <artifactId>argeo-osgi-parent</artifactId>
-               <version>2.1.7</version>
-       </parent>
-       <groupId>org.argeo.commons</groupId>
-       <artifactId>argeo-commons</artifactId>
-       <version>2.1-SNAPSHOT</version>
-       <name>Argeo Commons</name>
-       <packaging>pom</packaging>
-       <url>http://www.argeo.org/</url>
-       <properties>
-               <version.context>testing</version.context>
-               <version.argeo-tp>2.1.27</version.argeo-tp>
-       </properties>
-       <modules>
-               <!-- Base -->
-               <module>org.argeo.enterprise</module>
-               <module>org.argeo.jcr</module>
-               <module>org.argeo.osgi.boot</module>
-               <module>org.argeo.core</module>
-               <!-- Eclipse -->
-               <module>org.argeo.eclipse.ui</module>
-               <module>org.argeo.eclipse.ui.rap</module>
-               <!-- CMS -->
-               <module>org.argeo.api</module>
-               <module>org.argeo.maintenance</module>
-               <module>org.argeo.cms</module>
-               <module>org.argeo.cms.ui.theme</module>
-               <module>org.argeo.cms.ui</module>
-               <module>org.argeo.cms.ui.rap</module>
-               <!-- CMS E4 -->
-               <module>org.argeo.cms.e4</module>
-               <module>org.argeo.cms.e4.rap</module>
-               <!-- Distribution -->
-               <module>dep</module>
-               <module>demo</module>
-               <module>dist</module>
-               <module>sdk</module>
-       </modules>
-       <scm>
-               <connection>scm:git:http://git.argeo.org/lgpl/argeo-commons.git</connection>
-               <url>http://git.argeo.org/?p=lgpl/argeo-commons.git;a=summary</url>
-               <developerConnection>scm:git:https://code.argeo.org/git/lgpl/argeo-commons.git</developerConnection>
-               <tag>HEAD</tag>
-       </scm>
-       <organization>
-               <name>Argeo GmbH</name>
-       </organization>
-       <inceptionYear>2009</inceptionYear>
-       <licenses>
-               <license>
-                       <name>LGPL-3.0-or-later</name>
-                       <url>https://www.gnu.org/licenses/lgpl-3.0.txt</url>
-                       <distribution>repo</distribution>
-               </license>
-       </licenses>
-       <build>
-               <plugins>
-                       <plugin>
-                               <groupId>org.codehaus.mojo</groupId>
-                               <artifactId>license-maven-plugin</artifactId>
-                               <version>2.0.0</version>
-                               <configuration>
-                                       <projectName>Argeo Commons</projectName>
-                                       <licenseName>lgpl_v3</licenseName>
-                                       <excludes>
-                                               <exclude>**/springutil/*</exclude>
-                                               <exclude>**/org/argeo/jcr/fs/Text.java</exclude>
-                                       </excludes>
-                               </configuration>
-                       </plugin>
-               </plugins>
-       </build>
-       <dependencies>
-               <dependency>
-                       <groupId>org.argeo.tp</groupId>
-                       <artifactId>argeo-tp</artifactId>
-                       <version>${version.argeo-tp}</version>
-                       <scope>provided</scope>
-                       <exclusions>
-                               <exclusion>
-                                       <groupId>org.argeo.tp.apache</groupId>
-                                       <artifactId>org.apache.xerces</artifactId>
-                               </exclusion>
-                               <!-- Avoid slf4j implementations lurking in the classpath. -->
-                               <exclusion>
-                                       <groupId>org.argeo.tp.sdk</groupId>
-                                       <artifactId>biz.aQute.bndlib</artifactId>
-                               </exclusion>
-                               <exclusion>
-                                       <groupId>org.argeo.tp.misc</groupId>
-                                       <artifactId>slf4j.osgi</artifactId>
-                               </exclusion>
-                       </exclusions>
-               </dependency>
-       </dependencies>
-       <dependencyManagement>
-               <dependencies>
-                       <dependency>
-                               <groupId>org.argeo.tp</groupId>
-                               <artifactId>argeo-tp</artifactId>
-                               <version>${version.argeo-tp}</version>
-                               <type>pom</type>
-                               <scope>import</scope>
-                       </dependency>
-                       <dependency>
-                               <groupId>org.argeo.tp</groupId>
-                               <artifactId>argeo-tp-rap-e4</artifactId>
-                               <version>${version.argeo-tp}</version>
-                               <type>pom</type>
-                               <scope>import</scope>
-                       </dependency>
-               </dependencies>
-       </dependencyManagement>
-       <distributionManagement>
-               <repository>
-                       <id>staging</id>
-                       <url>dav:https://forge.argeo.org/data/java/argeo-2.1</url>
-                       <uniqueVersion>false</uniqueVersion>
-               </repository>
-               <site>
-                       <id>staging</id>
-                       <url>file:///srv/docfactory/argeo-2.1/site/argeo-commons/</url>
-               </site>
-       </distributionManagement>
-       <repositories>
-               <repository>
-                       <id>argeo</id>
-                       <url>http://forge.argeo.org/data/java/argeo-2.1/</url>
-                       <releases>
-                               <enabled>true</enabled>
-                               <updatePolicy>never</updatePolicy>
-                               <checksumPolicy>warn</checksumPolicy>
-                       </releases>
-                       <snapshots>
-                               <enabled>false</enabled>
-                       </snapshots>
-               </repository>
-               <!-- Disable Maven default repository -->
-               <repository>
-                       <id>central</id>
-                       <url>http://repo1.maven.org/maven2</url>
-                       <releases>
-                               <enabled>false</enabled>
-                       </releases>
-                       <snapshots>
-                               <enabled>false</enabled>
-                       </snapshots>
-               </repository>
-       </repositories>
-       <reporting>
-               <plugins>
-                       <plugin>
-                               <artifactId>maven-project-info-reports-plugin</artifactId>
-                               <version>2.9</version>
-                               <reportSets>
-                                       <reportSet>
-                                               <reports>
-                                                       <report>index</report>
-                                                       <report>summary</report>
-                                                       <report>license</report>
-                                                       <report>scm</report>
-                                               </reports>
-                                       </reportSet>
-                               </reportSets>
-                       </plugin>
-                       <plugin>
-                               <artifactId>maven-javadoc-plugin</artifactId>
-                               <version>3.0.0</version>
-                               <configuration>
-                                       <failOnError>false</failOnError>
-                                       <additionalJOption>-Xdoclint:none</additionalJOption>
-                                       <excludePackageNames>*.internal.*,org.eclipse.*,org.argeo.cms.ui.eclipse.*</excludePackageNames>
-                                       <encoding>UTF-8</encoding>
-                                       <detectLinks>true</detectLinks>
-                                       <links>
-                                               <link>http://docs.oracle.com/javase/8/docs/api</link>
-                                               <link>https://osgi.org/javadoc/r5/core</link>
-                                               <link>https://osgi.org/javadoc/r5/enterprise</link>
-                                               <link>https://docs.adobe.com/docs/en/spec/javax.jcr/javadocs/jcr-2.0</link>
-                                               <link>http://help.eclipse.org/oxygen/topic/org.eclipse.platform.doc.isv/reference/api</link>
-                                               <link>http://docs.spring.io/spring/docs/3.2.x/javadoc-api</link>
-                                       </links>
-                               </configuration>
-                               <reportSets>
-                                       <reportSet>
-                                               <id>aggregate-javadoc</id>
-                                               <inherited>false</inherited>
-                                               <reports>
-                                                       <report>aggregate</report>
-                                               </reports>
-                                       </reportSet>
-                                       <reportSet>
-                                               <id>javadoc</id>
-                                               <reports />
-                                       </reportSet>
-                               </reportSets>
-                       </plugin>
-                       <plugin>
-                               <artifactId>maven-jxr-plugin</artifactId>
-                               <version>2.5</version>
-                               <reportSets>
-                                       <reportSet>
-                                               <id>aggregate-jxr</id>
-                                               <inherited>false</inherited>
-                                               <reports>
-                                                       <report>aggregate</report>
-                                               </reports>
-                                       </reportSet>
-                                       <reportSet>
-                                               <id>jxr</id>
-                                               <reports />
-                                       </reportSet>
-                               </reportSets>
-                       </plugin>
-               </plugins>
-       </reporting>
-</project>
diff --git a/rap/org.argeo.cms.e4.rap/.classpath b/rap/org.argeo.cms.e4.rap/.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/rap/org.argeo.cms.e4.rap/.project b/rap/org.argeo.cms.e4.rap/.project
new file mode 100644 (file)
index 0000000..40c9e01
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.e4.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/rap/org.argeo.cms.e4.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml b/rap/org.argeo.cms.e4.rap/OSGI-INF/cms-admin-rap.xml
new file mode 100644 (file)
index 0000000..1f688ba
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="optional" name="CMS Admin RAP">
+   <implementation class="org.argeo.cms.e4.rap.CmsE4AdminApp"/>
+   <service>
+      <provide interface="org.eclipse.rap.rwt.application.ApplicationConfiguration"/>
+      <property name="contextName" type="String" value="cms"/>
+   </service>
+</scr:component>
diff --git a/rap/org.argeo.cms.e4.rap/bnd.bnd b/rap/org.argeo.cms.e4.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..5bbe4bc
--- /dev/null
@@ -0,0 +1,9 @@
+Bundle-ActivationPolicy: lazy
+Service-Component: OSGI-INF/cms-admin-rap.xml
+
+Import-Package: org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.eclipse.e4.ui.workbench,\
+org.eclipse.rap.rwt.client,\
+org.eclipse.nebula.widgets.richtext;resolution:=optional,\
+*
diff --git a/rap/org.argeo.cms.e4.rap/build.properties b/rap/org.argeo.cms.e4.rap/build.properties
new file mode 100644 (file)
index 0000000..c58ea21
--- /dev/null
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java
new file mode 100644 (file)
index 0000000..5fe22ae
--- /dev/null
@@ -0,0 +1,139 @@
+package org.argeo.cms.e4.rap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Base class for CMS RAP applications. */
+public abstract class AbstractRapE4App implements ApplicationConfiguration {
+       private String e4Xmi;
+       private String path;
+       private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+       private Map<String, String> baseProperties = new HashMap<String, String>();
+
+       private BundleContext bundleContext;
+       public final static String CONTEXT_NAME_PROPERTY = "contextName";
+       private String contextName;
+
+       /**
+        * To be overridden in order to add multiple entry points, directly or using
+        * {@link #addE4EntryPoint(Application, String, String, Map)}.
+        */
+       protected void addEntryPoints(Application application) {
+       }
+
+       public void configure(Application application) {
+               application.setExceptionHandler(new ExceptionHandler() {
+
+                       @Override
+                       public void handleException(Throwable throwable) {
+                               CmsFeedback.show("Unexpected RWT exception", throwable);
+                       }
+               });
+
+               if (e4Xmi != null) {// backward compatibility
+                       addE4EntryPoint(application, path, e4Xmi, getBaseProperties());
+               } else {
+                       addEntryPoints(application);
+               }
+       }
+
+       protected Map<String, String> getBaseProperties() {
+               return baseProperties;
+       }
+
+//     protected void addEntryPoint(Application application, E4ApplicationConfig config, Map<String, String> properties) {
+//             CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+//             application.addEntryPoint(path, entryPointFactory, properties);
+//             application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+//     }
+
+       protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map<String, String> properties) {
+               E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi);
+               CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config);
+               application.addEntryPoint(path, entryPointFactory, properties);
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+       }
+
+       /**
+        * To be overridden for further configuration.
+        * 
+        * @see E4ApplicationConfig
+        */
+       protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) {
+               return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+       }
+
+       @Deprecated
+       public void setPageTitle(String pageTitle) {
+               if (pageTitle != null)
+                       baseProperties.put(WebClient.PAGE_TITLE, pageTitle);
+       }
+
+       /** Returns a new map used to customise and entry point. */
+       public Map<String, String> customise(String pageTitle) {
+               Map<String, String> custom = new HashMap<>(getBaseProperties());
+               if (pageTitle != null)
+                       custom.put(WebClient.PAGE_TITLE, pageTitle);
+               return custom;
+       }
+
+       @Deprecated
+       public void setE4Xmi(String e4Xmi) {
+               this.e4Xmi = e4Xmi;
+       }
+
+       @Deprecated
+       public void setPath(String path) {
+               this.path = path;
+       }
+
+       public void setLifeCycleUri(String lifeCycleUri) {
+               this.lifeCycleUri = lifeCycleUri;
+       }
+
+       protected BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       protected void setBundleContext(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+       }
+
+       public String getContextName() {
+               return contextName;
+       }
+
+       public void setContextName(String contextName) {
+               this.contextName = contextName;
+       }
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               this.bundleContext = bundleContext;
+               for (String key : properties.keySet()) {
+                       Object value = properties.get(key);
+                       if (value != null)
+                               baseProperties.put(key, value.toString());
+               }
+
+               if (properties.containsKey(CONTEXT_NAME_PROPERTY)) {
+                       assert properties.get(CONTEXT_NAME_PROPERTY) != null;
+                       contextName = properties.get(CONTEXT_NAME_PROPERTY).toString();
+               } else {
+                       contextName = "<unknown context>";
+               }
+       }
+
+       public void destroy(Map<String, Object> properties) {
+
+       }
+}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4AdminApp.java
new file mode 100644 (file)
index 0000000..66be1e8
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.cms.e4.rap;
+
+import org.eclipse.rap.rwt.application.Application;
+
+/**
+ * Access to canonical views of the core CMS concepts, useful for devleopers and
+ * operators.
+ */
+public class CmsE4AdminApp extends AbstractRapE4App {
+       @Override
+       protected void addEntryPoints(Application application) {
+               addE4EntryPoint(application, "/devops", "org.argeo.cms.e4/e4xmi/cms-devops.e4xmi",
+                               customise("Argeo CMS DevOps"));
+               addE4EntryPoint(application, "/ego", "org.argeo.cms.e4/e4xmi/cms-ego.e4xmi", customise("Argeo CMS Ego"));
+       }
+
+}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java
new file mode 100644 (file)
index 0000000..a5a3234
--- /dev/null
@@ -0,0 +1,76 @@
+package org.argeo.cms.e4.rap;
+
+import java.security.PrivilegedAction;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.rap.e4.E4ApplicationConfig;
+import org.eclipse.rap.e4.E4EntryPointFactory;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class CmsE4EntryPointFactory extends E4EntryPointFactory {
+       public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle";
+
+       public CmsE4EntryPointFactory(E4ApplicationConfig config) {
+               super(config);
+       }
+
+       public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) {
+               super(defaultConfig(e4Xmi, lifeCycleUri));
+       }
+
+       public CmsE4EntryPointFactory(String e4Xmi) {
+               this(e4Xmi, DEFAULT_LIFECYCLE_URI);
+       }
+
+       public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) {
+               E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true);
+               return config;
+       }
+
+       @Override
+       public EntryPoint create() {
+               EntryPoint ep = createEntryPoint();
+               EntryPoint authEp = new EntryPoint() {
+
+                       @Override
+                       public int createUI() {
+                               Subject subject = new Subject();
+                               return Subject.doAs(subject, new PrivilegedAction<Integer>() {
+
+                                       @Override
+                                       public Integer run() {
+                                               // SPNEGO
+                                               // HttpServletRequest request = RWT.getRequest();
+                                               // String authorization = request.getHeader(HEADER_AUTHORIZATION);
+                                               // if (authorization == null || !authorization.startsWith("Negotiate")) {
+                                               // HttpServletResponse response = RWT.getResponse();
+                                               // response.setStatus(401);
+                                               // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+                                               // response.setDateHeader("Date", System.currentTimeMillis());
+                                               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60
+                                               // * 1000));
+                                               // response.setHeader("Accept-Ranges", "bytes");
+                                               // response.setHeader("Connection", "Keep-Alive");
+                                               // response.setHeader("Keep-Alive", "timeout=5, max=97");
+                                               // // response.setContentType("text/html; charset=UTF-8");
+                                               // }
+
+                                               JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+                                               Integer exitCode = ep.createUI();
+                                               jsExecutor.execute("location.reload()");
+                                               return exitCode;
+                                       }
+
+                               });
+                       }
+               };
+               return authEp;
+       }
+
+       protected EntryPoint createEntryPoint() {
+               return super.create();
+       }
+}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java
new file mode 100644 (file)
index 0000000..95be53d
--- /dev/null
@@ -0,0 +1,183 @@
+package org.argeo.cms.e4.rap;
+
+import java.security.AccessController;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.UxContext;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.ui.util.SimpleImageManager;
+import org.eclipse.e4.core.services.events.IEventBroker;
+import org.eclipse.e4.ui.workbench.UIEvents;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+@SuppressWarnings("restriction")
+public class CmsLoginLifecycle implements CmsView {
+       private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class);
+
+       private UxContext uxContext;
+       private CmsImageManager imageManager;
+
+       private LoginContext loginContext;
+       private BrowserNavigation browserNavigation;
+
+       private String state = null;
+       private String uid;
+
+       @PostContextCreate
+       boolean login(final IEventBroker eventBroker) {
+               uid = UUID.randomUUID().toString();
+               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+               if (browserNavigation != null)
+                       browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() {
+                               private static final long serialVersionUID = -3668136623771902865L;
+
+                               @Override
+                               public void navigated(BrowserNavigationEvent event) {
+                                       state = event.getState();
+                                       if (uxContext != null)// is logged in
+                                               stateChanged();
+                               }
+                       });
+
+               Subject subject = Subject.getSubject(AccessController.getContext());
+               Display display = Display.getCurrent();
+//             UiContext.setData(CmsView.KEY, this);
+               // FIXME get CMS context
+               CmsLoginShell loginShell = new CmsLoginShell(this, null);
+               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+               loginShell.setSubject(subject);
+               try {
+                       // try pre-auth
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell);
+                       loginContext.login();
+               } catch (LoginException e) {
+                       loginShell.createUi();
+                       loginShell.open();
+
+                       while (!loginShell.getShell().isDisposed()) {
+                               if (!display.readAndDispatch())
+                                       display.sleep();
+                       }
+               }
+               if (CurrentUser.getUsername(getSubject()) == null)
+                       return false;
+               uxContext = new SimpleSwtUxContext();
+               imageManager = new SimpleImageManager();
+
+               eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() {
+                       @Override
+                       public void handleEvent(Event event) {
+                               startupComplete();
+                               eventBroker.unsubscribe(this);
+                       }
+               });
+
+               // lcs.changeApplicationLocale(Locale.FRENCH);
+               return true;
+       }
+
+       @PreSave
+       void destroy() {
+               // logout();
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               browserNavigation.pushState(state, state);
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+               if (loginContext == null)
+                       throw new IllegalArgumentException("Login context cannot be null");
+               // logout previous login context
+               // if (this.loginContext != null)
+               // try {
+               // this.loginContext.logout();
+               // } catch (LoginException e1) {
+               // System.err.println("Could not log out: " + e1);
+               // }
+               this.loginContext = loginContext;
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext == null)
+                       throw new IllegalStateException("Login context should not be null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       throw new IllegalStateException("Cannot log out", e);
+               }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               String msg = "Unexpected exception in Eclipse 4 RAP";
+               log.error(msg, e);
+               CmsFeedback.show(msg, e);
+       }
+
+       @Override
+       public CmsImageManager getImageManager() {
+               return imageManager;
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       // CALLBACKS
+       protected void startupComplete() {
+       }
+
+       protected void stateChanged() {
+
+       }
+
+       // GETTERS
+       protected BrowserNavigation getBrowserNavigation() {
+               return browserNavigation;
+       }
+
+       protected String getState() {
+               return state;
+       }
+
+}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java
new file mode 100644 (file)
index 0000000..1bca333
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.cms.e4.rap;
+
+import java.util.Enumeration;
+
+import org.apache.commons.io.FilenameUtils;
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.rap.rwt.application.Application;
+import org.osgi.framework.Bundle;
+
+/** Simple RAP app which loads all e4xmi files. */
+public class SimpleRapE4App extends AbstractRapE4App {
+       private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class);
+
+       private String baseE4xmi = "/e4xmi";
+
+       @Override
+       protected void addEntryPoints(Application application) {
+               Bundle bundle = getBundleContext().getBundle();
+               Enumeration<String> paths = bundle.getEntryPaths(baseE4xmi);
+               while (paths.hasMoreElements()) {
+                       String p = paths.nextElement();
+                       if (p.endsWith(".e4xmi")) {
+                               String e4xmiPath = bundle.getSymbolicName() + '/' + p;
+                               String name = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p));
+                               addE4EntryPoint(application, name, e4xmiPath, getBaseProperties());
+                               if (log.isDebugEnabled())
+                                       log.debug("Registered " + e4xmiPath + " as " + getContextName() + name);
+                       }
+               }
+       }
+
+}
diff --git a/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java
new file mode 100644 (file)
index 0000000..1122f19
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse 4 RAP specific extensions. */
+package org.argeo.cms.e4.rap;
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/.classpath b/rap/org.argeo.cms.ui.rap/.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/rap/org.argeo.cms.ui.rap/.project b/rap/org.argeo.cms.ui.rap/.project
new file mode 100644 (file)
index 0000000..1a37a67
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.ui.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rap/org.argeo.cms.ui.rap/META-INF/.gitignore b/rap/org.argeo.cms.ui.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rap/org.argeo.cms.ui.rap/bnd.bnd b/rap/org.argeo.cms.ui.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..e3e71b3
--- /dev/null
@@ -0,0 +1,15 @@
+Import-Package:\
+org.eclipse.swt,\
+org.argeo.eclipse.ui,\
+javax.jcr.nodetype,\
+javax.jcr.security,\
+org.eclipse.swt.graphics,\
+org.eclipse.jetty.util.component;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.http;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.io;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.security;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.server.handler;version="[9.4,12)";resolution:=optional,\
+org.eclipse.jetty.*;version="[9.4,12)";resolution:=optional,\
+javax.servlet.*;version="[3,5)",\
+*
+
diff --git a/rap/org.argeo.cms.ui.rap/build.properties b/rap/org.argeo.cms.ui.rap/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/AppUi.java
new file mode 100644 (file)
index 0000000..01ebb23
--- /dev/null
@@ -0,0 +1,268 @@
+package org.argeo.cms.ui.script;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.script.Invocable;
+import javax.script.ScriptException;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.Selected;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsPane;
+import org.argeo.cms.web.SimpleErgonomics;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.application.EntryPointFactory;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+
+public class AppUi implements CmsUiProvider, Branding {
+       private final CmsScriptApp app;
+
+       private CmsUiProvider ui;
+       private String createUi;
+       private Object impl;
+       private String script;
+       // private Branding branding = new Branding();
+
+       private EntryPointFactory factory;
+
+       // Branding
+       private String themeId;
+       private String additionalHeaders;
+       private String bodyHtml;
+       private String pageTitle;
+       private String pageOverflow;
+       private String favicon;
+
+       public AppUi(CmsScriptApp app) {
+               this.app = app;
+       }
+
+       public AppUi(CmsScriptApp app, String scriptPath) {
+               this.app = app;
+               this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC),
+                               app.getScriptEngine(), scriptPath);
+       }
+
+       public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) {
+               this.app = app;
+               this.ui = uiProvider;
+       }
+
+       public AppUi(CmsScriptApp app, EntryPointFactory factory) {
+               this.app = app;
+               this.factory = factory;
+       }
+
+       public void apply(Repository repository, Application application, Branding appBranding, String path) {
+               Map<String, String> factoryProperties = new HashMap<>();
+               if (appBranding != null)
+                       appBranding.applyBranding(factoryProperties);
+               applyBranding(factoryProperties);
+               if (factory != null) {
+                       application.addEntryPoint("/" + path, factory, factoryProperties);
+               } else {
+                       EntryPointFactory entryPointFactory = new EntryPointFactory() {
+                               @Override
+                               public EntryPoint create() {
+                                       SimpleErgonomics ergonomics = new SimpleErgonomics(repository, CmsConstants.SYS_WORKSPACE,
+                                                       "/home/root/argeo:keyring", AppUi.this, factoryProperties);
+//                                     CmsUiProvider header = app.getHeader();
+//                                     if (header != null)
+//                                             ergonomics.setHeader(header);
+                                       app.applySides(ergonomics);
+                                       Integer headerHeight = app.getHeaderHeight();
+                                       if (headerHeight != null)
+                                               ergonomics.setHeaderHeight(headerHeight);
+                                       return ergonomics;
+                               }
+                       };
+                       application.addEntryPoint("/" + path, entryPointFactory, factoryProperties);
+               }
+       }
+
+       public void setUi(CmsUiProvider uiProvider) {
+               this.ui = uiProvider;
+       }
+
+       public void applyBranding(Map<String, String> properties) {
+               if (themeId != null)
+                       properties.put(WebClient.THEME_ID, themeId);
+               if (additionalHeaders != null)
+                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
+               if (bodyHtml != null)
+                       properties.put(WebClient.BODY_HTML, bodyHtml);
+               if (pageTitle != null)
+                       properties.put(WebClient.PAGE_TITLE, pageTitle);
+               if (pageOverflow != null)
+                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
+               if (favicon != null)
+                       properties.put(WebClient.FAVICON, favicon);
+       }
+
+       // public Branding getBranding() {
+       // return branding;
+       // }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsPane cmsPane = new CmsPane(parent, SWT.NONE);
+
+               if (false) {
+                       // QA
+                       CmsSwtUtils.style(cmsPane.getQaArea(), "qa");
+                       Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT);
+                       CmsSwtUtils.style(reload, "qa");
+                       reload.setText("Reload");
+                       reload.addSelectionListener(new Selected() {
+                               private static final long serialVersionUID = 1L;
+
+                               @Override
+                               public void widgetSelected(SelectionEvent e) {
+                                       new Thread() {
+                                               @Override
+                                               public void run() {
+                                                       app.reload();
+                                               }
+                                       }.start();
+                                       RWT.getClient().getService(JavaScriptExecutor.class)
+                                                       .execute("setTimeout('location.reload()',1000)");
+                               }
+                       });
+
+                       // Support
+                       CmsSwtUtils.style(cmsPane.getSupportArea(), "support");
+                       Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE);
+                       CmsSwtUtils.style(msg, "support");
+                       msg.setText("UNSUPPORTED DEVELOPMENT VERSION");
+               }
+
+               if (ui != null) {
+                       ui.createUi(cmsPane.getMainArea(), context);
+               }
+               if (createUi != null) {
+                       Invocable invocable = (Invocable) app.getScriptEngine();
+                       try {
+                               invocable.invokeFunction(createUi, cmsPane.getMainArea(), context);
+
+                       } catch (NoSuchMethodException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ScriptException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+               if (impl != null) {
+                       Invocable invocable = (Invocable) app.getScriptEngine();
+                       try {
+                               invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context);
+
+                       } catch (NoSuchMethodException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ScriptException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+
+               // Invocable invocable = (Invocable) app.getScriptEngine();
+               // try {
+               // invocable.invokeMethod(AppUi.this, "initUi", parent, context);
+               //
+               // } catch (NoSuchMethodException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // } catch (ScriptException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // }
+
+               return null;
+       }
+
+       public void setCreateUi(String createUi) {
+               this.createUi = createUi;
+       }
+
+       public void setImpl(Object impl) {
+               this.impl = impl;
+       }
+
+       public Object getImpl() {
+               return impl;
+       }
+
+       public String getScript() {
+               return script;
+       }
+
+       public void setScript(String script) {
+               this.script = script;
+       }
+
+       // Branding
+       public String getThemeId() {
+               return themeId;
+       }
+
+       public void setThemeId(String themeId) {
+               this.themeId = themeId;
+       }
+
+       public String getAdditionalHeaders() {
+               return additionalHeaders;
+       }
+
+       public void setAdditionalHeaders(String additionalHeaders) {
+               this.additionalHeaders = additionalHeaders;
+       }
+
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
+
+       public void setBodyHtml(String bodyHtml) {
+               this.bodyHtml = bodyHtml;
+       }
+
+       public String getPageTitle() {
+               return pageTitle;
+       }
+
+       public void setPageTitle(String pageTitle) {
+               this.pageTitle = pageTitle;
+       }
+
+       public String getPageOverflow() {
+               return pageOverflow;
+       }
+
+       public void setPageOverflow(String pageOverflow) {
+               this.pageOverflow = pageOverflow;
+       }
+
+       public String getFavicon() {
+               return favicon;
+       }
+
+       public void setFavicon(String favicon) {
+               this.favicon = favicon;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/Branding.java
new file mode 100644 (file)
index 0000000..f72338e
--- /dev/null
@@ -0,0 +1,20 @@
+package org.argeo.cms.ui.script;
+
+import java.util.Map;
+
+public interface Branding {
+       public void applyBranding(Map<String, String> properties);
+
+       public String getThemeId();
+
+       public String getAdditionalHeaders();
+
+       public String getBodyHtml();
+
+       public String getPageTitle();
+
+       public String getPageOverflow();
+
+       public String getFavicon();
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptApp.java
new file mode 100644 (file)
index 0000000..edf558e
--- /dev/null
@@ -0,0 +1,421 @@
+package org.argeo.cms.ui.script;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.script.ScriptEngine;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.ui.CmsUiConstants;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.web.BundleResourceLoader;
+import org.argeo.cms.web.SimpleErgonomics;
+import org.argeo.cms.web.WebThemeUtils;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+
+public class CmsScriptApp implements Branding {
+       public final static String CONTEXT_NAME = "contextName";
+
+       ServiceRegistration<ApplicationConfiguration> appConfigReg;
+
+       private ScriptEngine scriptEngine;
+
+       private final static CmsLog log = CmsLog.getLog(CmsScriptApp.class);
+
+       private String webPath;
+       private String repo = "(cn=node)";
+
+       // private Branding branding = new Branding();
+       private CmsTheme theme;
+
+       private List<String> resources = new ArrayList<>();
+
+       private Map<String, AppUi> ui = new HashMap<>();
+
+       private CmsUiProvider header;
+       private Integer headerHeight = null;
+       private CmsUiProvider lead;
+       private CmsUiProvider end;
+       private CmsUiProvider footer;
+
+       // Branding
+       private String themeId;
+       private String additionalHeaders;
+       private String bodyHtml;
+       private String pageTitle;
+       private String pageOverflow;
+       private String favicon;
+
+       public CmsScriptApp(ScriptEngine scriptEngine) {
+               super();
+               this.scriptEngine = scriptEngine;
+       }
+
+       public void apply(BundleContext bundleContext, Repository repository, Application application) {
+               BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
+
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+               // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
+
+               application.setExceptionHandler(new CmsExceptionHandler());
+
+               // loading animated gif
+               application.addResource(CmsUiConstants.LOADING_IMAGE, createResourceLoader(CmsUiConstants.LOADING_IMAGE));
+               // empty image
+               application.addResource(CmsUiConstants.NO_IMAGE, createResourceLoader(CmsUiConstants.NO_IMAGE));
+
+               for (String resource : resources) {
+                       application.addResource(resource, bundleRL);
+                       if (log.isTraceEnabled())
+                               log.trace("Resource " + resource);
+               }
+
+               if (theme != null) {
+                       WebThemeUtils.apply(application, theme);
+                       String themeHeaders = theme.getHtmlHeaders();
+                       if (themeHeaders != null) {
+                               if (additionalHeaders == null)
+                                       additionalHeaders = themeHeaders;
+                               else
+                                       additionalHeaders = themeHeaders + "\n" + additionalHeaders;
+                       }
+                       themeId = theme.getThemeId();
+               }
+
+               // client JavaScript
+               Bundle appBundle = bundleRL.getBundle();
+               BundleContext bc = appBundle.getBundleContext();
+               HttpService httpService = bc.getService(bc.getServiceReference(HttpService.class));
+               HttpContext httpContext = new BundleHttpContext(bc);
+               Enumeration<URL> themeResources = appBundle.findEntries("/js/", "*", true);
+               if (themeResources != null)
+                       bundleResources: while (themeResources.hasMoreElements()) {
+                               try {
+                                       String name = themeResources.nextElement().getPath();
+                                       if (name.endsWith("/"))
+                                               continue bundleResources;
+                                       String alias = "/" + getWebPath() + name;
+
+                                       httpService.registerResources(alias, name, httpContext);
+                                       if (log.isDebugEnabled())
+                                               log.debug("Mapped " + name + " to alias " + alias);
+
+                               } catch (NamespaceException e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               }
+                       }
+
+               // App UIs
+               for (String appUiName : ui.keySet()) {
+                       AppUi appUi = ui.get(appUiName);
+                       appUi.apply(repository, application, this, appUiName);
+
+               }
+
+       }
+
+       public void applySides(SimpleErgonomics simpleErgonomics) {
+               simpleErgonomics.setHeader(header);
+               simpleErgonomics.setLead(lead);
+               simpleErgonomics.setEnd(end);
+               simpleErgonomics.setFooter(footer);
+       }
+
+       public void register(BundleContext bundleContext, ApplicationConfiguration appConfig) {
+               Hashtable<String, String> props = new Hashtable<>();
+               props.put(CONTEXT_NAME, webPath);
+               appConfigReg = bundleContext.registerService(ApplicationConfiguration.class, appConfig, props);
+       }
+
+       public void reload() {
+               BundleContext bundleContext = appConfigReg.getReference().getBundle().getBundleContext();
+               ApplicationConfiguration appConfig = bundleContext.getService(appConfigReg.getReference());
+               appConfigReg.unregister();
+               register(bundleContext, appConfig);
+
+               // BundleContext bundleContext = (BundleContext)
+               // getScriptEngine().get("bundleContext");
+               // try {
+               // Bundle bundle = bundleContext.getBundle();
+               // bundle.stop();
+               // bundle.start();
+               // } catch (BundleException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // }
+       }
+
+       private static ResourceLoader createResourceLoader(final String resourceName) {
+               return new ResourceLoader() {
+                       public InputStream getResourceAsStream(String resourceName) throws IOException {
+                               return getClass().getClassLoader().getResourceAsStream(resourceName);
+                       }
+               };
+       }
+
+       public List<String> getResources() {
+               return resources;
+       }
+
+       public AppUi newUi(String name) {
+               if (ui.containsKey(name))
+                       throw new IllegalArgumentException("There is already an UI named " + name);
+               AppUi appUi = new AppUi(this);
+               // appUi.setApp(this);
+               ui.put(name, appUi);
+               return appUi;
+       }
+
+       public void addUi(String name, AppUi appUi) {
+               if (ui.containsKey(name))
+                       throw new IllegalArgumentException("There is already an UI named " + name);
+               // appUi.setApp(this);
+               ui.put(name, appUi);
+       }
+
+       public void applyBranding(Map<String, String> properties) {
+               if (themeId != null)
+                       properties.put(WebClient.THEME_ID, themeId);
+               if (additionalHeaders != null)
+                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
+               if (bodyHtml != null)
+                       properties.put(WebClient.BODY_HTML, bodyHtml);
+               if (pageTitle != null)
+                       properties.put(WebClient.PAGE_TITLE, pageTitle);
+               if (pageOverflow != null)
+                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
+               if (favicon != null)
+                       properties.put(WebClient.FAVICON, favicon);
+       }
+
+       class CmsExceptionHandler implements ExceptionHandler {
+
+               @Override
+               public void handleException(Throwable throwable) {
+                       // TODO be smarter
+                       CmsUiUtils.getCmsView().exception(throwable);
+               }
+
+       }
+
+       // public Branding getBranding() {
+       // return branding;
+       // }
+
+       ScriptEngine getScriptEngine() {
+               return scriptEngine;
+       }
+
+       public static String toJson(Node node) {
+               try {
+                       StringBuilder sb = new StringBuilder();
+                       sb.append('{');
+                       PropertyIterator pit = node.getProperties();
+                       int count = 0;
+                       while (pit.hasNext()) {
+                               Property p = pit.nextProperty();
+                               int type = p.getType();
+                               if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.PATH) {
+                                       Node ref = p.getNode();
+                                       if (count != 0)
+                                               sb.append(',');
+                                       // TODO limit depth?
+                                       sb.append(toJson(ref));
+                                       count++;
+                               } else if (!p.isMultiple()) {
+                                       if (count != 0)
+                                               sb.append(',');
+                                       sb.append('\"').append(p.getName()).append("\":\"").append(p.getString()).append('\"');
+                                       count++;
+                               }
+                       }
+                       sb.append('}');
+                       return sb.toString();
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot convert " + node + " to JSON", e);
+               }
+       }
+
+       public void fromJson(Node node, String json) {
+               // TODO
+       }
+
+       public CmsTheme getTheme() {
+               return theme;
+       }
+
+       public void setTheme(CmsTheme theme) {
+               this.theme = theme;
+       }
+
+       public String getWebPath() {
+               return webPath;
+       }
+
+       public void setWebPath(String context) {
+               this.webPath = context;
+       }
+
+       public String getRepo() {
+               return repo;
+       }
+
+       public void setRepo(String repo) {
+               this.repo = repo;
+       }
+
+       public Map<String, AppUi> getUi() {
+               return ui;
+       }
+
+       public void setUi(Map<String, AppUi> ui) {
+               this.ui = ui;
+       }
+
+       // Branding
+       public String getThemeId() {
+               return themeId;
+       }
+
+       public void setThemeId(String themeId) {
+               this.themeId = themeId;
+       }
+
+       public String getAdditionalHeaders() {
+               return additionalHeaders;
+       }
+
+       public void setAdditionalHeaders(String additionalHeaders) {
+               this.additionalHeaders = additionalHeaders;
+       }
+
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
+
+       public void setBodyHtml(String bodyHtml) {
+               this.bodyHtml = bodyHtml;
+       }
+
+       public String getPageTitle() {
+               return pageTitle;
+       }
+
+       public void setPageTitle(String pageTitle) {
+               this.pageTitle = pageTitle;
+       }
+
+       public String getPageOverflow() {
+               return pageOverflow;
+       }
+
+       public void setPageOverflow(String pageOverflow) {
+               this.pageOverflow = pageOverflow;
+       }
+
+       public String getFavicon() {
+               return favicon;
+       }
+
+       public void setFavicon(String favicon) {
+               this.favicon = favicon;
+       }
+
+       public CmsUiProvider getHeader() {
+               return header;
+       }
+
+       public void setHeader(CmsUiProvider header) {
+               this.header = header;
+       }
+
+       public Integer getHeaderHeight() {
+               return headerHeight;
+       }
+
+       public void setHeaderHeight(Integer headerHeight) {
+               this.headerHeight = headerHeight;
+       }
+
+       public CmsUiProvider getLead() {
+               return lead;
+       }
+
+       public void setLead(CmsUiProvider lead) {
+               this.lead = lead;
+       }
+
+       public CmsUiProvider getEnd() {
+               return end;
+       }
+
+       public void setEnd(CmsUiProvider end) {
+               this.end = end;
+       }
+
+       public CmsUiProvider getFooter() {
+               return footer;
+       }
+
+       public void setFooter(CmsUiProvider footer) {
+               this.footer = footer;
+       }
+
+       static class BundleHttpContext implements HttpContext {
+               private BundleContext bundleContext;
+
+               public BundleHttpContext(BundleContext bundleContext) {
+                       super();
+                       this.bundleContext = bundleContext;
+               }
+
+               @Override
+               public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
+                       // TODO Auto-generated method stub
+                       return true;
+               }
+
+               @Override
+               public URL getResource(String name) {
+
+                       return bundleContext.getBundle().getEntry(name);
+               }
+
+               @Override
+               public String getMimeType(String name) {
+                       return null;
+               }
+
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/CmsScriptRwtApplication.java
new file mode 100644 (file)
index 0000000..499840a
--- /dev/null
@@ -0,0 +1,120 @@
+package org.argeo.cms.ui.script;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.jcr.Repository;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class CmsScriptRwtApplication implements ApplicationConfiguration {
+       public final static String APP = "APP";
+       public final static String BC = "BC";
+
+       private final CmsLog log = CmsLog.getLog(CmsScriptRwtApplication.class);
+
+       BundleContext bundleContext;
+       Repository repository;
+
+       ScriptEngine engine;
+
+       public void init(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+               ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+               ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
+               try {
+//                     Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
+//                     ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
+//                     engine = scriptEngineManager.getEngineByName("JavaScript");
+//                     if (engine == null) {// Nashorn
+//                             Thread.currentThread().setContextClassLoader(originalCcl);
+//                             scriptEngineManager = new ScriptEngineManager();
+//                             Thread.currentThread().setContextClassLoader(bundleCl);
+//                             engine = scriptEngineManager.getEngineByName("JavaScript");
+//                     }
+                       engine = loadScriptEngine(originalCcl, bundleCl);
+
+                       // Load script
+                       URL appUrl = bundleContext.getBundle().getEntry("cms/app.js");
+                       // System.out.println("Loading " + appUrl);
+                       // System.out.println("Loading " + appUrl.getHost());
+                       // System.out.println("Loading " + appUrl.getPath());
+
+                       CmsScriptApp app = new CmsScriptApp(engine);
+                       engine.put(APP, app);
+                       engine.put(BC, bundleContext);
+                       try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+                               engine.eval(reader);
+                       } catch (IOException | ScriptException e) {
+                               throw new CmsException("Cannot execute " + appUrl, e);
+                       }
+
+                       if (log.isDebugEnabled())
+                               log.debug("CMS script app initialized from " + appUrl);
+
+               } catch (Exception e) {
+                       e.printStackTrace();
+               } finally {
+                       Thread.currentThread().setContextClassLoader(originalCcl);
+               }
+       }
+
+       public void destroy(BundleContext bundleContext) {
+               engine = null;
+       }
+
+       @Override
+       public void configure(Application application) {
+               load(application);
+       }
+
+       void load(Application application) {
+               CmsScriptApp app = getApp();
+               app.apply(bundleContext, repository, application);
+               if (log.isDebugEnabled())
+                       log.debug("CMS script app loaded to " + app.getWebPath());
+       }
+
+       CmsScriptApp getApp() {
+               if (engine == null)
+                       throw new IllegalStateException("CMS script app is not initialized");
+               return (CmsScriptApp) engine.get(APP);
+       }
+
+       void update() {
+
+               try {
+                       bundleContext.getBundle().update();
+               } catch (BundleException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       private static ScriptEngine loadScriptEngine(ClassLoader originalCcl, ClassLoader bundleCl) {
+               Thread.currentThread().setContextClassLoader(bundleCl);// GraalVM needs it to be before creating manager
+               ScriptEngineManager scriptEngineManager = new ScriptEngineManager(bundleCl);
+               ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
+               if (engine == null) {// Nashorn
+                       Thread.currentThread().setContextClassLoader(originalCcl);
+                       scriptEngineManager = new ScriptEngineManager();
+                       Thread.currentThread().setContextClassLoader(bundleCl);
+                       engine = scriptEngineManager.getEngineByName("JavaScript");
+               }
+               return engine;
+       }
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptAppActivator.java
new file mode 100644 (file)
index 0000000..a550953
--- /dev/null
@@ -0,0 +1,45 @@
+package org.argeo.cms.ui.script;
+
+import javax.jcr.Repository;
+
+import org.argeo.api.cms.CmsLog;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ScriptAppActivator implements BundleActivator {
+       private final static CmsLog log = CmsLog.getLog(ScriptAppActivator.class);
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               try {
+                       CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication();
+                       appConfig.init(context);
+                       CmsScriptApp app = appConfig.getApp();
+                       ServiceTracker<Repository, Repository> repoSt = new ServiceTracker<Repository, Repository>(context,
+                                       FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) {
+
+                               @Override
+                               public Repository addingService(ServiceReference<Repository> reference) {
+                                       Repository repository = super.addingService(reference);
+                                       appConfig.setRepository(repository);
+                                       CmsScriptApp app = appConfig.getApp();
+                                       app.register(context, appConfig);
+                                       return repository;
+                               }
+
+                       };
+                       repoSt.open();
+               } catch (Exception e) {
+                       log.error("Cannot initialise script bundle " + context.getBundle().getSymbolicName(), e);
+                       throw e;
+               }
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/ScriptUi.java
new file mode 100644 (file)
index 0000000..0c870e1
--- /dev/null
@@ -0,0 +1,115 @@
+package org.argeo.cms.ui.script;
+
+import java.net.URL;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+
+class ScriptUi implements CmsUiProvider {
+       private final static CmsLog log = CmsLog.getLog(ScriptUi.class);
+
+       private boolean development = true;
+       private ScriptEngine scriptEngine;
+
+       private URL appUrl;
+       // private BundleContext bundleContext;
+       // private String path;
+
+       // private Bindings bindings;
+       // private String script;
+
+       public ScriptUi(BundleContext bundleContext,ScriptEngine scriptEngine, String path) {
+               this.scriptEngine = scriptEngine;
+////           ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+//             ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+//             ClassLoader originalCcl = Thread.currentThread().getContextClassLoader();
+//             try {
+////                   Thread.currentThread().setContextClassLoader(bundleCl);
+////                   scriptEngine = scriptEngineManager.getEngineByName("JavaScript");
+////                   scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext);
+//                     scriptEngine = CmsScriptRwtApplication.loadScriptEngine(originalCcl, bundleCl);
+//
+//             } catch (Exception e) {
+//                     e.printStackTrace();
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(originalCcl);
+//             }
+               this.appUrl = bundleContext.getBundle().getEntry(path);
+               load();
+       }
+
+       private void load() {
+//             try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+//                     scriptEngine.eval(reader);
+//             } catch (IOException | ScriptException e) {
+//                     log.warn("Cannot execute " + appUrl, e);
+//             }
+
+               try {
+                       scriptEngine.eval("load('" + appUrl + "')");
+               } catch (ScriptException e) {
+                       log.warn("Cannot execute " + appUrl, e);
+               }
+
+       }
+
+       // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws
+       // ScriptException {
+       // super();
+       // this.scriptEngine = scriptEngine;
+       // this.script = script;
+       // bindings = scriptEngine.createBindings();
+       // scriptEngine.eval(script, bindings);
+       // }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               long begin = System.currentTimeMillis();
+               // if (bindings == null) {
+               // bindings = scriptEngine.createBindings();
+               // try {
+               // scriptEngine.eval(script, bindings);
+               // } catch (ScriptException e) {
+               // log.warn("Cannot evaluate script", e);
+               // }
+               // }
+               // Bindings bindings = scriptEngine.createBindings();
+               // bindings.put("parent", parent);
+               // bindings.put("context", context);
+               // URL appUrl = bundleContext.getBundle().getEntry(path);
+               // try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+               // scriptEngine.eval(reader,bindings);
+               // } catch (IOException | ScriptException e) {
+               // log.warn("Cannot execute " + appUrl, e);
+               // }
+
+               if (development)
+                       load();
+
+               Invocable invocable = (Invocable) scriptEngine;
+               try {
+                       invocable.invokeFunction("createUi", parent, context);
+               } catch (NoSuchMethodException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ScriptException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+               long duration = System.currentTimeMillis() - begin;
+               if (log.isTraceEnabled())
+                       log.trace(appUrl + " UI in " + duration + " ms");
+               return null;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/cms.js
new file mode 100644 (file)
index 0000000..be9618d
--- /dev/null
@@ -0,0 +1,90 @@
+// CMS
+var ScrolledPage = Java.type('org.argeo.cms.ui.widgets.ScrolledPage');
+
+var CmsScriptApp = Java.type('org.argeo.cms.ui.script.CmsScriptApp');
+var AppUi = Java.type('org.argeo.cms.ui.script.AppUi');
+var Theme = Java.type('org.argeo.cms.ui.script.Theme');
+var ScriptUi = Java.type('org.argeo.cms.ui.script.ScriptUi');
+var CmsUtils = Java.type('org.argeo.cms.ui.util.CmsUiUtils');
+var SimpleCmsHeader = Java.type('org.argeo.cms.ui.util.SimpleCmsHeader');
+var CmsLink = Java.type('org.argeo.cms.ui.util.CmsLink');
+var MenuLink = Java.type('org.argeo.cms.ui.util.MenuLink');
+var UserMenuLink = Java.type('org.argeo.cms.ui.util.UserMenuLink');
+
+// SWT
+var SWT = Java.type('org.eclipse.swt.SWT');
+var Composite = Java.type('org.eclipse.swt.widgets.Composite');
+var Label = Java.type('org.eclipse.swt.widgets.Label');
+var Button = Java.type('org.eclipse.swt.widgets.Button');
+var Text = Java.type('org.eclipse.swt.widgets.Text');
+var Browser = Java.type('org.eclipse.swt.browser.Browser');
+
+var FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
+var GridLayout = Java.type('org.eclipse.swt.layout.GridLayout');
+var RowLayout = Java.type('org.eclipse.swt.layout.RowLayout');
+var FormLayout = Java.type('org.eclipse.swt.layout.FormLayout');
+var GridData = Java.type('org.eclipse.swt.layout.GridData');
+
+function loadNode(node) {
+       var json = CmsScriptApp.toJson(node)
+       var fromJson = JSON.parse(json)
+       return fromJson
+}
+
+function newArea(parent, style, layout) {
+       var control = new Composite(parent, SWT.NONE)
+       control.setLayout(layout)
+       CmsUtils.style(control, style)
+       return control
+}
+
+function newLabel(parent, style, text) {
+       var control = new Label(parent, SWT.WRAP)
+       control.setText(text)
+       CmsUtils.style(control, style)
+       CmsUtils.markup(control)
+       return control
+}
+
+function newButton(parent, style, text) {
+       var control = new Button(parent, SWT.FLAT)
+       control.setText(text)
+       CmsUtils.style(control, style)
+       CmsUtils.markup(control)
+       return control
+}
+
+function newFormLabel(parent, style, text) {
+       return newLabel(parent, style, '<b>' + text + '</b>')
+}
+
+function newText(parent, style, msg) {
+       var control = new Text(parent, SWT.NONE)
+       control.setMessage(msg)
+       CmsUtils.style(control, style)
+       return control
+}
+
+function newScrolledPage(parent) {
+       var scrolled = new ScrolledPage(parent, SWT.NONE)
+       scrolled.setLayoutData(CmsUtils.fillAll())
+       scrolled.setLayout(CmsUtils.noSpaceGridLayout())
+       var page = new Composite(scrolled, SWT.NONE)
+       page.setLayout(CmsUtils.noSpaceGridLayout())
+       page.setBackgroundMode(SWT.INHERIT_NONE)
+       return page
+}
+
+function gridData(control) {
+       var gridData = new GridData()
+       control.setLayoutData(gridData)
+       return gridData
+}
+
+function gridData(control, hAlign, vAlign) {
+       var gridData = new GridData(hAlign, vAlign, false, false)
+       control.setLayoutData(gridData)
+       return gridData
+}
+
+// print(__FILE__, __LINE__, __DIR__)
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/ui/script/package-info.java
new file mode 100644 (file)
index 0000000..7440596
--- /dev/null
@@ -0,0 +1,2 @@
+/** Argeo CMS user interface scripting. */
+package org.argeo.cms.ui.script;
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/AbstractCmsEntryPoint.java
new file mode 100644 (file)
index 0000000..947a4d1
--- /dev/null
@@ -0,0 +1,398 @@
+package org.argeo.cms.web;
+
+import static org.argeo.util.naming.SharedSecret.X_SHARED_SECRET;
+
+import java.io.IOException;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallback;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.naming.AuthPassword;
+import org.argeo.util.naming.SharedSecret;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.AbstractEntryPoint;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/** Manages history and navigation */
+@Deprecated
+public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implements CmsView {
+       private static final long serialVersionUID = 906558779562569784L;
+
+       private final CmsLog log = CmsLog.getLog(AbstractCmsEntryPoint.class);
+
+       // private final Subject subject;
+       private LoginContext loginContext;
+
+       private final Repository repository;
+       private final String workspace;
+       private final String defaultPath;
+       private final Map<String, String> factoryProperties;
+
+       // Current state
+       private Session session;
+       private Node node;
+       private String nodePath;// useful when changing auth
+       private String state;
+       private Throwable exception;
+
+       // Client services
+       private final JavaScriptExecutor jsExecutor;
+       private final BrowserNavigation browserNavigation;
+
+       public AbstractCmsEntryPoint(Repository repository, String workspace, String defaultPath,
+                       Map<String, String> factoryProperties) {
+               this.repository = repository;
+               this.workspace = workspace;
+               this.defaultPath = defaultPath;
+               this.factoryProperties = new HashMap<String, String>(factoryProperties);
+               // subject = new Subject();
+
+               // Initial login
+               LoginContext lc;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
+                       lc.login();
+               } catch (LoginException e) {
+                       try {
+                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
+                               lc.login();
+                       } catch (LoginException e1) {
+                               throw new CmsException("Cannot log in as anonymous", e1);
+                       }
+               }
+               authChange(lc);
+
+               jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+               if (browserNavigation != null)
+                       browserNavigation.addBrowserNavigationListener(new CmsNavigationListener());
+       }
+
+       @Override
+       protected Shell createShell(Display display) {
+               Shell shell = super.createShell(display);
+               shell.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_SHELL);
+               display.disposeExec(new Runnable() {
+
+                       @Override
+                       public void run() {
+                               if (log.isTraceEnabled())
+                                       log.trace("Logging out " + session);
+                               JcrUtils.logoutQuietly(session);
+                       }
+               });
+               return shell;
+       }
+
+       @Override
+       protected final void createContents(final Composite parent) {
+               // UiContext.setData(CmsView.KEY, this);
+               CmsSwtUtils.registerCmsView(parent.getShell(), this);
+               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               try {
+                                       initUi(parent);
+                               } catch (Exception e) {
+                                       throw new CmsException("Cannot create entrypoint contents", e);
+                               }
+                               return null;
+                       }
+               });
+       }
+
+       /** Create UI */
+       protected abstract void initUi(Composite parent);
+
+       /** Recreate UI after navigation or auth change */
+       protected abstract void refresh();
+
+       /**
+        * The node to return when no node was found (for authenticated users and
+        * anonymous)
+        */
+//     private Node getDefaultNode(Session session) throws RepositoryException {
+//             if (!session.hasPermission(defaultPath, "read")) {
+//                     String userId = session.getUserID();
+//                     if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
+//                             // TODO throw a special exception
+//                             throw new CmsException("Login required");
+//                     else
+//                             throw new CmsException("Unauthorized");
+//             }
+//             return session.getNode(defaultPath);
+//     }
+
+       protected String getBaseTitle() {
+               return factoryProperties.get(WebClient.PAGE_TITLE);
+       }
+
+       public void navigateTo(String state) {
+               exception = null;
+               String title = setState(state);
+               doRefresh();
+               if (browserNavigation != null)
+                       browserNavigation.pushState(state, title);
+       }
+
+       // @Override
+       // public synchronized Subject getSubject() {
+       // return subject;
+       // }
+
+       // @Override
+       // public LoginContext getLoginContext() {
+       // return loginContext;
+       // }
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public synchronized void logout() {
+               if (loginContext == null)
+                       throw new CmsException("Login context should not be null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+                       LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
+                       anonymousLc.login();
+                       authChange(anonymousLc);
+               } catch (LoginException e) {
+                       log.error("Cannot logout", e);
+               }
+       }
+
+       @Override
+       public synchronized void authChange(LoginContext lc) {
+               if (lc == null)
+                       throw new CmsException("Login context cannot be null");
+               // logout previous login context
+               if (this.loginContext != null)
+                       try {
+                               this.loginContext.logout();
+                       } catch (LoginException e1) {
+                               log.warn("Could not log out: " + e1);
+                       }
+               this.loginContext = lc;
+               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+
+                       @Override
+                       public Void run() {
+                               try {
+                                       JcrUtils.logoutQuietly(session);
+                                       session = repository.login(workspace);
+                                       if (nodePath != null)
+                                               try {
+                                                       node = session.getNode(nodePath);
+                                               } catch (PathNotFoundException e) {
+                                                       navigateTo("~");
+                                               }
+
+                                       // refresh UI
+                                       doRefresh();
+                               } catch (RepositoryException e) {
+                                       throw new CmsException("Cannot perform auth change", e);
+                               }
+                               return null;
+                       }
+
+               });
+       }
+
+       @Override
+       public void exception(final Throwable e) {
+               AbstractCmsEntryPoint.this.exception = e;
+               log.error("Unexpected exception in CMS", e);
+               doRefresh();
+       }
+
+       protected synchronized void doRefresh() {
+               Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               refresh();
+                               return null;
+                       }
+               });
+       }
+
+       /** Sets the state of the entry point and retrieve the related JCR node. */
+       protected synchronized String setState(String newState) {
+               String previousState = this.state;
+
+               String newNodePath = null;
+               String prefix = null;
+               this.state = newState;
+               if (newState.equals("~"))
+                       this.state = "";
+
+               try {
+                       int firstSlash = state.indexOf('/');
+                       if (firstSlash == 0) {
+                               newNodePath = state;
+                               prefix = "";
+                       } else if (firstSlash > 0) {
+                               prefix = state.substring(0, firstSlash);
+                               newNodePath = state.substring(firstSlash);
+                       } else {
+                               newNodePath = defaultPath;
+                               prefix = state;
+
+                       }
+
+                       // auth
+                       int colonIndex = prefix.indexOf('$');
+                       if (colonIndex > 0) {
+                               SharedSecret token = new SharedSecret(new AuthPassword(X_SHARED_SECRET + '$' + prefix)) {
+
+                                       @Override
+                                       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                               super.handle(callbacks);
+                                               // handle HTTP context
+                                               for (Callback callback : callbacks) {
+                                                       if (callback instanceof RemoteAuthCallback) {
+                                                               ((RemoteAuthCallback) callback)
+                                                                               .setRequest(new ServletHttpRequest(UiContext.getHttpRequest()));
+                                                               ((RemoteAuthCallback) callback)
+                                                                               .setResponse(new ServletHttpResponse(UiContext.getHttpResponse()));
+                                                       }
+                                               }
+                                       }
+                               };
+                               LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, token);
+                               lc.login();
+                               authChange(lc);// sets the node as well
+                               // } else {
+                               // // TODO check consistency
+                               // }
+                       } else {
+                               Node newNode = null;
+                               if (session.nodeExists(newNodePath))
+                                       newNode = session.getNode(newNodePath);
+                               else {
+//                                     throw new CmsException("Data " + newNodePath + " does not exist");
+                                       newNode = null;
+                               }
+                               setNode(newNode);
+                       }
+                       String title = publishMetaData(getNode());
+
+                       if (log.isTraceEnabled())
+                               log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
+
+                       return title;
+               } catch (Exception e) {
+                       log.error("Cannot set state '" + state + "'", e);
+                       if (state.equals("") || newState.equals("~") || newState.equals(previousState))
+                               return "Unrecoverable exception : " + e.getClass().getSimpleName();
+                       if (previousState.equals(""))
+                               previousState = "~";
+                       navigateTo(previousState);
+                       throw new CmsException("Unexpected issue when accessing #" + newState, e);
+               }
+       }
+
+       private String publishMetaData(Node node) throws RepositoryException {
+               // Title
+               String title;
+               if (node != null && node.isNodeType(NodeType.MIX_TITLE) && node.hasProperty(Property.JCR_TITLE))
+                       title = node.getProperty(Property.JCR_TITLE).getString() + " - " + getBaseTitle();
+               else
+                       title = getBaseTitle();
+
+               HttpServletRequest request = UiContext.getHttpRequest();
+               if (request == null)
+                       return null;
+
+               StringBuilder js = new StringBuilder();
+               if (title == null)
+                       title = "";
+               title = title.replace("'", "\\'");// sanitize
+               js.append("document.title = '" + title + "';");
+               jsExecutor.execute(js.toString());
+               return title;
+       }
+
+       // Simply remove some illegal character
+       // private String clean(String stringToClean) {
+       // return stringToClean.replaceAll("'", "").replaceAll("\\n", "")
+       // .replaceAll("\\t", "");
+       // }
+
+       protected synchronized Node getNode() {
+               return node;
+       }
+
+       private synchronized void setNode(Node node) throws RepositoryException {
+               this.node = node;
+               this.nodePath = node == null ? null : node.getPath();
+       }
+
+       protected String getState() {
+               return state;
+       }
+
+       protected Throwable getException() {
+               return exception;
+       }
+
+       protected void resetException() {
+               exception = null;
+       }
+
+       protected Session getSession() {
+               return session;
+       }
+
+       private class CmsNavigationListener implements BrowserNavigationListener {
+               private static final long serialVersionUID = -3591018803430389270L;
+
+               @Override
+               public void navigated(BrowserNavigationEvent event) {
+                       setState(event.getState());
+                       doRefresh();
+               }
+       }
+}
\ No newline at end of file
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/BundleResourceLoader.java
new file mode 100644 (file)
index 0000000..ca93e62
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+
+/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
+public class BundleResourceLoader implements ResourceLoader {
+       private final Bundle bundle;
+
+       public BundleResourceLoader(Bundle bundle) {
+               this.bundle = bundle;
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               URL res = bundle.getEntry(resourceName);
+               if (res == null) {
+                       res = bundle.getResource(resourceName);
+                       if (res == null)
+                               throw new IllegalArgumentException(
+                                               "Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
+               }
+               return res.openStream();
+       }
+
+       public Bundle getBundle() {
+               return bundle;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsThemeResourceLoader.java
new file mode 100644 (file)
index 0000000..5de0f91
--- /dev/null
@@ -0,0 +1,23 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.argeo.api.cms.CmsTheme;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** A RAP {@link ResourceLoader} based on a {@link CmsTheme}. */
+public class CmsThemeResourceLoader implements ResourceLoader {
+       private final CmsTheme theme;
+
+       public CmsThemeResourceLoader(CmsTheme theme) {
+               super();
+               this.theme = theme;
+       }
+
+       @Override
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
+               return theme.getResourceAsStream(resourceName);
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebApp.java
new file mode 100644 (file)
index 0000000..4008b49
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.cms.web;
+
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAppListener;
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.swt.widgets.Display;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.EventAdmin;
+
+/** An RWT web app integrating with a {@link CmsApp}. */
+public class CmsWebApp implements ApplicationConfiguration, ExceptionHandler, CmsAppListener {
+       private final static CmsLog log = CmsLog.getLog(CmsWebApp.class);
+
+       private BundleContext bundleContext;
+       private CmsApp cmsApp;
+       private String cmsAppId;
+       private EventAdmin eventAdmin;
+
+       private ServiceRegistration<ApplicationConfiguration> rwtAppReg;
+
+       private final static String CONTEXT_NAME = "contextName";
+       private String contextName;
+
+       private final static String FAVICON_PNG = "favicon.png";
+
+       public void init(BundleContext bundleContext, Map<String, String> properties) {
+               this.bundleContext = bundleContext;
+               contextName = properties.get(CONTEXT_NAME);
+               if (cmsApp != null) {
+                       if (cmsApp.allThemesAvailable())
+                               publishWebApp();
+               }
+       }
+
+       public void destroy(BundleContext bundleContext, Map<String, String> properties) {
+               if (cmsApp != null) {
+                       cmsApp.removeCmsAppListener(this);
+                       cmsApp = null;
+               }
+       }
+
+       @Override
+       public void configure(Application application) {
+               // TODO make it configurable?
+               // SWT compatibility is required for:
+               // - Browser.execute()
+               // - blocking dialogs
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+               for (String uiName : cmsApp.getUiNames()) {
+                       CmsTheme theme = cmsApp.getTheme(uiName);
+                       if (theme != null)
+                               WebThemeUtils.apply(application, theme);
+               }
+
+               Map<String, String> properties = new HashMap<>();
+               addEntryPoints(application, properties);
+               application.setExceptionHandler(this);
+       }
+
+       @Override
+       public void handleException(Throwable throwable) {
+               Display display = Display.getCurrent();
+               if (display != null && !display.isDisposed()) {
+                       CmsView cmsView = CmsSwtUtils.getCmsView(display.getActiveShell());
+                       cmsView.exception(throwable);
+               } else {
+                       log.error("Unexpected exception outside an UI thread", throwable);
+               }
+
+       }
+
+       protected void addEntryPoints(Application application, Map<String, String> commonProperties) {
+               for (String uiName : cmsApp.getUiNames()) {
+                       Map<String, String> properties = new HashMap<>(commonProperties);
+                       CmsTheme theme = cmsApp.getTheme(uiName);
+                       if (theme != null) {
+                               properties.put(WebClient.THEME_ID, theme.getThemeId());
+                               properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+                               properties.put(WebClient.BODY_HTML, theme.getBodyHtml());
+                               Set<String> imagePaths = theme.getImagesPaths();
+                               if (imagePaths.contains(FAVICON_PNG)) {
+                                       properties.put(WebClient.FAVICON, FAVICON_PNG);
+                               }
+                       } else {
+                               properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+                       }
+                       String entryPointName = !uiName.equals("") ? "/" + uiName : "/";
+                       application.addEntryPoint(entryPointName, () -> {
+                               CmsWebEntryPoint entryPoint = new CmsWebEntryPoint(this, uiName);
+                               entryPoint.setEventAdmin(eventAdmin);
+                               return entryPoint;
+                       }, properties);
+                       if (log.isDebugEnabled())
+                               log.info("Added web entry point " + (contextName != null ? "/" + contextName : "") + entryPointName);
+               }
+//             if (log.isDebugEnabled())
+//                     log.debug("Published CMS web app /" + (contextName != null ? contextName : ""));
+       }
+
+       CmsApp getCmsApp() {
+               return cmsApp;
+       }
+
+       BundleContext getBundleContext() {
+               return bundleContext;
+       }
+
+       public void setCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               this.cmsApp = cmsApp;
+               this.cmsAppId = properties.get(Constants.SERVICE_PID);
+               this.cmsApp.addCmsAppListener(this);
+       }
+
+       public void unsetCmsApp(CmsApp cmsApp, Map<String, String> properties) {
+               String cmsAppId = properties.get(Constants.SERVICE_PID);
+               if (!cmsAppId.equals(this.cmsAppId))
+                       return;
+               if (this.cmsApp != null) {
+                       this.cmsApp.removeCmsAppListener(this);
+               }
+               if (rwtAppReg != null)
+                       rwtAppReg.unregister();
+               this.cmsApp = null;
+       }
+
+       @Override
+       public void themingUpdated() {
+               if (cmsApp != null && cmsApp.allThemesAvailable())
+                       publishWebApp();
+       }
+
+       protected void publishWebApp() {
+               Dictionary<String, Object> regProps = LangUtils.dict(CONTEXT_NAME, contextName);
+               if (rwtAppReg != null)
+                       rwtAppReg.unregister();
+               if (bundleContext != null) {
+                       rwtAppReg = bundleContext.registerService(ApplicationConfiguration.class, this, regProps);
+                       if (log.isDebugEnabled())
+                               log.debug("Publishing CMS web app /" + (contextName != null ? contextName : "") + " ...");
+               }
+       }
+
+       public void setEventAdmin(EventAdmin eventAdmin) {
+               this.eventAdmin = eventAdmin;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/CmsWebEntryPoint.java
new file mode 100644 (file)
index 0000000..afc07c5
--- /dev/null
@@ -0,0 +1,357 @@
+package org.argeo.cms.web;
+
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsUi;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.UxContext;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.argeo.cms.servlet.ServletHttpRequest;
+import org.argeo.cms.servlet.ServletHttpResponse;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.dialogs.CmsFeedback;
+import org.argeo.cms.ui.util.DefaultImageManager;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTError;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/** The {@link CmsView} for a {@link CmsWebApp}. */
+@SuppressWarnings("restriction")
+public class CmsWebEntryPoint implements EntryPoint, CmsView, BrowserNavigationListener {
+       private static final long serialVersionUID = 7733510691684570402L;
+       private final static CmsLog log = CmsLog.getLog(CmsWebEntryPoint.class);
+
+       private EventAdmin eventAdmin;
+
+       private final CmsWebApp cmsWebApp;
+       private final String uiName;
+
+       private LoginContext loginContext;
+       private String state;
+       private Throwable exception;
+       private UxContext uxContext;
+       private CmsImageManager imageManager;
+
+       private Display display;
+       private CmsUi ui;
+
+       private String uid;
+
+       // Client services
+       // private final JavaScriptExecutor jsExecutor;
+       private final BrowserNavigation browserNavigation;
+
+       /** Experimental OS-like multi windows. */
+       private boolean multipleShells = false;
+
+       public CmsWebEntryPoint(CmsWebApp cmsWebApp, String uiName) {
+               assert cmsWebApp != null;
+               assert uiName != null;
+               this.cmsWebApp = cmsWebApp;
+               this.uiName = uiName;
+               uid = UUID.randomUUID().toString();
+
+               // Initial login
+               LoginContext lc;
+               try {
+                       lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER,
+                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(UiContext.getHttpRequest()),
+                                                       new ServletHttpResponse(UiContext.getHttpResponse())));
+                       lc.login();
+               } catch (LoginException e) {
+                       try {
+                               lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
+                               lc.login();
+                       } catch (LoginException e1) {
+                               throw new IllegalStateException("Cannot log in as anonymous", e1);
+                       }
+               }
+               authChange(lc);
+
+               // jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
+               browserNavigation = RWT.getClient().getService(BrowserNavigation.class);
+               if (browserNavigation != null)
+                       browserNavigation.addBrowserNavigationListener(this);
+       }
+
+       protected void createContents(Composite parent) {
+               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
+                       @Override
+                       public Void run() {
+                               try {
+                                       uxContext = new SimpleSwtUxContext();
+                                       imageManager = new DefaultImageManager();
+                                       CmsSession cmsSession = getCmsSession();
+                                       if (cmsSession != null) {
+                                               UiContext.setLocale(cmsSession.getLocale());
+                                               LocaleUtils.setThreadLocale(cmsSession.getLocale());
+                                       } else {
+                                               Locale rwtLocale = RWT.getUISession().getLocale();
+                                               LocaleUtils.setThreadLocale(rwtLocale);
+                                       }
+                                       parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+                                       display = parent.getDisplay();
+                                       ui = cmsWebApp.getCmsApp().initUi(parent);
+                                       if (ui instanceof Composite)
+                                               ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+                                       // we need ui to be set before refresh so that CmsView can store UI context data
+                                       // in it.
+                                       cmsWebApp.getCmsApp().refreshUi(ui, null);
+                               } catch (Exception e) {
+                                       throw new IllegalStateException("Cannot create entrypoint contents", e);
+                               }
+                               return null;
+                       }
+               });
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       public <T> T doAs(PrivilegedAction<T> action) {
+               return Subject.doAs(getSubject(), action);
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public synchronized void logout() {
+               if (loginContext == null)
+                       throw new IllegalArgumentException("Login context should not be null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+                       LoginContext anonymousLc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS);
+                       anonymousLc.login();
+                       authChange(anonymousLc);
+               } catch (LoginException e) {
+                       log.error("Cannot logout", e);
+               }
+       }
+
+       @Override
+       public synchronized void authChange(LoginContext lc) {
+               if (lc == null)
+                       throw new IllegalArgumentException("Login context cannot be null");
+               // logout previous login context
+               if (this.loginContext != null)
+                       try {
+                               this.loginContext.logout();
+                       } catch (LoginException e1) {
+                               log.warn("Could not log out: " + e1);
+                       }
+               this.loginContext = lc;
+               doRefresh();
+       }
+
+       @Override
+       public void exception(final Throwable e) {
+               if (e instanceof SWTError) {
+                       SWTError swtError = (SWTError) e;
+                       if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED)
+                               return;
+               }
+               display.syncExec(() -> {
+//                     CmsFeedback.show("Unexpected exception in CMS", e);
+                       exception = e;
+//             log.error("Unexpected exception in CMS", e);
+                       doRefresh();
+               });
+       }
+
+       protected synchronized void doRefresh() {
+               if (ui != null)
+                       Subject.doAs(getSubject(), new PrivilegedAction<Void>() {
+                               @Override
+                               public Void run() {
+                                       if (exception != null) {
+                                               // TODO internationalise
+                                               CmsFeedback.show("Unexpected exception", exception);
+                                               exception = null;
+                                               // TODO report
+                                       }
+                                       cmsWebApp.getCmsApp().refreshUi(ui, state);
+                                       return null;
+                               }
+                       });
+       }
+
+       /** Sets the state of the entry point and retrieve the related JCR node. */
+       protected String setState(String newState) {
+               cmsWebApp.getCmsApp().setState(ui, newState);
+               state = newState;
+               return null;
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               exception = null;
+               String title = setState(state);
+               if (title != null)
+                       doRefresh();
+               if (browserNavigation != null)
+                       browserNavigation.pushState(state, title);
+       }
+
+       public CmsImageManager getImageManager() {
+               return imageManager;
+       }
+
+       @Override
+       public void navigated(BrowserNavigationEvent event) {
+               setState(event.getState());
+               // doRefresh();
+       }
+
+       @Override
+       public void sendEvent(String topic, Map<String, Object> properties) {
+               if (properties == null)
+                       properties = new HashMap<>();
+               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
+                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
+                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
+               properties.put(CMS_VIEW_UID_PROPERTY, uid);
+               eventAdmin.sendEvent(new Event(topic, properties));
+       }
+
+       @Override
+       public void stateChanged(String state, String title) {
+               browserNavigation.pushState(state, title);
+       }
+
+       @Override
+       public CmsSession getCmsSession() {
+               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(cmsWebApp.getBundleContext(), getSubject());
+               return cmsSession;
+       }
+
+       @Override
+       public Object getData(String key) {
+               if (ui != null) {
+                       return ui.getData(key);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+       @Override
+       public void setData(String key, Object value) {
+               if (ui != null) {
+                       ui.setData(key, value);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+       /*
+        * EntryPoint IMPLEMENTATION
+        */
+
+       @Override
+       public int createUI() {
+               Display display = new Display();
+               Shell shell = createShell(display);
+               shell.setLayout(CmsSwtUtils.noSpaceGridLayout());
+               CmsSwtUtils.registerCmsView(shell, this);
+               createContents(shell);
+               shell.layout();
+//             if (shell.getMaximized()) {
+//                     shell.layout();
+//             } else {
+////                   shell.pack();
+//             }
+               shell.open();
+               if (getApplicationContext().getLifeCycleFactory().getLifeCycle() instanceof RWTLifeCycle) {
+                       eventLoop: while (!shell.isDisposed()) {
+                               try {
+                                       if (!display.readAndDispatch()) {
+                                               display.sleep();
+                                       }
+                               } catch (Throwable e) {
+                                       if (e instanceof SWTError) {
+                                               SWTError swtError = (SWTError) e;
+                                               if (swtError.code == SWT.ERROR_FUNCTION_DISPOSED) {
+                                                       log.error("Unexpected SWT error in event loop, ignoring it. " + e.getMessage());
+                                                       continue eventLoop;
+                                               } else {
+                                                       log.error("Unexpected SWT error in event loop, shutting down...", e);
+                                                       break eventLoop;
+                                               }
+                                       } else if (e instanceof ThreadDeath) {
+                                               throw (ThreadDeath) e;
+                                       } else if (e instanceof Error) {
+                                               log.error("Unexpected error in event loop, shutting down...", e);
+                                               break eventLoop;
+                                       } else {
+                                               log.error("Unexpected exception in event loop, ignoring it. " + e.getMessage());
+                                               continue eventLoop;
+                                       }
+                               }
+                       }
+                       if (!display.isDisposed())
+                               display.dispose();
+               }
+               return 0;
+       }
+
+       protected Shell createShell(Display display) {
+               Shell shell;
+               if (!multipleShells) {
+                       shell = new Shell(display, SWT.NO_TRIM);
+                       shell.setMaximized(true);
+               } else {
+                       shell = new Shell(display, SWT.SHELL_TRIM);
+                       shell.setSize(800, 600);
+               }
+               return shell;
+       }
+
+       public void setEventAdmin(EventAdmin eventAdmin) {
+               this.eventAdmin = eventAdmin;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/MinimalWebApp.java
new file mode 100644 (file)
index 0000000..2eff71e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.argeo.cms.web;
+
+import static org.argeo.cms.osgi.BundleCmsTheme.CMS_THEME_BUNDLE_PROPERTY;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.cms.osgi.BundleCmsTheme;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.osgi.framework.BundleContext;
+
+/** Lightweight web app using only RWT and not the whole Eclipse platform. */
+public class MinimalWebApp implements ApplicationConfiguration {
+
+       private BundleCmsTheme theme;
+
+       public void init(BundleContext bundleContext, Map<String, Object> properties) {
+               if (properties.containsKey(CMS_THEME_BUNDLE_PROPERTY)) {
+                       String cmsThemeBundle = properties.get(CMS_THEME_BUNDLE_PROPERTY).toString();
+                       theme = new BundleCmsTheme(bundleContext, cmsThemeBundle);
+               }
+       }
+
+       public void destroy() {
+
+       }
+
+       /** To be overridden. Does nothing by default. */
+       protected void addEntryPoints(Application application, Map<String, String> properties) {
+
+       }
+
+       @Override
+       public void configure(Application application) {
+               if (theme != null)
+                       WebThemeUtils.apply(application, theme);
+
+               Map<String, String> properties = new HashMap<>();
+               if (theme != null) {
+                       properties.put(WebClient.THEME_ID, theme.getThemeId());
+                       properties.put(WebClient.HEAD_HTML, theme.getHtmlHeaders());
+               } else {
+                       properties.put(WebClient.THEME_ID, RWT.DEFAULT_THEME_ID);
+               }
+               addEntryPoints(application, properties);
+
+       }
+
+       public void setTheme(BundleCmsTheme theme) {
+               this.theme = theme;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleApp.java
new file mode 100644 (file)
index 0000000..f063117
--- /dev/null
@@ -0,0 +1,414 @@
+package org.argeo.cms.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.Privilege;
+import javax.jcr.version.VersionManager;
+
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.jcr.CmsJcrUtils;
+import org.argeo.cms.ui.CmsUiConstants;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.LifeCycleUiProvider;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.util.StyleSheetResourceLoader;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.Application.OperationMode;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.application.EntryPointFactory;
+import org.eclipse.rap.rwt.application.ExceptionHandler;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/** A basic generic app based on {@link SimpleErgonomics}. */
+@Deprecated
+public class SimpleApp implements CmsUiConstants, ApplicationConfiguration {
+       private final static CmsLog log = CmsLog.getLog(SimpleApp.class);
+
+       private String contextName = null;
+
+       private Map<String, Map<String, String>> branding = new HashMap<String, Map<String, String>>();
+       private Map<String, List<String>> styleSheets = new HashMap<String, List<String>>();
+
+       private List<String> resources = new ArrayList<String>();
+
+       private BundleContext bundleContext;
+
+       private Repository repository;
+       private String workspace = null;
+       private String jcrBasePath = "/";
+       private List<String> roPrincipals = Arrays.asList(CmsConstants.ROLE_ANONYMOUS, CmsConstants.ROLE_USER);
+       private List<String> rwPrincipals = Arrays.asList(CmsConstants.ROLE_USER);
+
+       private CmsUiProvider header;
+       private Map<String, CmsUiProvider> pages = new LinkedHashMap<String, CmsUiProvider>();
+
+       private Integer headerHeight = 40;
+
+       private ServiceRegistration<ApplicationConfiguration> appReg;
+
+       public void configure(Application application) {
+               try {
+                       BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
+
+                       application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+                       // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
+
+                       application.setExceptionHandler(new CmsExceptionHandler());
+
+                       // loading animated gif
+                       application.addResource(LOADING_IMAGE, createResourceLoader(LOADING_IMAGE));
+                       // empty image
+                       application.addResource(NO_IMAGE, createResourceLoader(NO_IMAGE));
+
+                       for (String resource : resources) {
+                               application.addResource(resource, bundleRL);
+                               if (log.isTraceEnabled())
+                                       log.trace("Resource " + resource);
+                       }
+
+                       Map<String, String> defaultBranding = null;
+                       if (branding.containsKey("*"))
+                               defaultBranding = branding.get("*");
+                       // String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
+
+                       // entry points
+                       for (String page : pages.keySet()) {
+                               Map<String, String> properties = defaultBranding != null ? new HashMap<String, String>(defaultBranding)
+                                               : new HashMap<String, String>();
+                               if (branding.containsKey(page)) {
+                                       properties.putAll(branding.get(page));
+                               }
+                               // favicon
+                               if (properties.containsKey(WebClient.FAVICON)) {
+                                       String themeId = defaultBranding.get(WebClient.THEME_ID);
+                                       Bundle themeBundle = findThemeBundle(bundleContext, themeId);
+                                       String faviconRelPath = properties.get(WebClient.FAVICON);
+                                       application.addResource(faviconRelPath,
+                                                       new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
+                                       if (log.isTraceEnabled())
+                                               log.trace("Favicon " + faviconRelPath);
+
+                               }
+
+                               // page title
+                               if (!properties.containsKey(WebClient.PAGE_TITLE)) {
+                                       if (page.length() > 0)
+                                               properties.put(WebClient.PAGE_TITLE, Character.toUpperCase(page.charAt(0)) + page.substring(1));
+                               }
+
+                               // default body HTML
+                               if (!properties.containsKey(WebClient.BODY_HTML))
+                                       properties.put(WebClient.BODY_HTML, DEFAULT_LOADING_BODY);
+
+                               //
+                               // ADD ENTRY POINT
+                               //
+                               application.addEntryPoint("/" + page,
+                                               new CmsEntryPointFactory(pages.get(page), repository, workspace, properties), properties);
+                               log.info("Page /" + page);
+                       }
+
+                       // stylesheets and themes
+                       Set<Bundle> themeBundles = new HashSet<>();
+                       for (String themeId : styleSheets.keySet()) {
+                               Bundle themeBundle = findThemeBundle(bundleContext, themeId);
+                               StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
+                                               themeBundle != null ? themeBundle : bundleContext.getBundle());
+                               if (themeBundle != null)
+                                       themeBundles.add(themeBundle);
+                               List<String> cssLst = styleSheets.get(themeId);
+                               if (log.isDebugEnabled())
+                                       log.debug("Theme " + themeId);
+                               for (String css : cssLst) {
+                                       application.addStyleSheet(themeId, css, styleSheetRL);
+                                       if (log.isDebugEnabled())
+                                               log.debug(" CSS " + css);
+                               }
+
+                       }
+                       for (Bundle themeBundle : themeBundles) {
+                               BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
+                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.png");
+                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.gif");
+                               SimpleApp.addThemeResources(application, themeBundle, themeBRL, "*.jpg");
+                       }
+               } catch (RuntimeException e) {
+                       // Easier access to initialisation errors
+                       log.error("Unexpected exception when configuring RWT application.", e);
+                       throw e;
+               }
+       }
+
+       public void init() throws RepositoryException {
+               Session session = null;
+               try {
+                       session = CmsJcrUtils.openDataAdminSession(repository, workspace);
+                       // session = JcrUtils.loginOrCreateWorkspace(repository, workspace);
+                       VersionManager vm = session.getWorkspace().getVersionManager();
+                       JcrUtils.mkdirs(session, jcrBasePath);
+                       session.save();
+                       if (!vm.isCheckedOut(jcrBasePath))
+                               vm.checkout(jcrBasePath);
+                       for (String principal : rwPrincipals)
+                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_WRITE);
+                       for (String principal : roPrincipals)
+                               JcrUtils.addPrivilege(session, jcrBasePath, principal, Privilege.JCR_READ);
+
+                       for (String pageName : pages.keySet()) {
+                               try {
+                                       initPage(session, pages.get(pageName));
+                                       session.save();
+                               } catch (Exception e) {
+                                       throw new CmsException("Cannot initialize page " + pageName, e);
+                               }
+                       }
+
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
+
+               // publish to OSGi
+               register();
+       }
+
+       protected void initPage(Session adminSession, CmsUiProvider page) throws RepositoryException {
+               if (page instanceof LifeCycleUiProvider)
+                       ((LifeCycleUiProvider) page).init(adminSession);
+       }
+
+       public void destroy() {
+               for (String pageName : pages.keySet()) {
+                       try {
+                               CmsUiProvider page = pages.get(pageName);
+                               if (page instanceof LifeCycleUiProvider)
+                                       ((LifeCycleUiProvider) page).destroy();
+                       } catch (Exception e) {
+                               log.error("Cannot destroy page " + pageName, e);
+                       }
+               }
+       }
+
+       protected void register() {
+               Hashtable<String, String> props = new Hashtable<String, String>();
+               if (contextName != null)
+                       props.put("contextName", contextName);
+               appReg = bundleContext.registerService(ApplicationConfiguration.class, this, props);
+               if (log.isDebugEnabled())
+                       log.debug("Registered " + (contextName == null ? "/" : contextName));
+       }
+
+       protected void unregister() {
+               appReg.unregister();
+               if (log.isDebugEnabled())
+                       log.debug("Unregistered " + (contextName == null ? "/" : contextName));
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setWorkspace(String workspace) {
+               this.workspace = workspace;
+       }
+
+       public void setHeader(CmsUiProvider header) {
+               this.header = header;
+       }
+
+       public void setPages(Map<String, CmsUiProvider> pages) {
+               this.pages = pages;
+       }
+
+       public void setJcrBasePath(String basePath) {
+               this.jcrBasePath = basePath;
+       }
+
+       public void setRoPrincipals(List<String> roPrincipals) {
+               this.roPrincipals = roPrincipals;
+       }
+
+       public void setRwPrincipals(List<String> rwPrincipals) {
+               this.rwPrincipals = rwPrincipals;
+       }
+
+       public void setHeaderHeight(Integer headerHeight) {
+               this.headerHeight = headerHeight;
+       }
+
+       public void setBranding(Map<String, Map<String, String>> branding) {
+               this.branding = branding;
+       }
+
+       public void setStyleSheets(Map<String, List<String>> styleSheets) {
+               this.styleSheets = styleSheets;
+       }
+
+       public void setBundleContext(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+       }
+
+       public void setResources(List<String> resources) {
+               this.resources = resources;
+       }
+
+       public void setContextName(String contextName) {
+               this.contextName = contextName;
+       }
+
+       private static void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL,
+                       String pattern) {
+               Enumeration<URL> themeResources = themeBundle.findEntries("/", pattern, true);
+               if (themeResources == null)
+                       return;
+               while (themeResources.hasMoreElements()) {
+                       String resource = themeResources.nextElement().getPath();
+                       // remove first '/' so that RWT registers it
+                       resource = resource.substring(1);
+                       if (!resource.endsWith("/")) {
+                               application.addResource(resource, themeBRL);
+                               if (log.isTraceEnabled())
+                                       log.trace("Registered " + resource + " from theme " + themeBundle);
+                       }
+
+               }
+
+       }
+
+       private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
+               if (themeId == null)
+                       return null;
+               // TODO optimize
+               // TODO deal with multiple versions
+               Bundle themeBundle = null;
+               if (themeId != null) {
+                       for (Bundle bundle : bundleContext.getBundles())
+                               if (themeId.equals(bundle.getSymbolicName())) {
+                                       themeBundle = bundle;
+                                       break;
+                               }
+               }
+               return themeBundle;
+       }
+
+       class CmsExceptionHandler implements ExceptionHandler {
+
+               @Override
+               public void handleException(Throwable throwable) {
+                       // TODO be smarter
+                       CmsUiUtils.getCmsView().exception(throwable);
+               }
+
+       }
+
+       private class CmsEntryPointFactory implements EntryPointFactory {
+               private final CmsUiProvider page;
+               private final Repository repository;
+               private final String workspace;
+               private final Map<String, String> properties;
+
+               public CmsEntryPointFactory(CmsUiProvider page, Repository repository, String workspace,
+                               Map<String, String> properties) {
+                       this.page = page;
+                       this.repository = repository;
+                       this.workspace = workspace;
+                       this.properties = properties;
+               }
+
+               @Override
+               public EntryPoint create() {
+                       SimpleErgonomics entryPoint = new SimpleErgonomics(repository, workspace, jcrBasePath, page, properties) {
+                               private static final long serialVersionUID = -637940404865527290L;
+
+                               @Override
+                               protected void createAdminArea(Composite parent) {
+                                       Composite adminArea = new Composite(parent, SWT.NONE);
+                                       adminArea.setLayout(new FillLayout());
+                                       Button refresh = new Button(adminArea, SWT.PUSH);
+                                       refresh.setText("Reload App");
+                                       refresh.addSelectionListener(new SelectionAdapter() {
+                                               private static final long serialVersionUID = -7671999525536351366L;
+
+                                               @Override
+                                               public void widgetSelected(SelectionEvent e) {
+                                                       long timeBeforeReload = 1000;
+                                                       RWT.getClient().getService(JavaScriptExecutor.class).execute(
+                                                                       "setTimeout(function() { " + "location.reload();" + "}," + timeBeforeReload + ");");
+                                                       reloadApp();
+                                               }
+                                       });
+                               }
+                       };
+                       // entryPoint.setState("");
+                       entryPoint.setHeader(header);
+                       entryPoint.setHeaderHeight(headerHeight);
+                       // CmsSession.current.set(entryPoint);
+                       return entryPoint;
+               }
+
+               private void reloadApp() {
+                       new Thread("Refresh app") {
+                               @Override
+                               public void run() {
+                                       unregister();
+                                       register();
+                               }
+                       }.start();
+               }
+       }
+
+       private static ResourceLoader createResourceLoader(final String resourceName) {
+               return new ResourceLoader() {
+                       public InputStream getResourceAsStream(String resourceName) throws IOException {
+                               return getClass().getClassLoader().getResourceAsStream(resourceName);
+                       }
+               };
+       }
+
+       // private static ResourceLoader createUrlResourceLoader(final URL url) {
+       // return new ResourceLoader() {
+       // public InputStream getResourceAsStream(String resourceName)
+       // throws IOException {
+       // return url.openStream();
+       // }
+       // };
+       // }
+
+       /*
+        * TEXTS
+        */
+       private static String DEFAULT_LOADING_BODY = "<div"
+                       + " style=\"position: absolute; left: 50%; top: 50%; margin: -32px -32px; width: 64px; height:64px\">"
+                       + "<img src=\"./rwt-resources/" + LOADING_IMAGE
+                       + "\" width=\"32\" height=\"32\" style=\"margin: 16px 16px\"/>" + "</div>";
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/SimpleErgonomics.java
new file mode 100644 (file)
index 0000000..26ca370
--- /dev/null
@@ -0,0 +1,238 @@
+package org.argeo.cms.web;
+
+import java.util.Map;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.UxContext;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.swt.CmsStyles;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.DefaultImageManager;
+import org.argeo.cms.ui.util.SystemNotifications;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Simple header/body ergonomics. */
+@Deprecated
+public class SimpleErgonomics extends AbstractCmsEntryPoint {
+       private static final long serialVersionUID = 8743413921359548523L;
+
+       private final static CmsLog log = CmsLog.getLog(SimpleErgonomics.class);
+
+       private boolean uiInitialized = false;
+       private Composite headerArea;
+       private Composite leftArea;
+       private Composite rightArea;
+       private Composite footerArea;
+       private Composite bodyArea;
+       private final CmsUiProvider uiProvider;
+
+       private CmsUiProvider header;
+       private Integer headerHeight = 0;
+       private Integer footerHeight = 0;
+       private CmsUiProvider lead;
+       private CmsUiProvider end;
+       private CmsUiProvider footer;
+
+       private CmsImageManager imageManager = new DefaultImageManager();
+       private UxContext uxContext = null;
+       private String uid;
+
+       public SimpleErgonomics(Repository repository, String workspace, String defaultPath, CmsUiProvider uiProvider,
+                       Map<String, String> factoryProperties) {
+               super(repository, workspace, defaultPath, factoryProperties);
+               this.uiProvider = uiProvider;
+       }
+
+       @Override
+       protected void initUi(Composite parent) {
+               uid = UUID.randomUUID().toString();
+               parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false)));
+
+               uxContext = new SimpleSwtUxContext();
+               if (!getUxContext().isMasterData())
+                       createAdminArea(parent);
+               headerArea = new Composite(parent, SWT.NONE);
+               headerArea.setLayout(new FillLayout());
+               GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
+               headerData.heightHint = headerHeight;
+               headerArea.setLayoutData(headerData);
+
+               // TODO: bi-directional
+               leftArea = new Composite(parent, SWT.NONE);
+               leftArea.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false));
+               leftArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               bodyArea = new Composite(parent, SWT.NONE);
+               bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
+               bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               // TODO: bi-directional
+               rightArea = new Composite(parent, SWT.NONE);
+               rightArea.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
+               rightArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               footerArea = new Composite(parent, SWT.NONE);
+               // footerArea.setLayout(new FillLayout());
+               GridData footerData = new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1);
+               footerData.heightHint = footerHeight;
+               footerArea.setLayoutData(footerData);
+
+               uiInitialized = true;
+               refresh();
+       }
+
+       @Override
+       protected void refresh() {
+               if (!uiInitialized)
+                       return;
+               if (getState() == null)
+                       setState("");
+               refreshSides();
+               refreshBody();
+               if (log.isTraceEnabled())
+                       log.trace("UI refreshed " + getNode());
+       }
+
+       protected void createAdminArea(Composite parent) {
+       }
+
+       @Deprecated
+       protected void refreshHeader() {
+               if (header == null)
+                       return;
+
+               for (Control child : headerArea.getChildren())
+                       child.dispose();
+               try {
+                       header.createUi(headerArea, getNode());
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot refresh header", e);
+               }
+               headerArea.layout(true, true);
+       }
+
+       protected void refreshSides() {
+               refresh(headerArea, header, CmsStyles.CMS_HEADER);
+               refresh(leftArea, lead, CmsStyles.CMS_LEAD);
+               refresh(rightArea, end, CmsStyles.CMS_END);
+               refresh(footerArea, footer, CmsStyles.CMS_FOOTER);
+       }
+
+       private void refresh(Composite area, CmsUiProvider uiProvider, String style) {
+               if (uiProvider == null)
+                       return;
+
+               for (Control child : area.getChildren())
+                       child.dispose();
+               CmsSwtUtils.style(area, style);
+               try {
+                       uiProvider.createUi(area, getNode());
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot refresh header", e);
+               }
+               area.layout(true, true);
+       }
+
+       protected void refreshBody() {
+               // Exception
+               Throwable exception = getException();
+               if (exception != null) {
+                       SystemNotifications systemNotifications = new SystemNotifications(bodyArea);
+                       systemNotifications.notifyException(exception);
+                       resetException();
+                       return;
+                       // TODO report
+               }
+
+               // clear
+               for (Control child : bodyArea.getChildren())
+                       child.dispose();
+               bodyArea.setLayout(CmsSwtUtils.noSpaceGridLayout());
+
+               try {
+                       Node node = getNode();
+//                     if (node == null)
+//                             log.error("Context cannot be null");
+//                     else
+                       uiProvider.createUi(bodyArea, node);
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot refresh body", e);
+               }
+
+               bodyArea.layout(true, true);
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       public CmsImageManager getImageManager() {
+               return imageManager;
+       }
+
+       public void setHeader(CmsUiProvider header) {
+               this.header = header;
+       }
+
+       public void setHeaderHeight(Integer headerHeight) {
+               this.headerHeight = headerHeight;
+       }
+
+       public void setImageManager(CmsImageManager imageManager) {
+               this.imageManager = imageManager;
+       }
+
+       public CmsUiProvider getLead() {
+               return lead;
+       }
+
+       public void setLead(CmsUiProvider lead) {
+               this.lead = lead;
+       }
+
+       public CmsUiProvider getEnd() {
+               return end;
+       }
+
+       public void setEnd(CmsUiProvider end) {
+               this.end = end;
+       }
+
+       public CmsUiProvider getFooter() {
+               return footer;
+       }
+
+       public void setFooter(CmsUiProvider footer) {
+               this.footer = footer;
+       }
+
+       public CmsUiProvider getHeader() {
+               return header;
+       }
+
+       public void setFooterHeight(Integer footerHeight) {
+               this.footerHeight = footerHeight;
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/cms/web/WebThemeUtils.java
new file mode 100644 (file)
index 0000000..ea2ebdf
--- /dev/null
@@ -0,0 +1,28 @@
+package org.argeo.cms.web;
+
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsLog;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+/** Web specific utilities around theming. */
+public class WebThemeUtils {
+       private final static CmsLog log = CmsLog.getLog(WebThemeUtils.class);
+
+       public static void apply(Application application, CmsTheme theme) {
+               ResourceLoader resourceLoader = new CmsThemeResourceLoader(theme);
+               resources: for (String path : theme.getImagesPaths()) {
+                       if (path.startsWith("target/"))
+                               continue resources; // skip maven output
+                       application.addResource(path, resourceLoader);
+                       if (log.isTraceEnabled())
+                               log.trace("Theme " + theme.getThemeId() + ": added resource " + path);
+               }
+               for (String path : theme.getRapCssPaths()) {
+                       application.addStyleSheet(theme.getThemeId(), path, resourceLoader);
+                       if (log.isDebugEnabled())
+                               log.debug("Theme " + theme.getThemeId() + ": added RAP CSS " + path);
+               }
+       }
+
+}
diff --git a/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java b/rap/org.argeo.cms.ui.rap/src/org/argeo/eclipse/ui/jetty/RwtRunner.java
new file mode 100644 (file)
index 0000000..29165a4
--- /dev/null
@@ -0,0 +1,135 @@
+package org.argeo.eclipse.ui.jetty;
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.rap.rwt.application.AbstractEntryPoint;
+import org.eclipse.rap.rwt.application.ApplicationRunner;
+import org.eclipse.rap.rwt.engine.RWTServlet;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** A minimal RWT runner based on embedded Jetty. */
+public class RwtRunner {
+
+       private final Server server;
+       private final ServerConnector serverConnector;
+       private Path tempDir;
+
+       public RwtRunner() {
+               server = new Server(new QueuedThreadPool(10, 1));
+               serverConnector = new ServerConnector(server);
+               serverConnector.setPort(0);
+               server.setConnectors(new Connector[] { serverConnector });
+       }
+
+       protected Control createUi(Composite parent, Object context) {
+               return new Label(parent, SWT.NONE);
+       }
+
+       public void init() {
+               ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
+               context.setContextPath("/");
+               server.setHandler(context);
+
+               String entryPoint = "app";
+
+               // rwt-resources requires a file system
+               try {
+                       tempDir = Files.createTempDirectory("argeo-rwtRunner");
+                       context.setBaseResource(Resource.newResource(tempDir.resolve("www").toString()));
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot create temporary directory", e);
+               }
+               context.addEventListener(new ServletContextListener() {
+                       ApplicationRunner applicationRunner;
+
+                       @Override
+                       public void contextInitialized(ServletContextEvent sce) {
+                               applicationRunner = new ApplicationRunner(
+                                               (application) -> application.addEntryPoint("/" + entryPoint, () -> new AbstractEntryPoint() {
+                                                       private static final long serialVersionUID = 5678385921969090733L;
+
+                                                       @Override
+                                                       protected void createContents(Composite parent) {
+                                                               createUi(parent, null);
+                                                       }
+                                               }, null), sce.getServletContext());
+                               applicationRunner.start();
+                       }
+
+                       @Override
+                       public void contextDestroyed(ServletContextEvent sce) {
+                               applicationRunner.stop();
+                       }
+               });
+
+               context.addServlet(new ServletHolder(new RWTServlet()), "/" + entryPoint);
+
+               // Required to serve rwt-resources. It is important that this is last.
+               ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
+               context.addServlet(holderPwd, "/");
+
+               try {
+                       server.start();
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot start Jetty server", e);
+               }
+       }
+
+       public void destroy() {
+               try {
+                       serverConnector.close();
+                       server.stop();
+                       // TODO delete temp dir
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public Integer getEffectivePort() {
+               return serverConnector.getLocalPort();
+       }
+
+       public void waitFor() throws InterruptedException {
+               server.join();
+       }
+
+       public static void main(String[] args) throws Exception {
+               RwtRunner rwtRunner = new RwtRunner() {
+
+                       @Override
+                       protected Control createUi(Composite parent, Object context) {
+                               Label label = new Label(parent, SWT.NONE);
+                               label.setText("Hello world!");
+                               return label;
+                       }
+               };
+               rwtRunner.init();
+               Runtime.getRuntime().addShutdownHook(new Thread(() -> rwtRunner.destroy(), "Jetty shutdown"));
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("App available in " + jvmUptime + " ms, on port " + rwtRunner.getEffectivePort());
+
+               // open browser in app mode
+               Thread.sleep(2000);// wait for RWT to be ready
+               Runtime.getRuntime().exec("google-chrome --app=http://localhost:" + rwtRunner.getEffectivePort() + "/app");
+
+               rwtRunner.waitFor();
+       }
+}
diff --git a/rap/org.argeo.swt.specific.rap/.classpath b/rap/org.argeo.swt.specific.rap/.classpath
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/rap/org.argeo.swt.specific.rap/.project b/rap/org.argeo.swt.specific.rap/.project
new file mode 100644 (file)
index 0000000..53d7976
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.specific.rap</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rap/org.argeo.swt.specific.rap/META-INF/.gitignore b/rap/org.argeo.swt.specific.rap/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rap/org.argeo.swt.specific.rap/bnd.bnd b/rap/org.argeo.swt.specific.rap/bnd.bnd
new file mode 100644 (file)
index 0000000..bcd9b19
--- /dev/null
@@ -0,0 +1,5 @@
+Import-Package: org.eclipse.swt,\
+org.eclipse.jface.dialogs,\
+org.eclipse.swt.events,\
+javax.servlet.http;version="[3,5)",\
+*
diff --git a/rap/org.argeo.swt.specific.rap/build.properties b/rap/org.argeo.swt.specific.rap/build.properties
new file mode 100644 (file)
index 0000000..fd806ca
--- /dev/null
@@ -0,0 +1,2 @@
+source.. = src/
+output.. = bin/
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
new file mode 100644 (file)
index 0000000..6100c1a
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+       private static final long serialVersionUID = -7540791204102318801L;
+
+       public CmsFileDialog(Shell parent, int style) {
+               super(parent, style);
+       }
+
+       public CmsFileDialog(Shell parent) {
+               super(parent);
+       }
+
+}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
new file mode 100644 (file)
index 0000000..3f30bde
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+       private static final long serialVersionUID = 8027963992680980549L;
+
+       public CmsFileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public void setText(String text) {
+               super.setText(text);
+       }
+
+       @Override
+       public String getFileName() {
+               return super.getFileName();
+       }
+
+       @Override
+       public String[] getFileNames() {
+               return super.getFileNames();
+       }
+
+       @Override
+       public void addSelectionListener(SelectionListener listener) {
+               super.addSelectionListener(listener);
+       }
+
+}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
new file mode 100644 (file)
index 0000000..a89b921
--- /dev/null
@@ -0,0 +1,39 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.AbstractTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+
+       public static void setStyleData(Widget widget, Object data) {
+               widget.setData(RWT.CUSTOM_VARIANT, data);
+       }
+
+       public static Object getStyleData(Widget widget) {
+               return widget.getData(RWT.CUSTOM_VARIANT);
+       }
+
+       public static void setMarkupData(Widget widget) {
+               widget.setData(RWT.MARKUP_ENABLED, true);
+       }
+
+       public static void setMarkupValidationDisabledData(Widget widget) {
+               widget.setData("org.eclipse.rap.rwt.markupValidationDisabled", Boolean.TRUE);
+       }
+
+       /**
+        * TootlTip support is supported only for {@link AbstractTableViewer} in RAP
+        */
+       public static void enableToolTipSupport(Viewer viewer) {
+               if (viewer instanceof ColumnViewer)
+                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+       }
+
+       private EclipseUiSpecificUtils() {
+       }
+}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
new file mode 100644 (file)
index 0000000..f9ca816
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.rap.fileupload.FileDetails;
+import org.eclipse.rap.fileupload.FileUploadHandler;
+import org.eclipse.rap.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.ClientFile;
+import org.eclipse.rap.rwt.client.service.ClientFileUploader;
+import org.eclipse.rap.rwt.dnd.ClientFileTransfer;
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+/** Configures a {@link Control} to receive files drop from the client OS. */
+public class FileDropAdapter {
+
+       public void prepareDropTarget(Control control, DropTarget dropTarget) {
+               dropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
+               dropTarget.addDropListener(new DropTargetAdapter() {
+                       private static final long serialVersionUID = 5361645765549463168L;
+
+                       @Override
+                       public void dropAccept(DropTargetEvent event) {
+                               if (!ClientFileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+                                       event.detail = DND.DROP_NONE;
+                               }
+                       }
+
+                       @Override
+                       public void drop(DropTargetEvent event) {
+                               handleFileDrop(control, event);
+                       }
+               });
+       }
+
+       public void handleFileDrop(Control control, DropTargetEvent event) {
+               ClientFile[] clientFiles = (ClientFile[]) event.data;
+               ClientFileUploader service = RWT.getClient().getService(ClientFileUploader.class);
+//             DiskFileUploadReceiver receiver = new DiskFileUploadReceiver();
+               FileUploadReceiver receiver = new FileUploadReceiver() {
+
+                       @Override
+                       public void receive(InputStream stream, FileDetails details) throws IOException {
+                               control.getDisplay().syncExec(() -> {
+                                       try {
+                                               processUpload(stream, details.getFileName(), details.getContentType());
+                                       } catch (IOException e) {
+                                               throw new IllegalStateException("Cannot process upload of " + details.getFileName(), e);
+                                       }
+                               });
+                       }
+               };
+               FileUploadHandler handler = new FileUploadHandler(receiver);
+//                 handler.setMaxFileSize( sizeLimit );
+//                 handler.setUploadTimeLimit( timeLimit );
+               service.submit(handler.getUploadUrl(), clientFiles);
+//             for (File file : receiver.getTargetFiles()) {
+//                     paths.add(file.toPath());
+//             }
+       }
+
+       /** Executed in UI thread */
+       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+       }
+
+}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/UiContext.java
new file mode 100644 (file)
index 0000000..72e17a2
--- /dev/null
@@ -0,0 +1,59 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+       /** Can be null, thus indicating that we are not in a web context. */
+       public static HttpServletRequest getHttpRequest() {
+               return RWT.getRequest();
+       }
+
+       public static HttpServletResponse getHttpResponse() {
+               return RWT.getResponse();
+       }
+
+       public static Locale getLocale() {
+               if (Display.getCurrent() != null)
+                       return RWT.getUISession().getLocale();
+               else
+                       return Locale.getDefault();
+       }
+
+       public static void setLocale(Locale locale) {
+               if (Display.getCurrent() != null)
+                       RWT.getUISession().setLocale(locale);
+               else
+                       Locale.setDefault(locale);
+       }
+
+       /** Can always be null */
+       @SuppressWarnings("unchecked")
+       public static <T> T getData(String key) {
+               Display display = getDisplay();
+               if (display == null)
+                       return null;
+               return (T) display.getData(key);
+       }
+
+       public static void setData(String key, Object value) {
+               Display display = getDisplay();
+               if (display == null)
+                       throw new IllegalStateException("Not display available");
+               display.setData(key, value);
+       }
+
+       private static Display getDisplay() {
+               return Display.getCurrent();
+       }
+
+       private UiContext() {
+       }
+
+}
diff --git a/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java b/rap/org.argeo.swt.specific.rap/src/org/argeo/eclipse/ui/specific/package-info.java
new file mode 100644 (file)
index 0000000..4ec451f
--- /dev/null
@@ -0,0 +1,2 @@
+/** Eclipse RAP-specific SWT/JFace utilities, to simplify single-sourcing. */
+package org.argeo.eclipse.ui.specific;
\ No newline at end of file
diff --git a/rcp/org.argeo.cms.e4.rcp/.classpath b/rcp/org.argeo.cms.e4.rcp/.classpath
new file mode 100644 (file)
index 0000000..eca7bdb
--- /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-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/rcp/org.argeo.cms.e4.rcp/.gitignore b/rcp/org.argeo.cms.e4.rcp/.gitignore
new file mode 100644 (file)
index 0000000..710cd68
--- /dev/null
@@ -0,0 +1,3 @@
+/bin/
+/target/
+/exec
diff --git a/rcp/org.argeo.cms.e4.rcp/.project b/rcp/org.argeo.cms.e4.rcp/.project
new file mode 100644 (file)
index 0000000..64d5619
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.e4.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..0c68a61
--- /dev/null
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..f29e940
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
diff --git a/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi
new file mode 100644 (file)
index 0000000..5b250ee
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="ASCII"?>
+<application:Application xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:advanced="http://www.eclipse.org/ui/2010/UIModel/application/ui/advanced" xmlns:application="http://www.eclipse.org/ui/2010/UIModel/application" xmlns:basic="http://www.eclipse.org/ui/2010/UIModel/application/ui/basic" xmi:id="_c4iAgCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.application">
+  <children xsi:type="basic:TrimmedWindow" xmi:id="_hSGBwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.trimmedwindow.argeocompanion" label="Argeo Companion">
+    <children xsi:type="advanced:PerspectiveStack" xmi:id="_nxzQICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspectivestack.0">
+      <children xsi:type="advanced:Perspective" xmi:id="_oI_oICnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.perspective.cmsadmin" label="CMS Admin">
+        <children xsi:type="basic:PartSashContainer" xmi:id="_qc16ECnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partsashcontainer.0" horizontal="true">
+          <children xsi:type="basic:PartStack" xmi:id="_RE87kDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.partstack.1">
+            <children xsi:type="basic:Part" xmi:id="_V1WvgDsXEeiUntFYWh-hFg" elementId="org.argeo.cms.e4.rcp.part.files" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.files.NodeFsBrowserView" label="Files"/>
+            <children xsi:type="basic:Part" xmi:id="_vOqDQCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.part.jcr" containerData="4000" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrBrowserView" label="JCR"/>
+          </children>
+          <children xsi:type="basic:PartStack" xmi:id="_0eRiwCnCEei1F8sdBz8Mpw" elementId="org.argeo.cms.e4.rcp.partstack.0" containerData="6000">
+            <tags>editorArea</tags>
+          </children>
+        </children>
+      </children>
+    </children>
+  </children>
+  <descriptors xmi:id="__9SDsC8JEeq0koquN4xGyg" elementId="org.argeo.cms.e4.partdescriptor.nodeEditor" allowMultiple="true" category="editorArea" closeable="true" contributionURI="bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.jcr.JcrNodeEditor"/>
+  <addons xmi:id="_c4iAgSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.core.commands.service" contributionURI="bundleclass://org.eclipse.e4.core.commands/org.eclipse.e4.core.commands.CommandServiceAddon"/>
+  <addons xmi:id="_c4iAginCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.contexts.service" contributionURI="bundleclass://org.eclipse.e4.ui.services/org.eclipse.e4.ui.services.ContextServiceAddon"/>
+  <addons xmi:id="_c4iAgynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.bindings.service" contributionURI="bundleclass://org.eclipse.e4.ui.bindings/org.eclipse.e4.ui.bindings.BindingServiceAddon"/>
+  <addons xmi:id="_c4iAhCnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.commands.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.CommandProcessingAddon"/>
+  <addons xmi:id="_c4iAhSnCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.contexts.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.ContextProcessingAddon"/>
+  <addons xmi:id="_c4iAhinCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.bindings.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench.swt/org.eclipse.e4.ui.workbench.swt.util.BindingProcessingAddon"/>
+  <addons xmi:id="_c4iAhynCEei1F8sdBz8Mpw" elementId="org.eclipse.e4.ui.workbench.handler.model" contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.addons.HandlerProcessingAddon"/>
+</application:Application>
diff --git a/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties
new file mode 100644 (file)
index 0000000..0a0da75
--- /dev/null
@@ -0,0 +1,23 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms,\
+org.argeo.cms.jcr,\
+
+applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi
+lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle
+clearPersistedState=true
+#argeo.cms.desktop.inTray=true
+
+# Remote node:
+#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node
+
+# Logging
+log.org.argeo=DEBUG
+
+argeo.node.useradmin.uris=os:///
+eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application
diff --git a/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/rcp/org.argeo.cms.e4.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..ff79c80
--- /dev/null
@@ -0,0 +1,7 @@
+Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true
+
+Require-Bundle: org.eclipse.core.runtime
+
+Import-Package: !org.eclipse.core.runtime,\
+org.eclipse.swt,\
+*
diff --git a/rcp/org.argeo.cms.e4.rcp/build.properties b/rcp/org.argeo.cms.e4.rcp/build.properties
new file mode 100644 (file)
index 0000000..355413e
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               argeo-companion.e4xmi
+source.. = src/
diff --git a/rcp/org.argeo.cms.e4.rcp/log4j.properties b/rcp/org.argeo.cms.e4.rcp/log4j.properties
new file mode 100644 (file)
index 0000000..13f949f
--- /dev/null
@@ -0,0 +1,32 @@
+log4j.rootLogger=WARN, development
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN
+log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO
+
+#log4j.logger.org.springframework.security=DEBUG
+#log4j.logger.org.apache.commons.exec=DEBUG
+#log4j.logger.org.apache.jackrabbit.webdav=DEBUG
+#log4j.logger.org.apache.jackrabbit.remote=DEBUG
+#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG
+
+log4j.logger.org.apache.catalina=INFO
+log4j.logger.org.apache.coyote=INFO
+
+log4j.logger.org.apache.directory=INFO
+log4j.logger.org.apache.directory.server=ERROR
+log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n
+
+# development appender (slow!)
+log4j.appender.development=org.apache.log4j.ConsoleAppender
+log4j.appender.development.layout=org.apache.log4j.PatternLayout
+log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n
diff --git a/rcp/org.argeo.cms.e4.rcp/plugin.xml b/rcp/org.argeo.cms.e4.rcp/plugin.xml
new file mode 100644 (file)
index 0000000..3e6896b
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+   <extension
+         id="CmsE4Application"
+         name="CMS E4 Application"
+         point="org.eclipse.core.runtime.applications">
+      <application
+            cardinality="singleton-global"
+            thread="main"
+            visible="true">
+         <run class="org.argeo.cms.e4.rcp.CmsE4Application"></run>
+      </application>
+   </extension>
+</plugin>
diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java
new file mode 100644 (file)
index 0000000..a708af1
--- /dev/null
@@ -0,0 +1,207 @@
+package org.argeo.cms.e4.rcp;
+
+import java.security.PrivilegedExceptionAction;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.UxContext;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.SimpleSwtUxContext;
+import org.argeo.cms.swt.auth.CmsLoginShell;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtension;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.equinox.app.IApplication;
+import org.eclipse.equinox.app.IApplicationContext;
+import org.eclipse.swt.widgets.Display;
+
+public class CmsE4Application implements IApplication, CmsView {
+       private LoginContext loginContext;
+       private IApplication e4Application;
+       private UxContext uxContext;
+       private String uid;
+
+       @Override
+       public Object start(IApplicationContext context) throws Exception {
+               // TODO wait for CMS to be ready
+               Thread.sleep(5000);
+
+               uid = UUID.randomUUID().toString();
+               Subject subject = new Subject();
+               Display display = createDisplay();
+               CmsLoginShell loginShell = new CmsLoginShell(this, null);
+               // TODO customize CmsLoginShell to be smaller and centered
+               loginShell.setSubject(subject);
+               try {
+                       // try pre-auth
+                       loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell);
+                       loginContext.login();
+               } catch (LoginException e) {
+                       e.printStackTrace();
+                       loginShell.createUi();
+                       loginShell.open();
+
+                       while (!loginShell.getShell().isDisposed()) {
+                               if (!display.readAndDispatch())
+                                       display.sleep();
+                       }
+               }
+               if (CurrentUser.getUsername(getSubject()) == null)
+                       throw new CmsException("Cannot log in");
+
+               // try {
+               // CallbackHandler callbackHandler = new DefaultLoginDialog(
+               // display.getActiveShell());
+               // loginContext = new LoginContext(
+               // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject,
+               // callbackHandler);
+               // } catch (LoginException e1) {
+               // throw new CmsException("Cannot initialize login context", e1);
+               // }
+               //
+               // // login
+               // try {
+               // loginContext.login();
+               // subject = loginContext.getSubject();
+               // } catch (LoginException e) {
+               // e.printStackTrace();
+               // display.dispose();
+               // try {
+               // Thread.sleep(2000);
+               // } catch (InterruptedException e1) {
+               // // silent
+               // }
+               // return null;
+               // }
+
+               uxContext = new SimpleSwtUxContext();
+               // UiContext.setData(CmsView.KEY, this);
+               CmsSwtUtils.registerCmsView(loginShell.getShell(), this);
+               e4Application = getApplication(null);
+               Object res = Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
+
+                       @Override
+                       public Object run() throws Exception {
+                               return e4Application.start(context);
+                       }
+
+               });
+               return res;
+       }
+
+       @Override
+       public void stop() {
+               if (e4Application != null)
+                       e4Application.stop();
+       }
+
+       static IApplication getApplication(String[] args) {
+               IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME,
+                               Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application");
+               try {
+                       IConfigurationElement[] elements = extension.getConfigurationElements();
+                       if (elements.length > 0) {
+                               IConfigurationElement[] runs = elements[0].getChildren("run");
+                               if (runs.length > 0) {
+                                       Object runnable;
+                                       runnable = runs[0].createExecutableExtension("class");
+                                       if (runnable instanceof IApplication)
+                                               return (IApplication) runnable;
+                               }
+                       }
+               } catch (Exception e) {
+                       throw new IllegalStateException("Cannot find e4 application", e);
+               }
+               throw new IllegalStateException("Cannot find e4 application");
+       }
+
+       public static Display createDisplay() {
+               Display.setAppName("Argeo CMS RCP");
+
+               // create the display
+               Display newDisplay = Display.getCurrent();
+               if (newDisplay == null) {
+                       newDisplay = new Display();
+               }
+               // Set the priority higher than normal so as to be higher
+               // than the JobManager.
+               Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1));
+               return newDisplay;
+       }
+
+       //
+       // CMS VIEW
+       //
+
+       @Override
+       public UxContext getUxContext() {
+               return uxContext;
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+               if (loginContext == null)
+                       throw new CmsException("Login context cannot be null");
+               // logout previous login context
+               // if (this.loginContext != null)
+               // try {
+               // this.loginContext.logout();
+               // } catch (LoginException e1) {
+               // System.err.println("Could not log out: " + e1);
+               // }
+               this.loginContext = loginContext;
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext == null)
+                       throw new CmsException("Login context should not bet null");
+               try {
+                       CurrentUser.logoutCmsSession(loginContext.getSubject());
+                       loginContext.logout();
+               } catch (LoginException e) {
+                       throw new CmsException("Cannot log out", e);
+               }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               // TODO Auto-generated method stub
+
+       }
+
+       @Override
+       public CmsImageManager getImageManager() {
+               // TODO Auto-generated method stub
+               return null;
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return CurrentUser.isAnonymous(getSubject());
+       }
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+}
diff --git a/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java
new file mode 100644 (file)
index 0000000..1d38fe7
--- /dev/null
@@ -0,0 +1,27 @@
+package org.argeo.cms.e4.rcp;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate;
+import org.eclipse.e4.ui.workbench.lifecycle.PreSave;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions;
+import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals;
+
+@SuppressWarnings("restriction")
+public class CmsRcpLifeCycle {
+
+       @PostContextCreate
+       void postContextCreate(IEclipseContext workbenchContext) {
+       }
+
+       @PreSave
+       void preSave(IEclipseContext workbenchContext) {
+       }
+
+       @ProcessAdditions
+       void processAdditions(IEclipseContext workbenchContext) {
+       }
+
+       @ProcessRemovals
+       void processRemovals(IEclipseContext workbenchContext) {
+       }
+}
diff --git a/rcp/org.argeo.cms.ui.rcp/.classpath b/rcp/org.argeo.cms.ui.rcp/.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/rcp/org.argeo.cms.ui.rcp/.gitignore b/rcp/org.argeo.cms.ui.rcp/.gitignore
new file mode 100644 (file)
index 0000000..09e3bc9
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/target/
diff --git a/rcp/org.argeo.cms.ui.rcp/.project b/rcp/org.argeo.cms.ui.rcp/.project
new file mode 100644 (file)
index 0000000..c9c2a44
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.cms.ui.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ds.core.builder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore b/rcp/org.argeo.cms.ui.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml b/rcp/org.argeo.cms.ui.rcp/OSGI-INF/cmsRcpApp.xml
new file mode 100644 (file)
index 0000000..6da9ae8
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="CMS RCP App">
+   <implementation class="org.argeo.cms.ui.rcp.CmsRcpApp"/>
+   <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic"/>
+   <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
+</scr:component>
diff --git a/rcp/org.argeo.cms.ui.rcp/bnd.bnd b/rcp/org.argeo.cms.ui.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..72b00d2
--- /dev/null
@@ -0,0 +1,9 @@
+
+Service-Component: OSGI-INF/cmsRcpApp.xml
+
+Import-Package:\
+org.argeo.cms.auth,\
+org.eclipse.swt,\
+org.eclipse.swt.graphics,\
+org.w3c.css.sac,\
+*
diff --git a/rcp/org.argeo.cms.ui.rcp/build.properties b/rcp/org.argeo.cms.ui.rcp/build.properties
new file mode 100644 (file)
index 0000000..6210e84
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               OSGI-INF/
+source.. = src/
diff --git a/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java b/rcp/org.argeo.cms.ui.rcp/src/org/argeo/cms/ui/rcp/CmsRcpApp.java
new file mode 100644 (file)
index 0000000..8614d70
--- /dev/null
@@ -0,0 +1,276 @@
+package org.argeo.cms.ui.rcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsImageManager;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.CmsTheme;
+import org.argeo.api.cms.CmsUi;
+import org.argeo.api.cms.CmsView;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.UxContext;
+import org.argeo.cms.osgi.CmsOsgiUtils;
+import org.argeo.cms.swt.CmsSwtUtils;
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
+import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+/** Runs a {@link CmsApp} as an SWT desktop application. */
+@SuppressWarnings("restriction")
+public class CmsRcpApp implements CmsView {
+       private final static CmsLog log = CmsLog.getLog(CmsRcpApp.class);
+
+       private BundleContext bundleContext = FrameworkUtil.getBundle(CmsRcpApp.class).getBundleContext();
+
+       private Display display;
+       private Shell shell;
+       private CmsApp cmsApp;
+       private CmsUiThread uiThread;
+
+       // CMS View
+       private String uid;
+       private LoginContext loginContext;
+
+       private EventAdmin eventAdmin;
+
+       private CSSEngine cssEngine;
+
+       private CmsUi ui;
+       // TODO make it configurable
+       private String uiName = "desktop";
+
+       public CmsRcpApp() {
+               uid = UUID.randomUUID().toString();
+       }
+
+       public void init(Map<String, String> properties) {
+               try {
+                       Thread.sleep(5000);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+               uiThread = new CmsUiThread();
+               uiThread.start();
+
+       }
+
+       public void destroy(Map<String, String> properties) {
+               if (!shell.isDisposed())
+                       shell.dispose();
+               try {
+                       uiThread.join();
+               } catch (InterruptedException e) {
+                       // silent
+               } finally {
+                       uiThread = null;
+               }
+       }
+
+       class CmsUiThread extends Thread {
+
+               public CmsUiThread() {
+                       super("CMS UI");
+               }
+
+               @Override
+               public void run() {
+                       display = new Display();
+                       shell = new Shell(display);
+                       shell.setText("Argeo CMS");
+                       Composite parent = shell;
+                       parent.setLayout(new GridLayout());
+                       CmsSwtUtils.registerCmsView(shell, CmsRcpApp.this);
+
+//                     Subject subject = new Subject();
+//                     CmsLoginShell loginShell = new CmsLoginShell(CmsRcpApp.this);
+//                     loginShell.setSubject(subject);
+                       try {
+                               // try pre-auth
+//                             loginContext = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject, loginShell);
+                               loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER);
+                               loginContext.login();
+                       } catch (LoginException e) {
+                               throw new IllegalStateException("Could not log in.", e);
+//                             loginShell.createUi();
+//                             loginShell.open();
+//
+//                             while (!loginShell.getShell().isDisposed()) {
+//                                     if (!display.readAndDispatch())
+//                                             display.sleep();
+//                             }
+                       }
+                       if (log.isDebugEnabled())
+                               log.debug("Logged in to desktop: " + loginContext.getSubject());
+
+                       Subject.doAs(loginContext.getSubject(), (PrivilegedAction<Void>) () -> {
+
+                               // TODO factorise with web app
+                               parent.setData(CmsApp.UI_NAME_PROPERTY, uiName);
+                               ui = cmsApp.initUi(parent);
+                               if (ui instanceof Composite)
+                                       ((Composite) ui).setLayoutData(CmsSwtUtils.fillAll());
+                               // ui.setLayoutData(CmsUiUtils.fillAll());
+                               // we need ui to be set before refresh so that CmsView can store UI context data
+                               // in it.
+                               cmsApp.refreshUi(ui, null);
+
+                               // Styling
+                               CmsTheme theme = CmsSwtUtils.getCmsTheme(parent);
+                               if (theme != null) {
+                                       cssEngine = new CSSSWTEngineImpl(display);
+                                       for (String path : theme.getSwtCssPaths()) {
+                                               try (InputStream in = theme.loadPath(path)) {
+                                                       cssEngine.parseStyleSheet(in);
+                                               } catch (IOException e) {
+                                                       throw new IllegalStateException("Cannot load stylesheet " + path, e);
+                                               }
+                                       }
+                                       cssEngine.setErrorHandler(new CSSErrorHandler() {
+                                               public void error(Exception e) {
+                                                       log.error("SWT styling error: ", e);
+                                               }
+                                       });
+                                       applyStyles(shell);
+                               }
+                               shell.layout(true, true);
+
+                               shell.open();
+                               while (!shell.isDisposed()) {
+                                       if (!display.readAndDispatch())
+                                               display.sleep();
+                               }
+                               display.dispose();
+                               return null;
+                       });
+               }
+
+       }
+
+       /*
+        * CMS VIEW
+        */
+
+       @Override
+       public String getUid() {
+               return uid;
+       }
+
+       @Override
+       public UxContext getUxContext() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void navigateTo(String state) {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public void authChange(LoginContext loginContext) {
+       }
+
+       @Override
+       public void logout() {
+               if (loginContext != null)
+                       try {
+                               loginContext.logout();
+                       } catch (LoginException e) {
+                               log.error("Cannot log out", e);
+                       }
+       }
+
+       @Override
+       public void exception(Throwable e) {
+               log.error("Unexpected exception in CMS RCP", e);
+       }
+
+       @Override
+       public CmsImageManager getImageManager() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public CmsSession getCmsSession() {
+               CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bundleContext, getSubject());
+               return cmsSession;
+       }
+
+       @Override
+       public Object getData(String key) {
+               if (ui != null) {
+                       return ui.getData(key);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+       @Override
+       public void setData(String key, Object value) {
+               if (ui != null) {
+                       ui.setData(key, value);
+               } else {
+                       throw new IllegalStateException("UI is not initialized");
+               }
+       }
+
+       @Override
+       public boolean isAnonymous() {
+               return false;
+       }
+
+       @Override
+       public void applyStyles(Object node) {
+               if (cssEngine != null)
+                       cssEngine.applyStyles(node, true);
+       }
+
+       @Override
+       public void sendEvent(String topic, Map<String, Object> properties) {
+               if (properties == null)
+                       properties = new HashMap<>();
+               if (properties.containsKey(CMS_VIEW_UID_PROPERTY) && !properties.get(CMS_VIEW_UID_PROPERTY).equals(uid))
+                       throw new IllegalArgumentException("Property " + CMS_VIEW_UID_PROPERTY + " is set to another CMS view uid ("
+                                       + properties.get(CMS_VIEW_UID_PROPERTY) + ") then " + uid);
+               properties.put(CMS_VIEW_UID_PROPERTY, uid);
+               eventAdmin.sendEvent(new Event(topic, properties));
+       }
+
+       public <T> T doAs(PrivilegedAction<T> action) {
+               return Subject.doAs(getSubject(), action);
+       }
+
+       protected Subject getSubject() {
+               return loginContext.getSubject();
+       }
+
+       /*
+        * DEPENDENCY INJECTION
+        */
+       public void setCmsApp(CmsApp cmsApp) {
+               this.cmsApp = cmsApp;
+       }
+
+       public void setEventAdmin(EventAdmin eventAdmin) {
+               this.eventAdmin = eventAdmin;
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/.classpath b/rcp/org.argeo.swt.minidesktop/.classpath
new file mode 100644 (file)
index 0000000..eca7bdb
--- /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-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/rcp/org.argeo.swt.minidesktop/.gitignore b/rcp/org.argeo.swt.minidesktop/.gitignore
new file mode 100644 (file)
index 0000000..97adb72
--- /dev/null
@@ -0,0 +1,3 @@
+/bin/
+/target/
+*.log
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.minidesktop/.project b/rcp/org.argeo.swt.minidesktop/.project
new file mode 100644 (file)
index 0000000..b6c2c1a
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.minidesktop</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore b/rcp/org.argeo.swt.minidesktop/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rcp/org.argeo.swt.minidesktop/bnd.bnd b/rcp/org.argeo.swt.minidesktop/bnd.bnd
new file mode 100644 (file)
index 0000000..f3c13be
--- /dev/null
@@ -0,0 +1,2 @@
+Import-Package: org.eclipse.swt,\
+*
diff --git a/rcp/org.argeo.swt.minidesktop/build.properties b/rcp/org.argeo.swt.minidesktop/build.properties
new file mode 100644 (file)
index 0000000..34d2e4d
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniBrowser.java
new file mode 100644 (file)
index 0000000..406382b
--- /dev/null
@@ -0,0 +1,187 @@
+package org.argeo.minidesktop;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.browser.LocationAdapter;
+import org.eclipse.swt.browser.LocationEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** A very minimalistic web browser based on {@link Browser}. */
+public class MiniBrowser {
+       private static Point defaultShellSize = new Point(800, 480);
+
+       private Browser browser;
+       private Text addressT;
+
+       private final boolean fullscreen;
+       private final boolean appMode;
+
+       public MiniBrowser(Composite composite, String url, boolean fullscreen, boolean appMode) {
+               this.fullscreen = fullscreen;
+               this.appMode = appMode;
+               createUi(composite);
+               setUrl(url);
+       }
+
+       public Control createUi(Composite parent) {
+               parent.setLayout(noSpaceGridLayout(new GridLayout()));
+               if (!isAppMode()) {
+                       Control toolBar = createToolBar(parent);
+                       toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               }
+               Control body = createBody(parent);
+               body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               return body;
+       }
+
+       protected Control createToolBar(Composite parent) {
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayout(new FillLayout());
+               addressT = new Text(toolBar, SWT.SINGLE);
+               addressT.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               setUrl(addressT.getText().trim());
+                       }
+               });
+               return toolBar;
+       }
+
+       protected Control createBody(Composite parent) {
+               browser = new Browser(parent, SWT.NONE);
+               if (isFullScreen())
+                       browser.addKeyListener(new KeyAdapter() {
+                               @Override
+                               public void keyPressed(KeyEvent e) {
+                                       if (e.keyCode == 0x77 && e.stateMask == 0x40000) {// Ctrl+W
+                                               browser.getShell().dispose();
+                                       }
+                               }
+                       });
+               browser.addLocationListener(new LocationAdapter() {
+                       @Override
+                       public void changed(LocationEvent event) {
+                               System.out.println(event);
+                               if (addressT != null)
+                                       addressT.setText(event.location);
+                       }
+
+               });
+               browser.addTitleListener(e -> titleChanged(e.title));
+               browser.addOpenWindowListener((e) -> {
+                       e.browser = openNewBrowserWindow();
+               });
+               return browser;
+       }
+
+       protected Browser openNewBrowserWindow() {
+
+               if (isFullScreen()) {
+                       // TODO manage multiple tabs?
+                       return browser;
+               } else {
+                       Shell newShell = new Shell(browser.getDisplay(), SWT.SHELL_TRIM);
+                       MiniBrowser newMiniBrowser = new MiniBrowser(newShell, null, false, isAppMode());
+                       newShell.setSize(defaultShellSize);
+                       newShell.open();
+                       return newMiniBrowser.browser;
+               }
+       }
+
+       protected boolean isFullScreen() {
+               return fullscreen;
+       }
+
+       void setUrl(String url) {
+               if (browser != null && url != null && !url.equals(browser.getUrl()))
+                       browser.setUrl(url.toString());
+       }
+
+       /** Called when URL changed; to be overridden, does nothing by default. */
+       protected void urlChanged(String url) {
+       }
+
+       /** Called when title changed; to be overridden, does nothing by default. */
+       protected void titleChanged(String title) {
+       }
+
+       protected Browser getBrowser() {
+               return browser;
+       }
+
+       protected boolean isAppMode() {
+               return appMode;
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               List<String> options = Arrays.asList(args);
+               if (options.contains("--help")) {
+                       System.out.println("Usage: java " + MiniBrowser.class.getName().replace('.', '/') + " [OPTION] [URL]");
+                       System.out.println("A minimalistic web browser Eclipse SWT Browser integration.");
+                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
+                       System.out.println("  --app        : open without an address bar and a toolbar");
+                       System.out.println("  --help       : print this help and exit");
+                       System.exit(1);
+               }
+               boolean fullscreen = options.contains("--fullscreen");
+               boolean appMode = options.contains("--app");
+               String url = "https://start.duckduckgo.com/";
+               if (options.size() > 0) {
+                       String last = options.get(options.size() - 1);
+                       if (!last.startsWith("--"))
+                               url = last.trim();
+               }
+
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell;
+               if (fullscreen) {
+                       shell = new Shell(display, SWT.NO_TRIM);
+                       shell.setFullScreen(true);
+                       Rectangle bounds = display.getBounds();
+                       shell.setSize(bounds.width, bounds.height);
+               } else {
+                       shell = new Shell(display, SWT.SHELL_TRIM);
+                       shell.setSize(defaultShellSize);
+               }
+
+               new MiniBrowser(shell, url, fullscreen, appMode) {
+
+                       @Override
+                       protected void titleChanged(String title) {
+                               shell.setText(title);
+                       }
+               };
+               shell.open();
+
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopImages.java
new file mode 100644 (file)
index 0000000..db5e6f2
--- /dev/null
@@ -0,0 +1,53 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+/** Icons. */
+public class MiniDesktopImages {
+
+       public final Image homeIcon;
+       public final Image exitIcon;
+       
+       public final Image terminalIcon;
+       public final Image browserIcon;
+       public final Image explorerIcon;
+       public final Image textEditorIcon;
+
+       public final Image folderIcon;
+       public final Image fileIcon;
+
+       public MiniDesktopImages(Display display) {
+               homeIcon = loadImage(display, "nav_home@2x.png");
+               exitIcon = loadImage(display, "delete@2x.png");
+
+               terminalIcon = loadImage(display, "console_view@2x.png");
+               browserIcon = loadImage(display, "external_browser@2x.png");
+               explorerIcon = loadImage(display, "fldr_obj@2x.png");
+               textEditorIcon = loadImage(display, "cheatsheet_obj@2x.png");
+
+               folderIcon = loadImage(display, "fldr_obj@2x.png");
+               fileIcon = loadImage(display, "file_obj@2x.png");
+       }
+
+       static Image loadImage(Display display, String path) {
+               InputStream stream = MiniDesktopImages.class.getResourceAsStream(path);
+               if (stream == null)
+                       throw new IllegalArgumentException("Image " + path + " not found");
+               Image image = null;
+               try {
+                       image = new Image(display, stream);
+               } catch (SWTException ex) {
+               } finally {
+                       try {
+                               stream.close();
+                       } catch (IOException ex) {
+                       }
+               }
+               return image;
+       }
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniDesktopManager.java
new file mode 100644 (file)
index 0000000..e0f483d
--- /dev/null
@@ -0,0 +1,349 @@
+package org.argeo.minidesktop;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CTabFolder;
+import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.program.Program;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** A very minimalistic desktop manager based on Java and Eclipse SWT. */
+public class MiniDesktopManager {
+       private Display display;
+
+       private Shell rootShell;
+       private Shell toolBarShell;
+       private CTabFolder tabFolder;
+       private int maxTabTitleLength = 16;
+
+       private final boolean fullscreen;
+       private final boolean stacking;
+
+       private MiniDesktopImages images;
+
+       public MiniDesktopManager(boolean fullscreen, boolean stacking) {
+               this.fullscreen = fullscreen;
+               this.stacking = stacking;
+       }
+
+       public void init() {
+               Display.setAppName("Mini SWT Desktop");
+               display = Display.getCurrent();
+               if (display != null)
+                       throw new IllegalStateException("Already a display " + display);
+               display = new Display();
+
+               if (display.getTouchEnabled()) {
+                       System.out.println("Touch enabled.");
+               }
+
+               images = new MiniDesktopImages(display);
+
+               int toolBarSize = 48;
+
+               if (isFullscreen()) {
+                       rootShell = new Shell(display, SWT.NO_TRIM);
+                       rootShell.setFullScreen(true);
+                       Rectangle bounds = display.getBounds();
+                       rootShell.setLocation(0, 0);
+                       rootShell.setSize(bounds.width, bounds.height);
+               } else {
+                       rootShell = new Shell(display, SWT.CLOSE | SWT.RESIZE);
+                       Rectangle shellArea = rootShell.computeTrim(200, 200, 800, 480);
+                       rootShell.setSize(shellArea.width, shellArea.height);
+                       rootShell.setText(Display.getAppName());
+                       rootShell.setImage(images.terminalIcon);
+               }
+
+               rootShell.setLayout(noSpaceGridLayout(new GridLayout(2, false)));
+               Composite toolBarArea = new Composite(rootShell, SWT.NONE);
+               toolBarArea.setLayoutData(new GridData(toolBarSize, rootShell.getSize().y));
+
+               ToolBar toolBar;
+               if (isFullscreen()) {
+                       toolBarShell = new Shell(rootShell, SWT.NO_TRIM | SWT.ON_TOP);
+                       toolBar = new ToolBar(toolBarShell, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                       createDock(toolBar);
+                       toolBarShell.pack();
+                       toolBarArea.setLayoutData(new GridData(toolBar.getSize().x, toolBar.getSize().y));
+               } else {
+                       toolBar = new ToolBar(toolBarArea, SWT.VERTICAL | SWT.FLAT | SWT.BORDER);
+                       createDock(toolBar);
+                       toolBarArea.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
+               }
+
+               if (isStacking()) {
+                       tabFolder = new CTabFolder(rootShell, SWT.MULTI | SWT.BORDER | SWT.BOTTOM);
+                       tabFolder.setLayout(noSpaceGridLayout(new GridLayout()));
+                       tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+                       Color selectionBackground = display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+                       tabFolder.setSelectionBackground(selectionBackground);
+
+                       // background
+                       Control background = createBackground(tabFolder);
+                       CTabItem homeTabItem = new CTabItem(tabFolder, SWT.NONE);
+                       homeTabItem.setText("Home");
+                       homeTabItem.setImage(images.homeIcon);
+                       homeTabItem.setControl(background);
+                       tabFolder.setFocus();
+               } else {
+                       createBackground(rootShell);
+               }
+
+               rootShell.open();
+               // rootShell.layout(true, true);
+
+               if (toolBarShell != null) {
+                       int toolBarShellY = (display.getBounds().height - toolBar.getSize().y) / 2;
+                       toolBarShell.setLocation(0, toolBarShellY);
+                       toolBarShell.open();
+               }
+
+               long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime();
+               System.out.println("SWT Mini Desktop Manager available in " + jvmUptime + " ms.");
+       }
+
+       protected void createDock(ToolBar toolBar) {
+               // Terminal
+               addToolItem(toolBar, images.terminalIcon, "Terminal", () -> {
+                       String url = System.getProperty("user.home");
+                       AppContext appContext = createAppParent(images.terminalIcon);
+                       new MiniTerminal(appContext.getAppParent(), url) {
+
+                               @Override
+                               protected void exitCalled() {
+                                       if (appContext.shell != null)
+                                               appContext.shell.dispose();
+                                       if (appContext.tabItem != null)
+                                               appContext.tabItem.dispose();
+                               }
+                       };
+                       String title;
+                       try {
+                               title = System.getProperty("user.name") + "@" + InetAddress.getLocalHost().getHostName();
+                       } catch (UnknownHostException e) {
+                               title = System.getProperty("user.name") + "@localhost";
+                       }
+                       if (appContext.shell != null)
+                               appContext.shell.setText(title);
+                       if (appContext.tabItem != null) {
+                               appContext.tabItem.setText(tabTitle(title));
+                               appContext.tabItem.setToolTipText(title);
+                       }
+                       openApp(appContext);
+               });
+
+               // Web browser
+               addToolItem(toolBar, images.browserIcon, "Browser", () -> {
+                       String url = "https://start.duckduckgo.com/";
+                       AppContext appContext = createAppParent(images.browserIcon);
+                       new MiniBrowser(appContext.getAppParent(), url, false, false) {
+                               @Override
+                               protected void titleChanged(String title) {
+                                       if (appContext.shell != null)
+                                               appContext.shell.setText(title);
+                                       if (appContext.tabItem != null) {
+                                               appContext.tabItem.setText(tabTitle(title));
+                                               appContext.tabItem.setToolTipText(title);
+                                       }
+                               }
+                       };
+                       openApp(appContext);
+               });
+
+               // File explorer
+               addToolItem(toolBar, images.explorerIcon, "Explorer", () -> {
+                       String url = System.getProperty("user.home");
+                       AppContext appContext = createAppParent(images.explorerIcon);
+                       new MiniExplorer(appContext.getAppParent(), url) {
+
+                               @Override
+                               protected void pathChanged(Path path) {
+                                       if (appContext.shell != null)
+                                               appContext.shell.setText(path.toString());
+                                       if (appContext.tabItem != null) {
+                                               appContext.tabItem.setText(path.getFileName().toString());
+                                               appContext.tabItem.setToolTipText(path.toString());
+                                       }
+                               }
+                       };
+                       openApp(appContext);
+               });
+
+               // Separator
+               new ToolItem(toolBar, SWT.SEPARATOR);
+
+               // Exit
+               addToolItem(toolBar, images.exitIcon, "Exit", () -> rootShell.dispose());
+
+               toolBar.pack();
+       }
+
+       protected String tabTitle(String title) {
+               return title.length() > maxTabTitleLength ? title.substring(0, maxTabTitleLength) : title;
+       }
+
+       protected void addToolItem(ToolBar toolBar, Image icon, String name, Runnable action) {
+               ToolItem searchI = new ToolItem(toolBar, SWT.PUSH);
+               searchI.setImage(icon);
+               searchI.setToolTipText(name);
+               searchI.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               action.run();
+                       }
+
+               });
+       }
+
+       protected AppContext createAppParent(Image icon) {
+               if (isStacking()) {
+                       Composite appParent = new Composite(tabFolder, SWT.CLOSE);
+                       appParent.setLayout(noSpaceGridLayout(new GridLayout()));
+                       CTabItem item = new CTabItem(tabFolder, SWT.CLOSE);
+                       item.setImage(icon);
+                       item.setControl(appParent);
+                       return new AppContext(item);
+               } else {
+                       Shell shell = isFullscreen() ? new Shell(rootShell, SWT.SHELL_TRIM)
+                                       : new Shell(rootShell.getDisplay(), SWT.SHELL_TRIM);
+                       shell.setImage(icon);
+                       return new AppContext(shell);
+               }
+       }
+
+       protected void openApp(AppContext appContext) {
+               if (appContext.shell != null) {
+                       Shell shell = (Shell) appContext.shell;
+                       shell.open();
+                       shell.setSize(new Point(800, 480));
+               }
+               if (appContext.tabItem != null) {
+                       tabFolder.setFocus();
+                       tabFolder.setSelection(appContext.tabItem);
+               }
+       }
+
+       protected Control createBackground(Composite parent) {
+               Composite backgroundArea = new Composite(parent, SWT.NONE);
+               backgroundArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               initBackground(backgroundArea);
+               return backgroundArea;
+       }
+
+       protected void initBackground(Composite backgroundArea) {
+               MiniHomePart homePart = new MiniHomePart() {
+
+                       @Override
+                       protected void fillAppsToolBar(ToolBar toolBar) {
+                               createDock(toolBar);
+                       }
+               };
+               homePart.createUiPart(backgroundArea, null);
+       }
+
+       public void run() {
+               while (!rootShell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+       public void dispose() {
+               if (!rootShell.isDisposed())
+                       rootShell.dispose();
+       }
+
+       protected boolean isFullscreen() {
+               return fullscreen;
+       }
+
+       protected boolean isStacking() {
+               return stacking;
+       }
+
+       protected Image getIconForExt(String ext) {
+               Program program = Program.findProgram(ext);
+               if (program == null)
+                       return display.getSystemImage(SWT.ICON_INFORMATION);
+
+               ImageData iconData = program.getImageData();
+               if (iconData == null) {
+                       return display.getSystemImage(SWT.ICON_INFORMATION);
+               } else {
+                       return new Image(display, iconData);
+               }
+
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               List<String> options = Arrays.asList(args);
+               if (options.contains("--help")) {
+                       System.out.println("Usage: java " + MiniDesktopManager.class.getName().replace('.', '/') + " [OPTION]");
+                       System.out.println("A minimalistic desktop manager based on Java and Eclipse SWT.");
+                       System.out.println("  --fullscreen : take control of the whole screen (default is to run in a window)");
+                       System.out.println("  --stacking   : open apps as tabs (default is to create new windows)");
+                       System.out.println("  --help       : print this help and exit");
+                       System.exit(1);
+               }
+               boolean fullscreen = options.contains("--fullscreen");
+               boolean stacking = options.contains("--stacking");
+
+               MiniDesktopManager desktopManager = new MiniDesktopManager(fullscreen, stacking);
+               desktopManager.init();
+               desktopManager.run();
+               desktopManager.dispose();
+               System.exit(0);
+       }
+
+       class AppContext {
+               private Shell shell;
+               private CTabItem tabItem;
+
+               public AppContext(Shell shell) {
+                       this.shell = shell;
+               }
+
+               public AppContext(CTabItem tabItem) {
+                       this.tabItem = tabItem;
+               }
+
+               Composite getAppParent() {
+                       if (shell != null)
+                               return shell;
+                       if (tabItem != null)
+                               return (Composite) tabItem.getControl();
+                       throw new IllegalStateException();
+               }
+       }
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniExplorer.java
new file mode 100644 (file)
index 0000000..1395c02
--- /dev/null
@@ -0,0 +1,165 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.program.Program;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniExplorer {
+       private Path path;
+       private Text addressT;
+       private Table browser;
+
+       private boolean showHidden = false;
+
+       public MiniExplorer(Composite parent, String url) {
+               this(parent);
+               setUrl(url);
+       }
+
+       public MiniExplorer(Composite parent) {
+               parent.setLayout(noSpaceGridLayout(new GridLayout()));
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new FillLayout());
+               addressT = new Text(toolBar, SWT.SINGLE);
+               addressT.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetDefaultSelected(SelectionEvent e) {
+                               setUrl(addressT.getText().trim());
+                       }
+               });
+               browser = createTable(parent, this.path);
+
+       }
+
+       public void setPath(Path url) {
+               this.path = url;
+               if (addressT != null)
+                       addressT.setText(url.toString());
+               if (browser != null) {
+                       Composite parent = browser.getParent();
+                       browser.dispose();
+                       browser = createTable(parent, this.path);
+                       parent.layout(true, true);
+               }
+               pathChanged(url);
+       }
+
+       protected void pathChanged(Path path) {
+
+       }
+
+       protected Table createTable(Composite parent, Path path) {
+               Table table = new Table(parent, SWT.BORDER);
+               table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               table.addMouseListener(new MouseAdapter() {
+
+                       @Override
+                       public void mouseDoubleClick(MouseEvent e) {
+                               Point pt = new Point(e.x, e.y);
+                               TableItem item = table.getItem(pt);
+                               Path path = (Path) item.getData();
+                               if (Files.isDirectory(path)) {
+                                       setPath(path);
+                               } else {
+                                       Program.launch(path.toString());
+                               }
+                       }
+               });
+
+               if (path != null) {
+                       if (path.getParent() != null) {
+                               TableItem parentTI = new TableItem(table, SWT.NONE);
+                               parentTI.setText("..");
+                               parentTI.setData(path.getParent());
+                       }
+
+                       try {
+                               // directories
+                               DirectoryStream<Path> ds = Files.newDirectoryStream(path, p -> Files.isDirectory(p) && isShown(p));
+                               ds.forEach(p -> {
+                                       TableItem ti = new TableItem(table, SWT.NONE);
+                                       ti.setText(p.getFileName().toString() + "/");
+                                       ti.setData(p);
+                               });
+                               // files
+                               ds = Files.newDirectoryStream(path, p -> !Files.isDirectory(p) && isShown(p));
+                               ds.forEach(p -> {
+                                       TableItem ti = new TableItem(table, SWT.NONE);
+                                       ti.setText(p.getFileName().toString());
+                                       ti.setData(p);
+                               });
+                       } catch (IOException e1) {
+                               // TODO Auto-generated catch block
+                               e1.printStackTrace();
+                       }
+               }
+               return table;
+       }
+
+       protected boolean isShown(Path path) {
+               if (showHidden)
+                       return true;
+               try {
+                       return !Files.isHidden(path);
+               } catch (IOException e) {
+                       throw new IllegalArgumentException("Cannot check " + path, e);
+               }
+       }
+
+       public void setUrl(String url) {
+               setPath(Paths.get(url));
+       }
+
+       private static GridLayout noSpaceGridLayout(GridLayout layout) {
+               layout.horizontalSpacing = 0;
+               layout.verticalSpacing = 0;
+               layout.marginWidth = 0;
+               layout.marginHeight = 0;
+               return layout;
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+               new MiniExplorer(shell, url) {
+
+                       @Override
+                       protected void pathChanged(Path path) {
+                               shell.setText(path.toString());
+                       }
+
+               };
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniHomePart.java
new file mode 100644 (file)
index 0000000..877f643
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.minidesktop;
+
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Group;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.ToolBar;
+
+/** A start page displaying network information and resources. */
+public class MiniHomePart {
+
+       public Control createUiPart(Composite parent, Object context) {
+               parent.setLayout(new GridLayout(2, false));
+               Display display = parent.getDisplay();
+
+               // Apps
+               Group appsGroup = new Group(parent, SWT.NONE);
+               appsGroup.setText("Apps");
+               appsGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 2, 1));
+               ToolBar appsToolBar = new ToolBar(appsGroup, SWT.HORIZONTAL | SWT.FLAT);
+               fillAppsToolBar(appsToolBar);
+
+               // Host
+               Group hostGroup = new Group(parent, SWT.NONE);
+               hostGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
+               hostGroup.setText("Host");
+               hostGroup.setLayout(new GridLayout(2, false));
+               label(hostGroup, "Hostname: ");
+               try {
+                       InetAddress defaultAddr = InetAddress.getLocalHost();
+                       String hostname = defaultAddr.getHostName();
+                       label(hostGroup, hostname);
+                       label(hostGroup, "Address: ");
+                       label(hostGroup, defaultAddr.getHostAddress());
+               } catch (UnknownHostException e) {
+                       label(hostGroup, e.getMessage());
+               }
+
+               Enumeration<NetworkInterface> netInterfaces = null;
+               try {
+                       netInterfaces = NetworkInterface.getNetworkInterfaces();
+               } catch (SocketException e) {
+                       label(hostGroup, "Interfaces: ");
+                       label(hostGroup, e.getMessage());
+               }
+               if (netInterfaces != null)
+                       while (netInterfaces.hasMoreElements()) {
+                               NetworkInterface netInterface = netInterfaces.nextElement();
+                               byte[] hardwareAddress = null;
+                               try {
+                                       hardwareAddress = netInterface.getHardwareAddress();
+                                       if (hardwareAddress != null) {
+                                               label(hostGroup, convertHardwareAddress(hardwareAddress));
+                                               label(hostGroup, netInterface.getName());
+                                               for (InterfaceAddress addr : netInterface.getInterfaceAddresses()) {
+                                                       label(hostGroup, cleanHostAddress(addr.getAddress().getHostAddress()));
+                                                       label(hostGroup, Short.toString(addr.getNetworkPrefixLength()));
+                                               }
+                                       }
+                               } catch (SocketException e) {
+                                       label(hostGroup, e.getMessage());
+                               }
+                       }
+
+               // Resources
+               Group resGroup = new Group(parent, SWT.NONE);
+               resGroup.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
+               resGroup.setText("Resources");
+               resGroup.setLayout(new GridLayout(3, false));
+
+               Runtime runtime = Runtime.getRuntime();
+
+               String maxMemoryStr = Long.toString(runtime.maxMemory() / (1024 * 1024)) + " MB";
+               label(resGroup, "Max Java memory: ");
+               label(resGroup, maxMemoryStr);
+               label(resGroup, "Java version: " + Runtime.version().toString());
+
+               label(resGroup, "Usable Java memory: ");
+               Label totalMemory = label(resGroup, maxMemoryStr);
+               ProgressBar totalOnMax = new ProgressBar(resGroup, SWT.SMOOTH);
+               totalOnMax.setMaximum(100);
+               label(resGroup, "Used Java memory: ");
+               Label usedMemory = label(resGroup, maxMemoryStr);
+               ProgressBar usedOnTotal = new ProgressBar(resGroup, SWT.SMOOTH);
+               totalOnMax.setMaximum(100);
+               new Thread() {
+                       @Override
+                       public void run() {
+                               while (!totalOnMax.isDisposed()) {
+                                       display.asyncExec(() -> {
+                                               if (totalOnMax.isDisposed())
+                                                       return;
+                                               totalOnMax.setSelection(javaTotalOnMaxPerct(runtime));
+                                               usedOnTotal.setSelection(javaUsedOnTotalPerct(runtime));
+                                               totalMemory.setText(Long.toString(runtime.totalMemory() / (1024 * 1024)) + " MB");
+                                               usedMemory.setText(
+                                                               Long.toString((runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024)) + " MB");
+                                       });
+                                       try {
+                                               Thread.sleep(1000);
+                                       } catch (InterruptedException e) {
+                                               return;
+                                       }
+                               }
+                       }
+               }.start();
+               return parent;
+       }
+
+       protected void fillAppsToolBar(ToolBar toolBar) {
+
+       }
+
+       protected int javaUsedOnTotalPerct(Runtime runtime) {
+               return Math.toIntExact((runtime.totalMemory() - runtime.freeMemory()) * 100 / runtime.totalMemory());
+       }
+
+       protected int javaTotalOnMaxPerct(Runtime runtime) {
+               return Math.toIntExact((runtime.totalMemory()) * 100 / runtime.maxMemory());
+       }
+
+       protected Label label(Composite parent, String text) {
+               Label label = new Label(parent, SWT.WRAP);
+               label.setText(text);
+               return label;
+       }
+
+       protected String cleanHostAddress(String hostAddress) {
+               // remove % from Ipv6 addresses
+               int index = hostAddress.indexOf('%');
+               if (index > 0)
+                       return hostAddress.substring(0, index);
+               else
+                       return hostAddress;
+       }
+
+       protected String convertHardwareAddress(byte[] hardwareAddress) {
+               if (hardwareAddress == null)
+                       return "";
+               // from https://stackoverflow.com/a/2797498/7878010
+               StringBuilder sb = new StringBuilder(18);
+               for (byte b : hardwareAddress) {
+                       if (sb.length() > 0)
+                               sb.append(':');
+                       sb.append(String.format("%02x", b).toUpperCase());
+               }
+               return sb.toString();
+       }
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniImageViewer.java
new file mode 100644 (file)
index 0000000..86ff53f
--- /dev/null
@@ -0,0 +1,129 @@
+package org.argeo.minidesktop;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageLoader;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniImageViewer implements PaintListener {
+       private URL url;
+       private Canvas area;
+
+       private Image image;
+
+       public MiniImageViewer(Composite parent, int style) {
+               parent.setLayout(new GridLayout());
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new RowLayout());
+               Button load = new Button(toolBar, SWT.FLAT);
+               load.setText("\u2191");// up arrow
+               load.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               FileDialog fileDialog = new FileDialog(area.getShell());
+                               String path = fileDialog.open();
+                               if (path != null) {
+                                       setUrl(path);
+                               }
+                       }
+
+               });
+
+               area = new Canvas(parent, SWT.NONE);
+               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               area.addPaintListener(this);
+       }
+
+       protected void load(URL url) {
+               try {
+                       ImageLoader imageLoader = new ImageLoader();
+                       ImageData[] data = imageLoader.load(url.openStream());
+                       image = new Image(area.getDisplay(), data[0]);
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       @Override
+       public void paintControl(PaintEvent e) {
+               e.gc.drawImage(image, 0, 0);
+
+       }
+
+       protected Path url2path(URL url) {
+               try {
+                       Path path = Paths.get(url.toURI());
+                       return path;
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+               }
+       }
+
+       public void setUrl(URL url) {
+               this.url = url;
+               if (area != null)
+                       load(this.url);
+       }
+
+       public void setUrl(String url) {
+               try {
+                       setUrl(new URL(url));
+               } catch (MalformedURLException e) {
+                       // try with http
+                       try {
+                               setUrl(new URL("file://" + url));
+                               return;
+                       } catch (MalformedURLException e1) {
+                               // nevermind...
+                       }
+                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               MiniImageViewer miniBrowser = new MiniImageViewer(shell, SWT.NONE);
+               String url = args.length > 0 ? args[0] : "";
+               if (!url.trim().equals("")) {
+                       miniBrowser.setUrl(url);
+                       shell.setText(url);
+               } else {
+                       shell.setText("*");
+               }
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTerminal.java
new file mode 100644 (file)
index 0000000..196ad0c
--- /dev/null
@@ -0,0 +1,342 @@
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Caret;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class MiniTerminal implements KeyListener, PaintListener {
+
+       private Canvas area;
+       private Caret caret;
+
+       private StringBuffer buf = new StringBuffer("");
+       private StringBuffer userInput = new StringBuffer("");
+       private List<String> history = new ArrayList<>();
+
+       private Point charExtent = null;
+       private int charsPerLine = 0;
+       private String[] lines = new String[0];
+       private List<String> logicalLines = new ArrayList<>();
+
+       private Font mono;
+       private Charset charset;
+
+       private Path currentDir;
+       private Path homeDir;
+       private String host = "localhost";
+       private String username;
+
+       // Sub process
+       private Process process;
+       private boolean running = false;
+       private OutputStream stdIn = null;
+
+       private Thread readOut;
+
+       public MiniTerminal(Composite parent, String url) {
+               this(parent);
+               setPath(url);
+       }
+
+       public MiniTerminal(Composite parent) {
+               charset = StandardCharsets.UTF_8;
+
+               Display display = parent.getDisplay();
+               // Linux-specific
+               mono = new Font(display, "Monospace", 10, SWT.NONE);
+
+               parent.setLayout(new GridLayout());
+               area = new Canvas(parent, SWT.NONE);
+               area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+               caret = new Caret(area, SWT.NONE);
+               area.setCaret(caret);
+
+               area.addKeyListener(this);
+               area.addPaintListener(this);
+
+               username = System.getProperty("user.name");
+               try {
+                       host = InetAddress.getLocalHost().getHostName();
+                       if (host.indexOf('.') > 0)
+                               host = host.substring(0, host.indexOf('.'));
+               } catch (UnknownHostException e) {
+                       host = "localhost";
+               }
+               homeDir = Paths.get(System.getProperty("user.home"));
+               currentDir = homeDir;
+
+               buf = new StringBuffer(prompt());
+       }
+
+       @Override
+       public void keyPressed(KeyEvent e) {
+       }
+
+       @Override
+       public void keyReleased(KeyEvent e) {
+               if (e.keyLocation != 0)
+                       return;// weird characters
+               // System.out.println(e.character);
+               if (e.keyCode == 0xd) {// return
+                       markLogicalLine();
+                       if (!running)
+                               processUserInput();
+                       // buf.append(prompt());
+               } else if (e.keyCode == 0x8) {// delete
+                       if (userInput.length() == 0)
+                               return;
+                       userInput.setLength(userInput.length() - 1);
+                       if (!running && buf.length() > 0)
+                               buf.setLength(buf.length() - 1);
+               } else if (e.stateMask == 0x40000 && e.keyCode == 0x63) {// Ctrl+C
+                       if (process != null)
+                               process.destroy();
+               } else if (e.stateMask == 0x40000 && e.keyCode == 0xdf) {// Ctrl+\
+                       if (process != null) {
+                               process.destroyForcibly();
+                       }
+               } else {
+                       // if (!running)
+                       buf.append(e.character);
+                       userInput.append(e.character);
+               }
+
+               if (area.isDisposed())
+                       return;
+               area.redraw();
+               // System.out.println("Append " + e);
+
+               if (running) {
+                       if (stdIn != null) {
+                               try {
+                                       stdIn.write(Character.toString(e.character).getBytes(charset));
+                               } catch (IOException e1) {
+                                       // TODO Auto-generated catch block
+                                       e1.printStackTrace();
+                               }
+                       }
+               }
+       }
+
+       protected String prompt() {
+               String fileName = currentDir.equals(homeDir) ? "~" : currentDir.getFileName().toString();
+               String end = username.equals("root") ? "]# " : "]$ ";
+               return "[" + username + "@" + host + " " + fileName + end;
+       }
+
+       private void displayPrompt() {
+               buf.append(prompt() + userInput);
+       }
+
+       protected void markLogicalLine() {
+               String str = buf.toString().trim();
+               logicalLines.add(str);
+               buf = new StringBuffer("");
+       }
+
+       private void processUserInput() {
+               String cmd = userInput.toString();
+               userInput = new StringBuffer("");
+               processUserInput(cmd);
+               history.add(cmd);
+       }
+
+       protected void processUserInput(String input) {
+               try {
+                       StringTokenizer st = new StringTokenizer(input);
+                       List<String> args = new ArrayList<>();
+                       while (st.hasMoreTokens())
+                               args.add(st.nextToken());
+                       if (args.size() == 0) {
+                               displayPrompt();
+                               return;
+                       }
+
+                       // change directory
+                       if (args.get(0).equals("cd")) {
+                               if (args.size() == 1) {
+                                       setPath(homeDir);
+                               } else {
+                                       Path newPath = currentDir.resolve(args.get(1));
+                                       if (!Files.exists(newPath) || !Files.isDirectory(newPath)) {
+                                               println(newPath + ": No such file or directory");
+                                               return;
+                                       }
+                                       setPath(newPath);
+                               }
+                               displayPrompt();
+                               return;
+                       }
+                       // show current directory
+                       else if (args.get(0).equals("pwd")) {
+                               println(currentDir);
+                               displayPrompt();
+                               return;
+                       }
+                       // exit
+                       else if (args.get(0).equals("exit")) {
+                               println("logout");
+                               exitCalled();
+                               return;
+                       }
+
+                       ProcessBuilder pb = new ProcessBuilder(args);
+                       pb.redirectErrorStream(true);
+                       pb.directory(currentDir.toFile());
+//                     Process process = Runtime.getRuntime().exec(input, null, currentPath.toFile());
+                       process = pb.start();
+
+                       stdIn = process.getOutputStream();
+                       readOut = new Thread("MiniTerminal read out") {
+                               @Override
+                               public void run() {
+                                       running = true;
+                                       try (BufferedReader in = new BufferedReader(
+                                                       new InputStreamReader(process.getInputStream(), charset))) {
+                                               String line = null;
+                                               while ((line = in.readLine()) != null) {
+                                                       println(line);
+                                               }
+                                       } catch (IOException e) {
+                                               println(e.getMessage());
+                                       }
+                                       stdIn = null;
+                                       displayPrompt();
+                                       running = false;
+                                       readOut = null;
+                                       process = null;
+                               }
+                       };
+                       readOut.start();
+               } catch (IOException e) {
+                       println(e.getMessage());
+                       displayPrompt();
+               }
+       }
+
+       protected int linesForLogicalLine(char[] line) {
+               return line.length / charsPerLine + 1;
+       }
+
+       protected void println(Object line) {
+               buf.append(line);
+               markLogicalLine();
+       }
+
+       protected void refreshLines(int charPerLine, int nbrOfLines) {
+               if (lines.length != nbrOfLines) {
+                       lines = new String[nbrOfLines];
+                       Arrays.fill(lines, null);
+               }
+               if (this.charsPerLine != charPerLine)
+                       this.charsPerLine = charPerLine;
+
+               int currentLine = nbrOfLines - 1;
+               // current line
+               if (buf.length() > 0) {
+                       lines[currentLine] = buf.toString();
+               } else {
+                       lines[currentLine] = "";
+               }
+               currentLine--;
+
+               logicalLines: for (int i = logicalLines.size() - 1; i >= 0; i--) {
+                       char[] logicalLine = logicalLines.get(i).toCharArray();
+                       int linesNeeded = linesForLogicalLine(logicalLine);
+                       for (int j = linesNeeded - 1; j >= 0; j--) {
+                               int from = j * charPerLine;
+                               int to = j == linesNeeded - 1 ? from + charPerLine : Math.min(from + charPerLine, logicalLine.length);
+                               lines[currentLine] = new String(Arrays.copyOfRange(logicalLine, from, to));
+//                             System.out.println("Set line " + currentLine + " to : " + lines[currentLine]);
+                               currentLine--;
+                               if (currentLine < 0)
+                                       break logicalLines;
+                       }
+               }
+       }
+
+       @Override
+       public void paintControl(PaintEvent e) {
+               GC gc = e.gc;
+               gc.setFont(mono);
+               if (charExtent == null)
+                       charExtent = gc.textExtent("a");
+
+               Point areaSize = area.getSize();
+               int charPerLine = areaSize.x / charExtent.x;
+               int nbrOfLines = areaSize.y / charExtent.y;
+               refreshLines(charPerLine, nbrOfLines);
+
+               for (int i = 0; i < lines.length; i++) {
+                       String line = lines[i];
+                       if (line != null)
+                               gc.drawString(line, 0, i * charExtent.y);
+               }
+//             String toDraw = buf.toString();
+//             gc.drawString(toDraw, 0, 0);
+//             area.setCaret(caret);
+       }
+
+       protected void exitCalled() {
+
+       }
+
+       public void setPath(String path) {
+               this.currentDir = Paths.get(path);
+       }
+
+       public void setPath(Path path) {
+               this.currentDir = path;
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               String url = args.length > 0 ? args[0] : System.getProperty("user.home");
+               new MiniTerminal(shell, url) {
+
+                       @Override
+                       protected void exitCalled() {
+                               shell.dispose();
+                               System.exit(0);
+                       }
+               };
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/MiniTextEditor.java
new file mode 100644 (file)
index 0000000..91cd19e
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.minidesktop;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class MiniTextEditor {
+       private URL url;
+       private Text text;
+
+       public MiniTextEditor(Composite parent, int style) {
+               parent.setLayout(new GridLayout());
+
+               Composite toolBar = new Composite(parent, SWT.NONE);
+               toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+               toolBar.setLayout(new RowLayout());
+               Button load = new Button(toolBar, SWT.FLAT);
+               load.setText("\u2191");// up arrow
+               load.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               FileDialog fileDialog = new FileDialog(text.getShell());
+                               String path = fileDialog.open();
+                               if (path != null) {
+                                       setUrl(path);
+                               }
+                       }
+
+               });
+
+               Button save = new Button(toolBar, SWT.FLAT);
+               save.setText("\u2193");// down arrow
+               // save.setText("\u1F609");// emoji
+               save.addSelectionListener(new SelectionAdapter() {
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               save(url);
+                       }
+
+               });
+
+               text = new Text(parent, SWT.WRAP | SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
+               text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+       }
+
+       protected void load(URL url) {
+               text.setText("");
+               // TODO deal with encoding and binary data
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
+                       String line = null;
+                       while ((line = in.readLine()) != null) {
+                               text.append(line + "\n");
+                       }
+                       text.setEditable(true);
+               } catch (IOException e) {
+                       if (e instanceof FileNotFoundException) {
+                               Path path = url2path(url);
+                               try {
+                                       Files.createFile(path);
+                                       load(url);
+                                       return;
+                               } catch (IOException e1) {
+                                       e = e1;
+                               }
+                       }
+                       text.setText(e.getMessage());
+                       text.setEditable(false);
+                       e.printStackTrace();
+                       // throw new IllegalStateException("Cannot load " + url, e);
+               }
+       }
+
+       protected Path url2path(URL url) {
+               try {
+                       Path path = Paths.get(url.toURI());
+                       return path;
+               } catch (URISyntaxException e) {
+                       throw new IllegalStateException("Cannot convert " + url + " to uri", e);
+               }
+       }
+
+       protected void save(URL url) {
+               if (!url.getProtocol().equals("file"))
+                       throw new IllegalArgumentException(url.getProtocol() + " protocol is not supported for write");
+               Path path = url2path(url);
+               try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(path)))) {
+                       out.write(text.getText());
+               } catch (IOException e) {
+                       throw new IllegalStateException("Cannot save " + url + " to " + path, e);
+               }
+       }
+
+       public void setUrl(URL url) {
+               this.url = url;
+               if (text != null)
+                       load(url);
+       }
+
+       public void setUrl(String url) {
+               try {
+                       setUrl(new URL(url));
+               } catch (MalformedURLException e) {
+                       // try with http
+                       try {
+                               setUrl(new URL("file://" + url));
+                               return;
+                       } catch (MalformedURLException e1) {
+                               // nevermind...
+                       }
+                       throw new IllegalArgumentException("Cannot interpret URL " + url, e);
+               }
+       }
+
+       public static void main(String[] args) {
+               Display display = Display.getCurrent() == null ? new Display() : Display.getCurrent();
+               Shell shell = new Shell(display, SWT.SHELL_TRIM);
+
+               MiniTextEditor miniBrowser = new MiniTextEditor(shell, SWT.NONE);
+               String url = args.length > 0 ? args[0] : "";
+               if (!url.trim().equals("")) {
+                       miniBrowser.setUrl(url);
+                       shell.setText(url);
+               } else {
+                       shell.setText("*");
+               }
+
+               shell.open();
+               shell.setSize(new Point(800, 480));
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch())
+                               display.sleep();
+               }
+       }
+
+}
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png
new file mode 100644 (file)
index 0000000..55c614d
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/cheatsheet_obj@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png
new file mode 100644 (file)
index 0000000..54ecae2
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/console_view@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png
new file mode 100644 (file)
index 0000000..eb2fc72
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/delete@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png
new file mode 100644 (file)
index 0000000..06d337a
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/external_browser@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png
new file mode 100644 (file)
index 0000000..31e671c
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/file_obj@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png
new file mode 100644 (file)
index 0000000..adb7c2c
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/fldr_obj@2x.png differ
diff --git a/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png
new file mode 100644 (file)
index 0000000..4c9a16c
Binary files /dev/null and b/rcp/org.argeo.swt.minidesktop/src/org/argeo/minidesktop/nav_home@2x.png differ
diff --git a/rcp/org.argeo.swt.specific.rcp/.classpath b/rcp/org.argeo.swt.specific.rcp/.classpath
new file mode 100644 (file)
index 0000000..457b115
--- /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-1.8" />
+       <classpathentry kind="output" path="bin" />
+</classpath>
diff --git a/rcp/org.argeo.swt.specific.rcp/.gitignore b/rcp/org.argeo.swt.specific.rcp/.gitignore
new file mode 100644 (file)
index 0000000..5e77890
--- /dev/null
@@ -0,0 +1,3 @@
+/target/
+/bin/
+*.log
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/.project b/rcp/org.argeo.swt.specific.rcp/.project
new file mode 100644 (file)
index 0000000..c79ee3f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.argeo.swt.specific.rcp</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore b/rcp/org.argeo.swt.specific.rcp/META-INF/.gitignore
new file mode 100644 (file)
index 0000000..4854a41
--- /dev/null
@@ -0,0 +1 @@
+/MANIFEST.MF
diff --git a/rcp/org.argeo.swt.specific.rcp/bnd.bnd b/rcp/org.argeo.swt.specific.rcp/bnd.bnd
new file mode 100644 (file)
index 0000000..bb88efd
--- /dev/null
@@ -0,0 +1,20 @@
+Import-Package: \
+!java.*,\
+org.apache.commons.io,\
+org.eclipse.core.commands,\
+!org.eclipse.core.runtime,\
+!org.eclipse.ui.plugin,\
+org.eclipse.swt,\
+javax.servlet.http;version="[3,5)",\
+javax.servlet;version="[3,5)",\
+*
+
+Export-Package: org.argeo.*,\
+org.eclipse.rap.fileupload.*;version="3.10",\
+org.eclipse.rap.rwt.*;version="3.10"
+
+# Was !org.eclipse.core.commands,\ why ?
+
+#Bundle-Activator: org.argeo.eclipse.ui.ArgeoUiPlugin
+#Bundle-ActivationPolicy: lazy
+#Ignore-Package: org.eclipse.core.commands
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/build.properties b/rcp/org.argeo.swt.specific.rcp/build.properties
new file mode 100644 (file)
index 0000000..c6b651a
--- /dev/null
@@ -0,0 +1,3 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpClient.java
new file mode 100644 (file)
index 0000000..0d9ce48
--- /dev/null
@@ -0,0 +1,44 @@
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.client.service.BrowserNavigation;
+import org.eclipse.rap.rwt.client.service.BrowserNavigationListener;
+import org.eclipse.rap.rwt.client.service.ClientService;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+
+public class RcpClient implements Client {
+
+       @Override
+       public <T extends ClientService> T getService(Class<T> type) {
+               if (type.isAssignableFrom(JavaScriptExecutor.class))
+                       return (T) javaScriptExecutor;
+               else if (type.isAssignableFrom(BrowserNavigation.class))
+                       return (T) browserNavigation;
+               else
+                       return null;
+       }
+
+       private JavaScriptExecutor javaScriptExecutor = new JavaScriptExecutor() {
+
+               @Override
+               public void execute(String code) {
+                       // TODO Auto-generated method stub
+
+               }
+       };
+       private BrowserNavigation browserNavigation = new BrowserNavigation() {
+
+               @Override
+               public void pushState(String state, String title) {
+                       // TODO Auto-generated method stub
+
+               }
+
+               @Override
+               public void addBrowserNavigationListener(
+                               BrowserNavigationListener listener) {
+                       // TODO Auto-generated method stub
+
+               }
+       };
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/rcp/internal/rwt/RcpResourceManager.java
new file mode 100644 (file)
index 0000000..91109a9
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.eclipse.ui.rcp.internal.rwt;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.commons.io.IOUtils;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RcpResourceManager implements ResourceManager {
+       private Map<String, byte[]> register = Collections
+                       .synchronizedMap(new TreeMap<String, byte[]>());
+
+       @Override
+       public void register(String name, InputStream in) {
+               try {
+                       register.put(name, IOUtils.toByteArray(in));
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot register " + name, e);
+               }
+       }
+
+       @Override
+       public boolean unregister(String name) {
+               return register.remove(name) != null;
+       }
+
+       @Override
+       public InputStream getRegisteredContent(String name) {
+               return new ByteArrayInputStream(register.get(name));
+       }
+
+       @Override
+       public String getLocation(String name) {
+               return name;
+       }
+
+       @Override
+       public boolean isRegistered(String name) {
+               return register.containsKey(name);
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileDialog.java
new file mode 100644 (file)
index 0000000..0c5d346
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+
+public class CmsFileDialog extends FileDialog {
+       public CmsFileDialog(Shell parent, int style) {
+               super(parent, style);
+       }
+
+       public CmsFileDialog(Shell parent) {
+               super(parent);
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/CmsFileUpload.java
new file mode 100644 (file)
index 0000000..638859a
--- /dev/null
@@ -0,0 +1,32 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Composite;
+
+public class CmsFileUpload extends FileUpload {
+       public CmsFileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       @Override
+       public void setText(String text) {
+               super.setText(text);
+       }
+
+       @Override
+       public String getFileName() {
+               return super.getFileName();
+       }
+
+       @Override
+       public String[] getFileNames() {
+               return super.getFileNames();
+       }
+
+       @Override
+       public void addSelectionListener(SelectionListener listener) {
+               super.addSelectionListener(listener);
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/DefaultNLS.java
new file mode 100644 (file)
index 0000000..fbb4fbf
--- /dev/null
@@ -0,0 +1,14 @@
+package org.argeo.eclipse.ui.specific;
+
+/** RCP specific {@link NLS} to be extended */
+public class DefaultNLS {// extends NLS {
+//     public final static String DEFAULT_BUNDLE_LOCATION = "/properties/plugin";
+//
+//     public DefaultNLS() {
+//             this(DEFAULT_BUNDLE_LOCATION);
+//     }
+//
+//     public DefaultNLS(String bundleName) {
+//             NLS.initializeMessages(bundleName, getClass());
+//     }
+}
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiConstants.java
new file mode 100644 (file)
index 0000000..ac862d7
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.eclipse.ui.specific;
+
+/** Constants which are specific to RWT.*/
+public interface EclipseUiConstants {
+       final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+       final static String MARKUP_SUPPORT = null;
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/EclipseUiSpecificUtils.java
new file mode 100644 (file)
index 0000000..d1acbcf
--- /dev/null
@@ -0,0 +1,40 @@
+package org.argeo.eclipse.ui.specific;
+
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.widgets.Widget;
+
+/** Static utilities to bridge differences between RCP and RAP */
+public class EclipseUiSpecificUtils {
+       private final static String CSS_CLASS = "org.eclipse.e4.ui.css.CssClassName";
+
+       public static void setStyleData(Widget widget, Object data) {
+               widget.setData(CSS_CLASS, data);
+       }
+
+       public static Object getStyleData(Widget widget) {
+               return widget.getData(CSS_CLASS);
+       }
+
+       public static void setMarkupData(Widget widget) {
+               // does nothing
+       }
+
+       public static void setMarkupValidationDisabledData(Widget widget) {
+               // does nothing
+       }
+
+       /**
+        * TootlTip support is supported for {@link ColumnViewer} in RCP
+        * 
+        * @see ColumnViewerToolTipSupport#enableFor(Viewer)
+        */
+       public static void enableToolTipSupport(Viewer viewer) {
+               if (viewer instanceof ColumnViewer)
+                       ColumnViewerToolTipSupport.enableFor((ColumnViewer) viewer);
+       }
+
+       private EclipseUiSpecificUtils() {
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/FileDropAdapter.java
new file mode 100644 (file)
index 0000000..524447e
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.eclipse.swt.dnd.DND;
+import org.eclipse.swt.dnd.DropTarget;
+import org.eclipse.swt.dnd.DropTargetAdapter;
+import org.eclipse.swt.dnd.DropTargetEvent;
+import org.eclipse.swt.dnd.FileTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Control;
+
+public class FileDropAdapter {
+
+       public void prepareDropTarget(Control control, DropTarget dropTarget) {
+               dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
+               dropTarget.addDropListener(new DropTargetAdapter() {
+                       @Override
+                       public void dropAccept(DropTargetEvent event) {
+                               if (!FileTransfer.getInstance().isSupportedType(event.currentDataType)) {
+                                       event.detail = DND.DROP_NONE;
+                               }
+                       }
+
+                       @Override
+                       public void drop(DropTargetEvent event) {
+                               handleFileDrop(control, event);
+                       }
+               });
+       }
+
+       public void handleFileDrop(Control control, DropTargetEvent event) {
+               String fileList[] = null;
+               FileTransfer ft = FileTransfer.getInstance();
+               if (ft.isSupportedType(event.currentDataType)) {
+                       fileList = (String[]) event.data;
+               }
+               System.out.println(Arrays.toString(fileList));
+       }
+
+       /** Executed in UI thread */
+       protected void processUpload(InputStream in, String fileName, String contentType) throws IOException {
+
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java b/rcp/org.argeo.swt.specific.rcp/src/org/argeo/eclipse/ui/specific/UiContext.java
new file mode 100644 (file)
index 0000000..20163cf
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.eclipse.ui.specific;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.swt.widgets.Display;
+
+/** Singleton class providing single sources infos about the UI context. */
+public class UiContext {
+
+       public static HttpServletRequest getHttpRequest() {
+               return null;
+       }
+
+       public static HttpServletResponse getHttpResponse() {
+               return null;
+       }
+
+       public static Locale getLocale() {
+               return Locale.getDefault();
+       }
+
+       public static void setLocale(Locale locale) {
+               Locale.setDefault(locale);
+       }
+
+       /** Can always be null */
+       @SuppressWarnings("unchecked")
+       public static <T> T getData(String key) {
+               Display display = getDisplay();
+               if (display == null)
+                       return null;
+               return (T) display.getData(key);
+       }
+
+       public static void setData(String key, Object value) {
+               Display display = getDisplay();
+               if (display == null)
+                       throw new IllegalStateException("Not display available");
+               display.setData(key, value);
+       }
+
+       private static Display getDisplay() {
+               return Display.getCurrent();
+       }
+
+       private UiContext() {
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileDetails.java
new file mode 100644 (file)
index 0000000..fbb36dd
--- /dev/null
@@ -0,0 +1,9 @@
+package org.eclipse.rap.fileupload;
+
+public interface FileDetails {
+       String getContentType();
+
+       long getContentLength();
+
+       String getFileName();
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadEvent.java
new file mode 100644 (file)
index 0000000..a745280
--- /dev/null
@@ -0,0 +1,21 @@
+package org.eclipse.rap.fileupload;
+
+import java.util.EventObject;
+
+public abstract class FileUploadEvent extends EventObject {
+
+       private static final long serialVersionUID = 1L;
+
+       protected FileUploadEvent(FileUploadHandler source) {
+               super(source);
+       }
+
+       public abstract FileDetails[] getFileDetails();
+
+       public abstract long getContentLength();
+
+       public abstract long getBytesRead();
+
+       public abstract Exception getException();
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadHandler.java
new file mode 100644 (file)
index 0000000..7d89300
--- /dev/null
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+/**
+ * A file upload handler is used to accept file uploads from a client. After
+ * creating a file upload handler, the server will accept file uploads to the
+ * URL returned by <code>getUploadUrl()</code>. Upload listeners can be attached
+ * to react on progress. When the upload has finished, a FileUploadHandler has
+ * to be disposed of by calling its <code>dispose()</code> method.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class FileUploadHandler {
+
+       public FileUploadHandler(FileUploadReceiver fileUploadReceiver) {
+       }
+
+       public void dispose() {
+
+       }
+
+       public void addUploadListener(FileUploadListener listener) {
+
+       }
+
+       public String getUploadUrl() {
+               return null;
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadListener.java
new file mode 100644 (file)
index 0000000..b59fd39
--- /dev/null
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import org.eclipse.swt.widgets.Display;
+
+
+/**
+ * Listener to react on progress and completion of a file upload.
+ * <p>
+ * <strong>Note:</strong> This listener will be called from a different thread than the UI thread.
+ * Implementations must use {@link Display#asyncExec(Runnable)} to access the UI.
+ * </p>
+ *
+ * @see FileUploadEvent
+ */
+public interface FileUploadListener {
+
+  /**
+   * Called when new information about an in-progress upload is available.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent#getBytesRead()
+   */
+  void uploadProgress( FileUploadEvent event );
+
+  /**
+   * Called when a file upload has finished successfully.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent
+   */
+  void uploadFinished( FileUploadEvent event );
+
+  /**
+   * Called when a file upload failed.
+   *
+   * @param event event object that contains information about the uploaded file
+   * @see FileUploadEvent#getErrorMessage()
+   */
+  void uploadFailed( FileUploadEvent event );
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/fileupload/FileUploadReceiver.java
new file mode 100644 (file)
index 0000000..3f4cf47
--- /dev/null
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2011, 2013 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Instances of this interface are responsible for reading and processing the data from a file
+ * upload.
+ */
+public abstract class FileUploadReceiver {
+
+  /**
+   * Reads and processes all data from the provided input stream.
+   *
+   * @param stream the stream to read from
+   * @param details the details of the uploaded file like file name, content-type and size
+   * @throws IOException if an input / output error occurs
+   */
+  public abstract void receive( InputStream stream, FileDetails details ) throws IOException;
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/RWT.java
new file mode 100644 (file)
index 0000000..1688594
--- /dev/null
@@ -0,0 +1,45 @@
+package org.eclipse.rap.rwt;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpClient;
+import org.argeo.eclipse.ui.rcp.internal.rwt.RcpResourceManager;
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.service.ResourceManager;
+
+public class RWT {
+       public final static String CUSTOM_VARIANT = "argeo-rcp:CUSTOM_VARIANT";
+       public final static String MARKUP_ENABLED = "argeo-rcp:MARKUP_ENABLED";
+       public static final String TOOLTIP_MARKUP_ENABLED = "argeo-rcp:TOOLTIP_MARKUP_ENABLED";
+       public final static String CUSTOM_ITEM_HEIGHT = "argeo-rcp:CUSTOM_ITEM_HEIGHT";
+       public final static String ACTIVE_KEYS = "argeo-rcp:ACTIVE_KEYS";
+       public final static String CANCEL_KEYS = "argeo-rcp:CANCEL_KEYS";
+       public final static String DEFAULT_THEME_ID  = "argeo-rcp:DEFAULT_THEME_ID";
+
+       public final static int HYPERLINK = 0;
+
+       private static Locale locale = Locale.getDefault();
+       private static RcpClient client = new RcpClient();
+       private static ResourceManager resourceManager = new RcpResourceManager();
+       static {
+
+       }
+
+       public static Locale getLocale() {
+               return locale;
+       }
+
+       public static HttpServletRequest getRequest() {
+               return null;
+       }
+
+       public static ResourceManager getResourceManager() {
+               return resourceManager;
+       }
+
+       public static Client getClient() {
+               return client;
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/SingletonUtil.java
new file mode 100644 (file)
index 0000000..6e30aa6
--- /dev/null
@@ -0,0 +1,7 @@
+package org.eclipse.rap.rwt;
+
+public class SingletonUtil {
+       public static <T> T getSessionInstance(Class<T> clss) {
+               return null;
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/AbstractEntryPoint.java
new file mode 100644 (file)
index 0000000..980a818
--- /dev/null
@@ -0,0 +1,43 @@
+package org.eclipse.rap.rwt.application;
+
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public abstract class AbstractEntryPoint implements EntryPoint {
+       private Display display;
+       private Shell shell;
+
+       protected Shell createShell(Display display) {
+               return new Shell(display);
+       }
+
+       protected void createContents(Composite parent) {
+
+       }
+
+       public int createUI() {
+               display = new Display();
+               shell = createShell(display);
+               shell.setLayout(new GridLayout(1, false));
+               createContents(shell);
+               if (shell.getMaximized()) {
+                       shell.layout();
+               } else {
+                       shell.pack();
+               }
+               shell.open();
+               while (!shell.isDisposed()) {
+                       if (!display.readAndDispatch()) {
+                               display.sleep();
+                       }
+               }
+               display.dispose();
+               return 0;
+       }
+
+       protected Shell getShell() {
+               return shell;
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/Application.java
new file mode 100644 (file)
index 0000000..6cb5f29
--- /dev/null
@@ -0,0 +1,27 @@
+package org.eclipse.rap.rwt.application;
+
+import java.util.Map;
+
+import org.eclipse.rap.rwt.service.ResourceLoader;
+
+public interface Application {
+       public static enum OperationMode {
+               JEE_COMPATIBILITY, SWT_COMPATIBILITY,
+       }
+
+       void setOperationMode(OperationMode operationMode);
+
+       void addResource(String name, ResourceLoader resourceLoader);
+
+       void setExceptionHandler(ExceptionHandler exceptionHandler);
+
+       void addEntryPoint(String path, EntryPointFactory entryPointFactory,
+                       Map<String, String> properties);
+
+       void addEntryPoint(String path, Class<? extends EntryPoint> entryPoint,
+                       Map<String, String> properties);
+
+       void addStyleSheet(String themeId, String styleSheetLocation,
+                       ResourceLoader resourceLoader);
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ApplicationConfiguration.java
new file mode 100644 (file)
index 0000000..961ad70
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface ApplicationConfiguration {
+       void configure(Application application);
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPoint.java
new file mode 100644 (file)
index 0000000..c0d559a
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPoint {
+       int createUI();
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/EntryPointFactory.java
new file mode 100644 (file)
index 0000000..d5b24d8
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface EntryPointFactory {
+       public EntryPoint create();
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/application/ExceptionHandler.java
new file mode 100644 (file)
index 0000000..13daf21
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.application;
+
+public interface ExceptionHandler {
+       public void handleException(Throwable throwable);
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/Client.java
new file mode 100644 (file)
index 0000000..934feae
--- /dev/null
@@ -0,0 +1,18 @@
+package org.eclipse.rap.rwt.client;
+
+import java.io.Serializable;
+
+import org.eclipse.rap.rwt.client.service.ClientService;
+
+public interface Client extends Serializable {
+
+  /**
+   * Returns this client's implementation of a given service, if available.
+   *
+   * @param type the type of the requested service, must be a subtype of ClientService
+   * @return the requested service if provided by this client, otherwise <code>null</code>
+   * @see ClientService
+   */
+  <T extends ClientService> T getService( Class<T> type );
+
+}
\ No newline at end of file
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/WebClient.java
new file mode 100644 (file)
index 0000000..1f19bdd
--- /dev/null
@@ -0,0 +1,10 @@
+package org.eclipse.rap.rwt.client;
+
+public interface WebClient {
+       public final static String FAVICON = "rcp:FAVICON";
+       public final static String PAGE_TITLE = "rcp:PAGE_TITLE";
+       public final static String BODY_HTML = "rcp:BODY_HTML";
+       public final static String THEME_ID = "rcp:THEME_ID";
+       public final static String HEAD_HTML = "rcp:HEAD_HTML";
+       public final static String PAGE_OVERFLOW = "rcp:PAGE_OVERFLOW";
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigation.java
new file mode 100644 (file)
index 0000000..ffba4e4
--- /dev/null
@@ -0,0 +1,7 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigation extends ClientService {
+       void pushState(String state, String title);
+
+       void addBrowserNavigationListener(BrowserNavigationListener listener);
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationEvent.java
new file mode 100644 (file)
index 0000000..3e1b3eb
--- /dev/null
@@ -0,0 +1,10 @@
+package org.eclipse.rap.rwt.client.service;
+
+public class BrowserNavigationEvent {
+       private String state;
+
+       public String getState() {
+               return state;
+       }
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/BrowserNavigationListener.java
new file mode 100644 (file)
index 0000000..8319c03
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface BrowserNavigationListener {
+       public void navigated(BrowserNavigationEvent event);
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/ClientService.java
new file mode 100644 (file)
index 0000000..9f479d1
--- /dev/null
@@ -0,0 +1,6 @@
+package org.eclipse.rap.rwt.client.service;
+
+import java.io.Serializable;
+
+public interface ClientService extends Serializable {
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/JavaScriptExecutor.java
new file mode 100644 (file)
index 0000000..6c44c72
--- /dev/null
@@ -0,0 +1,5 @@
+package org.eclipse.rap.rwt.client.service;
+
+public interface JavaScriptExecutor extends ClientService {
+       public void execute( String code );
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/client/service/UrlLauncher.java
new file mode 100644 (file)
index 0000000..9dae811
--- /dev/null
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2012 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.rwt.client.service;
+
+/**
+ * The UrlLauncher service allows loading an URL in an external window, application or save dialog.
+ *
+ * @since 2.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface UrlLauncher extends ClientService {
+
+  /**
+   * Opens the given URL.
+   *
+   * Any HTTP URL or relative URL will be opened in a new window.
+   * Modern browser may block any attempt to open new windows, but will usually prompt the user to
+   * accept or ignore. Even if accepted, the decision may be applied to only this attempt, or only
+   * to future attempts. It could also trigger a document reload, causing a session restart.
+   *
+   * Non-HTTP URLs like "mailto" will not create a new browser window, but require the client
+   * to have a matching protocol handler registered.
+   *
+   * @param url the URL to open
+   */
+  void openURL( String url );
+
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceLoader.java
new file mode 100644 (file)
index 0000000..7e7116c
--- /dev/null
@@ -0,0 +1,9 @@
+package org.eclipse.rap.rwt.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface ResourceLoader {
+       public InputStream getResourceAsStream(String resourceName)
+                       throws IOException;
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ResourceManager.java
new file mode 100644 (file)
index 0000000..c3379ea
--- /dev/null
@@ -0,0 +1,15 @@
+package org.eclipse.rap.rwt.service;
+
+import java.io.InputStream;
+
+public interface ResourceManager {
+       public void register(String name, InputStream in);
+
+       boolean unregister(String name);
+
+       public InputStream getRegisteredContent(String name);
+
+       public String getLocation(String name);
+
+       public boolean isRegistered(String name);
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/service/ServerPushSession.java
new file mode 100644 (file)
index 0000000..bed194f
--- /dev/null
@@ -0,0 +1,12 @@
+package org.eclipse.rap.rwt.service;
+
+/** Mock, does nothing as this is irrelevant for RCP. */
+public class ServerPushSession {
+       public void start() {
+
+       }
+
+       public void stop() {
+
+       }
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/DropDown.java
new file mode 100644 (file)
index 0000000..b2a2005
--- /dev/null
@@ -0,0 +1,33 @@
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Widget;
+
+public class DropDown {
+       private boolean visible=false;
+
+       public DropDown(Widget parent, int style) {
+               // FIXME implement a shell
+       }
+
+       public DropDown(Widget parent) {
+               this(parent, SWT.NONE);
+       }
+
+       public void setVisible(boolean visible) {
+               this.visible = visible;
+       }
+
+       public boolean isVisible() {
+               return visible;
+       }
+       
+       public void setItems( String[] items ) {
+               
+       }
+       
+       public void setSelectionIndex( int selection ) {
+               
+       }
+       
+}
diff --git a/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java b/rcp/org.argeo.swt.specific.rcp/src/org/eclipse/rap/rwt/widgets/FileUpload.java
new file mode 100644 (file)
index 0000000..cbf1449
--- /dev/null
@@ -0,0 +1,37 @@
+package org.eclipse.rap.rwt.widgets;
+
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+
+public class FileUpload extends Composite {
+
+       public FileUpload(Composite parent, int style) {
+               super(parent, style);
+       }
+
+       public void addSelectionListener(SelectionListener listener) {
+
+       }
+
+       public void submit(String url) {
+
+       }
+
+       public void setImage(Image image) {
+
+       }
+
+       public void setText(String text) {
+
+       }
+
+       public String getFileName() {
+               return null;
+       }
+
+       public String[] getFileNames() {
+               return null;
+       }
+
+}
diff --git a/sdk/.gitignore b/sdk/.gitignore
new file mode 100644 (file)
index 0000000..45dfa56
--- /dev/null
@@ -0,0 +1 @@
+/exec/
diff --git a/sdk/all.policy b/sdk/all.policy
new file mode 100644 (file)
index 0000000..facb613
--- /dev/null
@@ -0,0 +1,3 @@
+grant {
+  permission java.security.AllPermission;
+};
\ No newline at end of file
diff --git a/sdk/argeo-build b/sdk/argeo-build
new file mode 160000 (submodule)
index 0000000..90dfe68
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 90dfe68dd0a8f9510939b2a276aed3d79e563bb2
diff --git a/sdk/argeo-init.properties b/sdk/argeo-init.properties
new file mode 100644 (file)
index 0000000..08df826
--- /dev/null
@@ -0,0 +1,24 @@
+#argeo.osgi.baseUrl=http://forge.argeo.org/data/java/argeo-2.1/
+#argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.65/org.argeo.dep.cms.sdk-2.1.65.jar
+#argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.67/org.argeo.dep.cms.sdk-2.1.67.jar
+#argeo.osgi.distributionUrl=org/argeo/commons/org.argeo.dep.cms.sdk/2.1.68-SNAPSHOT/org.argeo.dep.cms.sdk-2.1.68-SNAPSHOT.jar
+
+#argeo.osgi.boot.debug=true
+
+argeo.osgi.start.1.osgiboot=org.argeo.init
+#argeo.osgi.start.2.node=org.eclipse.equinox.http.servlet,org.eclipse.equinox.http.jetty,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi
+#argeo.osgi.start.3.node=org.argeo.cms,org.eclipse.gemini.blueprint.extender,org.eclipse.equinox.http.registry
+
+#java.security.manager=
+#java.security.policy=file:../../all.policy
+
+argeo.node.repo.type=localfs
+org.osgi.service.http.port=7070
+log4j.configuration=file:../../log4j.properties
+
+#java.util.logging.config.file=../../logging.properties
+
+
+# DON'T CHANGE BELOW
+org.eclipse.rap.workbenchAutostart=false
+org.eclipse.equinox.http.jetty.autostart=false
\ No newline at end of file
diff --git a/sdk/build.sh b/sdk/build.sh
deleted file mode 100644 (file)
index 4cfd55d..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/bash
-
-# TODO source files and allow to override
-A2_CATEGORY=org.argeo.commons
-
-# Works on Fedora 34
-JVM=/usr/lib/jvm/jre-11/bin/java
-ECJ_JAR=/usr/share/java/ecj/ecj.jar
-OSGI_JAR=/usr/share/java/eclipse/osgi.jar
-
-SDK_DIR="$(cd "$(dirname "$0")"; pwd -P)"
-echo SDK: $SDK_DIR
-BUNDLES_BASEDIR="$(cd "$SDK_DIR/.."; pwd -P)"
-A2_UPSTREAM="$(cd "$SDK_DIR/a2/upstream"; pwd -P)"
-A2_BUILD="$(cd "$SDK_DIR/a2/build"; pwd -P)"
-
-echo PREPARING
-SOURCE_PATH=
-for bundle in $BUNDLES_BASEDIR/*.*.*/ ; do
-echo $bundle
-# clean
-rm -rf $bundle/generated/*
-rm -rf $bundle/bin/*
-# copy resources
-rsync -r --exclude "*.java" $bundle/src/ $bundle/bin
-SOURCE_PATH="$SOURCE_PATH $bundle/src[-d $bundle/bin]"
-done
-
-echo COMPILING
-$JVM -jar $ECJ_JAR @$SDK_DIR/ecj.args -time -cp $OSGI_JAR:"$(printf %s: $A2_UPSTREAM/*/*.jar)" $SOURCE_PATH 
-
-echo PACKAGING
-bnd -b $SDK_DIR build
-
-mkdir -p $A2_BUILD/$A2_CATEGORY
-mv $BUNDLES_BASEDIR/*/generated/*.jar $A2_BUILD/$A2_CATEGORY
-bnd index -d $A2_BUILD/ */*.jar
-
-echo DONE
diff --git a/sdk/cms-cluster_0.properties b/sdk/cms-cluster_0.properties
new file mode 100644 (file)
index 0000000..d0c3fb2
--- /dev/null
@@ -0,0 +1,33 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.eclipse.rap.rwt.osgi
+
+argeo.osgi.start.3.node=\
+org.argeo.cms
+
+argeo.osgi.start.5.node=\
+org.argeo.cms.e4.rap
+
+# Local
+org.osgi.service.http.port=7070
+argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
+argeo.node.repo.type=postgresql_cluster_ds
+argeo.node.repo.clusterId=03233754-16c3-49a1-8a00-58bf89a65182
+argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
+argeo.node.repo.dbuser=argeo
+argeo.node.repo.dbpassword=argeo
+
+# Logging
+log4j.configuration=file:../../log4j.properties
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/cms-cluster_1.properties b/sdk/cms-cluster_1.properties
new file mode 100644 (file)
index 0000000..b5e60f8
--- /dev/null
@@ -0,0 +1,33 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.eclipse.rap.rwt.osgi
+
+argeo.osgi.start.3.node=\
+org.argeo.cms
+
+argeo.osgi.start.5.node=\
+org.argeo.cms.e4.rap
+
+# Local
+org.osgi.service.http.port=7071
+argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
+argeo.node.repo.type=postgresql_cluster_ds
+argeo.node.repo.clusterId=52463fa3-2917-4814-9ff7-685c41cbc7c7
+argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo_cluster
+argeo.node.repo.dbuser=argeo
+argeo.node.repo.dbpassword=argeo
+
+# Logging
+log4j.configuration=file:../../log4j.properties
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/cms-e4-rap.properties b/sdk/cms-e4-rap.properties
new file mode 100644 (file)
index 0000000..a11ba7e
--- /dev/null
@@ -0,0 +1,69 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.eclipse.rap.rwt.osgi,\
+org.argeo.init
+
+argeo.osgi.start.3.node=\
+org.argeo.cms
+
+argeo.osgi.start.4.node=\
+org.argeo.cms.servlet,\
+org.argeo.cms.jcr
+
+argeo.osgi.start.5.node=\
+org.argeo.cms.e4.rap
+
+# Local
+argeo.node.repo.type=h2
+org.osgi.service.http.port=7070
+#org.eclipse.equinox.http.jetty.http.host=[IP address to listen to]
+#org.osgi.service.http.port.secure=7073
+#org.eclipse.equinox.http.jetty.websocket.enabled=true
+
+# Logging
+log.org.argeo=DEBUG
+#log4j.configuration=file:../../log4j.properties
+
+# SSL
+#org.osgi.service.http.port.secure=7073
+#org.eclipse.equinox.http.jetty.https.enabled=true
+#org.eclipse.equinox.http.jetty.ssl.keystore=data/node.p12
+#org.eclipse.equinox.http.jetty.ssl.keystoretype=PKCS12
+#org.eclipse.equinox.http.jetty.ssl.password=changeit
+#org.eclipse.equinox.http.jetty.ssl.wantclientauth=true
+
+# Hardened
+#org.osgi.framework.security=osgi
+#java.security.policy=file:../../all.policy
+
+# Internationalisation
+#argeo.i18n.locales=en,fr,ru
+#eclipse.registry.MultiLanguage=true
+#argeo.i18n.defaultLocale=en
+
+# Tuning
+# Number of DB connections
+#argeo.node.repo.maxPoolSize=10
+# Max amount of memory available to Jackrabbit caches
+#argeo.node.repo.maxCacheMB=16
+# Persistence level cache
+#argeo.node.repo.bundleCacheMB=8
+# Search, see http://wiki.apache.org/jackrabbit/Search
+#argeo.node.repo.extractorPoolSize=0
+#argeo.node.repo.searchCacheSize=1000
+#argeo.node.repo.maxVolatileIndexSize=1048576
+
+# Legacy
+#argeo.node.transaction.manager=bitronix
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/cms-local.properties b/sdk/cms-local.properties
new file mode 100644 (file)
index 0000000..e8ae494
--- /dev/null
@@ -0,0 +1,29 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.eclipse.rap.rwt.osgi
+
+argeo.osgi.start.3.node=\
+org.argeo.cms
+
+argeo.osgi.start.5.node=\
+org.argeo.cms.e4.rap
+
+# Local
+argeo.node.repo.type=h2
+org.osgi.service.http.port=7070
+argeo.node.useradmin.uris=os:///
+
+# Logging
+log4j.configuration=file:../../log4j.properties
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/cms-pgsql-ldap.properties b/sdk/cms-pgsql-ldap.properties
new file mode 100644 (file)
index 0000000..3f9aaff
--- /dev/null
@@ -0,0 +1,32 @@
+argeo.osgi.start.2.node=\
+org.eclipse.equinox.http.servlet,\
+org.eclipse.equinox.metatype,\
+org.eclipse.equinox.cm,\
+org.eclipse.equinox.ds,\
+org.eclipse.rap.rwt.osgi
+
+argeo.osgi.start.3.node=\
+org.argeo.cms
+
+argeo.osgi.start.5.node=\
+org.argeo.cms.e4.rap
+
+# Local
+org.osgi.service.http.port=7070
+argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@test-pgsql-ldap/dc=example,dc=com
+argeo.node.repo.type=postgresql_ds
+argeo.node.repo.dburl=jdbc:postgresql://test-pgsql-ldap/argeo
+argeo.node.repo.dbuser=argeo
+argeo.node.repo.dbpassword=argeo
+
+# Logging
+log4j.configuration=file:../../log4j.properties
+
+# DON'T CHANGE BELOW
+org.eclipse.equinox.http.jetty.autostart=false
+org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+com.sun.jndi.ldap.sasl,\
+com.sun.security.jgss,\
+com.sun.jndi.dns,\
+com.sun.nio.file,\
+com.sun.nio.sctp
diff --git a/sdk/configure.sh b/sdk/configure.sh
deleted file mode 100644 (file)
index ed8576d..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-SDK_DIR="$(cd "$(dirname "$0")"; pwd -P)"
-A2_UPSTREAM="$(cd "$SDK_DIR/a2/upstream"; pwd -P)"
-
-
-mvn -f $SDK_DIR clean assembly:single -Pa2-provided
-rsync -rv $SDK_DIR/target/sdk-*-a2-provided/ $A2_UPSTREAM
-bnd index -d $A2_UPSTREAM/ */*.jar
-
diff --git a/sdk/deploy/argeo-init/etc/argeo.d/jvm.args b/sdk/deploy/argeo-init/etc/argeo.d/jvm.args
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.debug b/sdk/deploy/argeo-init/etc/argeo.d/jvm.args.debug
new file mode 100644 (file)
index 0000000..4e6b1dc
--- /dev/null
@@ -0,0 +1 @@
+-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:8000
\ No newline at end of file
diff --git a/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service b/sdk/deploy/argeo-init/usr/lib/systemd/system/argeo@.service
new file mode 100644 (file)
index 0000000..1cd43c9
--- /dev/null
@@ -0,0 +1,32 @@
+[Unit]
+Description=Argeo node %I
+After=network.target
+Wants=postgresql.service
+
+[Service]
+Type=simple
+StateDirectory=argeo.d/%I
+LogsDirectory=argeo.d/%I
+ConfigurationDirectory=argeo.d/%I
+CacheDirectory=argeo.d/%I
+WorkingDirectory=/var/lib/argeo.d/%I
+
+ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java \
+-Dosgi.configuration.cascaded=true \
+-Dosgi.sharedConfiguration.area=/etc/argeo.d/%I \
+-Dosgi.sharedConfiguration.area.readOnly=true \
+-Dosgi.configuration.area=/var/lib/argeo.d/%I/state \
+-Dosgi.instance.area=/var/lib/argeo.d/%I/data \
+-Dargeo.node.repo.indexesBase=/var/cache/argeo.d/%I/indexes \
+-Dorg.osgi.framework.bootdelegation=com.sun.jndi.ldap,com.sun.jndi.ldap.sasl,com.sun.security.jgss,com.sun.jndi.dns,com.sun.nio.file,com.sun.nio.sctp \
+-Declipse.ignoreApp=true \
+-Dosgi.noShutdown=true \
+-Dorg.eclipse.equinox.http.jetty.autostart=false \
+@/etc/argeo.d/jvm.args \
+@/etc/argeo.d/%I/jvm.args \
+@/usr/share/argeo/jvm.args
+# Exit codes of the JVM when SIGTERM or SIGINT have been caught:
+SuccessExitStatus=143 130
+
+[Install]
+WantedBy=multi-user.target
diff --git a/sdk/deploy/argeo-init/usr/share/argeo/SETUP.txt b/sdk/deploy/argeo-init/usr/share/argeo/SETUP.txt
new file mode 100644 (file)
index 0000000..708e587
--- /dev/null
@@ -0,0 +1,9 @@
+
+# 389 Directory Server
+sudo dscreate from-file argeo-slapd.inf
+sudo dsconf -D "cn=Directory Manager" ldap://localhost backend import <backend> <path to LDIF file> 
+
+# PostgreSQL
+sudo postgresql-setup initdb
+sudo systemctl start postgresql
+sudo -u postgres psql < argeo-pgsql-setup.sql
diff --git a/sdk/deploy/argeo-init/usr/share/argeo/all.policy b/sdk/deploy/argeo-init/usr/share/argeo/all.policy
new file mode 100644 (file)
index 0000000..facb613
--- /dev/null
@@ -0,0 +1,3 @@
+grant {
+  permission java.security.AllPermission;
+};
\ No newline at end of file
diff --git a/sdk/deploy/argeo-init/usr/share/argeo/argeo-pgsql-setup.sql b/sdk/deploy/argeo-init/usr/share/argeo/argeo-pgsql-setup.sql
new file mode 100644 (file)
index 0000000..886f60a
--- /dev/null
@@ -0,0 +1,2 @@
+CREATE USER argeo WITH PASSWORD 'argeo';
+CREATE DATABASE argeo WITH OWNER argeo;
diff --git a/sdk/deploy/argeo-init/usr/share/argeo/argeo-slapd-setup.inf b/sdk/deploy/argeo-init/usr/share/argeo/argeo-slapd-setup.inf
new file mode 100644 (file)
index 0000000..98ad97a
--- /dev/null
@@ -0,0 +1,9 @@
+[general]
+[slapd]
+instance_name = argeo
+root_dn = cn=Directory Manager
+root_password = argeoargeo
+
+[backend-userroot]
+create_suffix_entry = True
+suffix = dc=example,dc=com
\ No newline at end of file
diff --git a/sdk/deploy/argeo-init/usr/share/argeo/jvm.args b/sdk/deploy/argeo-init/usr/share/argeo/jvm.args
new file mode 100644 (file)
index 0000000..27561e3
--- /dev/null
@@ -0,0 +1 @@
+-cp /usr/share/a2/org.argeo.tp.eclipse.equinox/org.eclipse.osgi.3.17.jar:/usr/share/a2/org.argeo.cms/org.argeo.init.2.3.jar org.argeo.init.Service
\ No newline at end of file
diff --git a/sdk/ecj.args b/sdk/ecj.args
deleted file mode 100644 (file)
index 306fd82..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
--11
--nowarn
\ No newline at end of file
diff --git a/sdk/init/node/.gitignore b/sdk/init/node/.gitignore
new file mode 100644 (file)
index 0000000..f619744
--- /dev/null
@@ -0,0 +1,4 @@
+/krb5.keytab
+/krb5.keytab.old
+/*.p12
+/*.jks
\ No newline at end of file
diff --git a/sdk/init/node/ou=roles,ou=node.ldif b/sdk/init/node/ou=roles,ou=node.ldif
new file mode 100644 (file)
index 0000000..ffa9073
--- /dev/null
@@ -0,0 +1,12 @@
+dn: cn=admin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=People,dc=example,dc=com
+
+dn: cn=userAdmin,ou=roles,ou=node
+objectClass: groupOfNames
+objectClass: top
+member: cn=admin,ou=roles,ou=node
+cn: userAdmin
+
diff --git a/sdk/pom.xml b/sdk/pom.xml
deleted file mode 100644 (file)
index 9631f6a..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.1-SNAPSHOT</version>
-               <relativePath>..</relativePath>
-       </parent>
-       <artifactId>sdk</artifactId>
-       <name>Commons SDK</name>
-       <packaging>pom</packaging>
-       <dependencies>
-               <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>
-       <profiles>
-               <profile>
-                       <id>a2-provided</id>
-                       <build>
-                               <plugins>
-                                       <plugin>
-                                               <artifactId>maven-assembly-plugin</artifactId>
-                                               <configuration>
-                                                       <descriptorRefs>
-                                                               <descriptorRef>a2-provided</descriptorRef>
-                                                       </descriptorRefs>
-                                               </configuration>
-                                       </plugin>
-                               </plugins>
-                       </build>
-               </profile>
-       </profiles>
-</project>
\ No newline at end of file
diff --git a/sdk/ssl/.gitignore b/sdk/ssl/.gitignore
new file mode 100644 (file)
index 0000000..bc77402
--- /dev/null
@@ -0,0 +1,7 @@
+/CA/
+/*.p12
+/*.jks
+/nssdb/
+/*.pem
+/old/
+/rootCA/
diff --git a/sdk/ssl/openssl.cnf b/sdk/ssl/openssl.cnf
new file mode 100644 (file)
index 0000000..05bb6f7
--- /dev/null
@@ -0,0 +1,120 @@
+dir            = ./CA          # Where everything is kept
+
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+[ CA_default ]
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem # The private key
+x509_extensions        = usr_cert              # The extentions to add to the cert
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+crl_extensions = crl_ext
+default_days   = 365                   # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = default               # use public key default MD
+preserve       = no                    # keep passed DN ordering
+policy         = policy_match
+
+[ policy_match ]
+countryName            = optional
+stateOrProvinceName    = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ req ]
+default_bits           = 4096
+default_md             = sha1
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extensions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+input_password = demo
+output_password = demo
+
+string_mask = utf8only
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_min                        = 2
+countryName_max                        = 2
+#stateOrProvinceName           = State or Province Name (full name)
+#localityName                  = Locality Name (eg, city)
+0.organizationName             = Organization Name (eg, company)
+organizationalUnitName         = Organizational Unit Name (eg, section)
+commonName                     = Common Name (eg, your name or your server\'s hostname)
+commonName_max                 = 64
+emailAddress                   = Email Address
+emailAddress_max               = 64
+# SET-ex3                      = SET extension number 3
+
+##
+## DEFAULT VALUES
+##
+countryName_default            = DE
+#stateOrProvinceName_default   = Berlin
+#localityName_default  = Berlin
+0.organizationName_default     = Example
+organizationalUnitName_default = Certificate Authorities
+commonName_default     = Intermediate CA
+
+[ req_attributes ]
+#challengePassword             = A challenge password
+#challengePassword_min         = 4
+#challengePassword_max         = 20
+#unstructuredName              = An optional company name
+
+[ usr_cert ]
+basicConstraints=CA:FALSE
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+subjectAltName=email:move
+issuerAltName=issuer:copy
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+[ v3_ca ]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ v3_intermediate_ca ]
+# Extensions for a typical intermediate CA (`man x509v3_config`).
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true, pathlen:0
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ crl_ext ]
+issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ server_ext ]
+extendedKeyUsage=serverAuth
+
+[ user_ext ]
+extendedKeyUsage=clientAuth,emailProtection
diff --git a/sdk/ssl/openssl_root.cnf b/sdk/ssl/openssl_root.cnf
new file mode 100644 (file)
index 0000000..c689459
--- /dev/null
@@ -0,0 +1,120 @@
+dir            = ./rootCA              # Where everything is kept
+
+[ ca ]
+default_ca     = CA_default            # The default ca section
+
+[ CA_default ]
+certs          = $dir/certs            # Where the issued certs are kept
+crl_dir                = $dir/crl              # Where the issued crl are kept
+database       = $dir/index.txt        # database index file.
+new_certs_dir  = $dir/newcerts         # default place for new certs.
+certificate    = $dir/cacert.pem       # The CA certificate
+serial         = $dir/serial           # The current serial number
+crlnumber      = $dir/crlnumber        # the current crl number
+crl            = $dir/crl.pem          # The current CRL
+private_key    = $dir/private/cakey.pem # The private key
+x509_extensions        = usr_cert              # The extentions to add to the cert
+name_opt       = ca_default            # Subject Name options
+cert_opt       = ca_default            # Certificate field options
+crl_extensions = crl_ext
+default_days   = 3650          # how long to certify for
+default_crl_days= 30                   # how long before next CRL
+default_md     = default               # use public key default MD
+preserve       = no                    # keep passed DN ordering
+policy         = policy_match
+
+[ policy_match ]
+countryName            = optional
+stateOrProvinceName    = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ policy_anything ]
+countryName            = optional
+stateOrProvinceName    = optional
+localityName           = optional
+organizationName       = optional
+organizationalUnitName = optional
+commonName             = optional
+emailAddress           = optional
+
+[ req ]
+default_bits           = 4096
+default_md             = sha1
+default_keyfile        = privkey.pem
+distinguished_name     = req_distinguished_name
+attributes             = req_attributes
+x509_extensions        = v3_ca # The extensions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+input_password = demo
+output_password = demo
+
+string_mask = utf8only
+req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName                    = Country Name (2 letter code)
+countryName_min                        = 2
+countryName_max                        = 2
+#stateOrProvinceName           = State or Province Name (full name)
+#localityName                  = Locality Name (eg, city)
+0.organizationName             = Organization Name (eg, company)
+organizationalUnitName         = Organizational Unit Name (eg, section)
+commonName                     = Common Name (eg, your name or your server\'s hostname)
+commonName_max                 = 64
+emailAddress                   = Email Address
+emailAddress_max               = 64
+# SET-ex3                      = SET extension number 3
+
+##
+## DEFAULT VALUES
+##
+countryName_default            = DE
+#stateOrProvinceName_default   = Berlin
+#localityName_default  = Berlin
+0.organizationName_default     = Example
+organizationalUnitName_default = Certificate Authorities
+commonName_default     = Root CA
+
+[ req_attributes ]
+#challengePassword             = A challenge password
+#challengePassword_min         = 4
+#challengePassword_max         = 20
+#unstructuredName              = An optional company name
+
+[ usr_cert ]
+basicConstraints=CA:FALSE
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+subjectAltName=email:move
+issuerAltName=issuer:copy
+
+[ v3_req ]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+[ v3_ca ]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ v3_intermediate_ca ]
+# Extensions for a typical intermediate CA (`man x509v3_config`).
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true, pathlen:0
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+[ crl_ext ]
+issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ server_ext ]
+extendedKeyUsage=serverAuth
+
+[ user_ext ]
+extendedKeyUsage=clientAuth,emailProtection
diff --git a/sdk/ssl/ssl.sh b/sdk/ssl/ssl.sh
new file mode 100644 (file)
index 0000000..1caa4b3
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+# COMPLETELY UNSAFE - FOR DEVELOPMENT ONLY
+# Run this script from its directory
+# all *.p12 passwords are 'demo'
+# all *.jks passwords are 'changeit'
+
+# Fail if any error
+set -e
+
+ROOT_CA_DN="/C=DE/O=Example/OU=Certificate Authorities/CN=Root CA/"
+INTERMEDIATE_CA_DN="/C=DE/O=Example/OU=Certificate Authorities/CN=Intermediate CA/"
+SERVER_DN=/C=DE/O=Example/OU=Systems/CN=$HOSTNAME/
+USERS_BASE_DN=/DC=com/DC=example/OU=People
+
+echo -- Init directory structures
+mkdir -p ./rootCA/{certs,crl,csr,newcerts,private}
+mkdir -p ./CA/{certs,crl,csr,newcerts,private}
+
+#
+# Root CA
+#
+export OPENSSL_CONF=./openssl_root.cnf
+export CATOP=./rootCA
+echo -- Create root CA in $CATOP
+touch $CATOP/index.txt
+openssl req -new -newkey rsa:4096 -extensions v3_ca \
+ -subj "$ROOT_CA_DN" \
+ -keyout $CATOP/private/cakey.pem -passout pass:demo -out ca_csr.pem \
+ 2>/dev/null # quiet
+openssl ca -create_serial -selfsign -batch -passin pass:demo -in ca_csr.pem -out $CATOP/cacert.pem \
+ 2>/dev/null # quiet
+
+echo -- Create intermediate CA in ./CA
+openssl req -new -newkey rsa:4096 -extensions v3_intermediate_ca \
+ -subj "$INTERMEDIATE_CA_DN" \
+ -keyout ./CA/private/cakey.pem -passout pass:demo -out ica_csr.pem \
+ 2>/dev/null # quiet
+openssl ca -batch -passin pass:demo -in ica_csr.pem -out ./CA/cacert.pem \
+ 2>/dev/null # quiet
+
+#
+# Intermediate CA
+#      
+export OPENSSL_CONF=./openssl.cnf
+export CATOP=./CA
+
+# create index and serial
+touch $CATOP/index.txt
+openssl x509 -in $CATOP/cacert.pem -noout -next_serial -out $CATOP/serial \
+ 2>/dev/null # quiet
+
+echo -- Create server key and certificate
+openssl req -new -newkey rsa:4096 -extensions server_ext \
+ -subj $SERVER_DN \
+ -keyout node_key.pem -passout pass:demo -out node_csr.pem \
+ 2>/dev/null # quiet
+openssl ca -batch -passin pass:demo -in node_csr.pem -out node_crt.pem \
+ 2>/dev/null # quiet
+
+# create CA chain
+cat node_crt.pem ./CA/cacert.pem ./rootCA/cacert.pem > chain.pem
+
+# convert to p12
+openssl pkcs12 -export -passin pass:demo -passout pass:changeit \
+ -name "$HOSTNAME" -inkey node_key.pem -in chain.pem \
+ -out node.p12 \
+ 2>/dev/null # quiet
+
+echo -- Import Certificate Authority into keystore
+keytool -importcert -noprompt -keystore node.p12 -storepass changeit \
+ -alias "rootCA" -file ./rootCA/cacert.pem
+keytool -importcert -noprompt -keystore node.p12 -storepass changeit \
+ -alias "CA" -file ./CA/cacert.pem
+
+echo -- Copy node.p12 to ../init/node
+cp node.p12 ../init/node/
+
+echo -- Create 'root' user client certificate root.p12
+openssl req -new -newkey rsa:4096 -extensions user_ext \
+ -subj $USERS_BASE_DN/UID=root/ \
+ -keyout newkey.pem -passout pass:demo -out newcsr.pem \
+ 2>/dev/null # quiet
+
+openssl ca -preserveDN -batch -passin pass:demo -in newcsr.pem -out newcrt.pem \
+ 2>/dev/null # quiet
+
+# create new CA chain
+#cat newcrt.pem ./CA/cacert.pem ./rootCA/cacert.pem > newchain.pem
+openssl pkcs12 -export -passin pass:demo -passout pass:demo \
+ -name "root" -inkey newkey.pem -in chain.pem \
+ -out root.p12 \
+ 2>/dev/null # quiet
+
+# demo user
+#openssl req -new -newkey rsa:4096 -extensions user_ext -days 365 \
+# -subj $USERS_BASE_DN/UID=demo/ \
+# -keyout newkey.pem -passout pass:demo -out newcsr.pem
+#openssl ca -preserveDN -batch -passin pass:demo -in newcsr.pem -out newcrt.pem
+#openssl pkcs12 -export -passin pass:demo -passout pass:demo \
+# -name "demo" -inkey newkey.pem -in newcrt.pem \
+# -out demo.p12
+
+# Self-signed
+#openssl req -x509 -new -newkey rsa:4096 -extensions server_ext -days 365 \
+# -subj $SERVER_DN \
+# -keyout newkey.pem -passout pass:demo -out newcrt.pem
+# Self-signed server certificate
+#openssl pkcs12 -export -passin pass:demo -passout pass:changeit \
+# -name "jetty" -inkey newkey.pem -in newcrt.pem \
+# -certfile ./CA/cacert.pem \
+# -out server.p12
+
+echo ## Clean up
+rm -vf *.pem